public
Description: Rails plugin: provides a convenient way to deal with lookup tables and drop downs menus.
Homepage: http://github.com/rpheath/acts_as_lookup/blob/master/README.textile
Clone URL: git://github.com/rpheath/acts_as_lookup.git
acts_as_lookup / lib / acts_as_lookup.rb
100644 119 lines (108 sloc) 4.536 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
module RPH
  module ActsAsLookup
    module Error
      class CustomError < RuntimeError
        def self.message(msg=nil); msg.nil? ? @message : self.message = msg; end
        def self.message=(msg); @message = msg; end
      end
      
      # custom error classes
      class InvalidAttr < CustomError
        message "attr passed to `acts_as_lookup' does not exist"; end
      class InvalidLookup < CustomError
        message "model passed to `lookup_for' does not have the `act_as_lookup' declaration"; end
      class InvalidModel < CustomError
        message "model passed to `lookup_for' does not seem to exist"; end
    end
    
    def self.included(base)
      base.extend ActMethods
    end
    
    module ActMethods
      # Example
      # class Category < ActiveRecord::Base
      # acts_as_lookup :title
      # end
      #
      # class User < ActiveRecord::Base
      # acts_as_lookup [:first_name, :last_name]
      # end
      def acts_as_lookup(field_to_select, optionz={})
        valid_attributes = [field_to_select].flatten.all? { |attr| self.new.respond_to?(attr) }
        raise(Error::InvalidAttr, Error::InvalidAttr.message) unless valid_attributes
        
        options = {
          :default_text => '--',
          :order => [field_to_select].flatten.compact.join(',')
        }.merge!(optionz)
      
        class_inheritable_accessor :options, :field_to_select, :selection_text
        extend ClassMethods
      
        self.options = options
        self.field_to_select = field_to_select
      end
    end
  
    module ClassMethods
      # $> Category.options_for_select
      # $> => [['--', nil], ['Title1', 1], ['Title2', 2], ['Title3', 3]]
      #
      # Example:
      # <%= f.select :category_id, Category.options_for_select -%>
      def options_for_select
        rows = self.find(:all, :conditions => (options[:conditions] || {}), :order => options[:order])
        default_selection = (options[:default_text] == :first ? [] : [[options[:default_text], nil]])
        default_selection + rows.collect do |r|
          [
            field_to_select.is_a?(Array) ?
              field_to_select.inject([]) { |attrs, attr| attrs << r.send(attr) }.compact.join(' ') :
                r.send(field_to_select),
            r.id
          ]
        end
      end
    end
  
    module ViewHelpers
      # obj - object relating to whatever form you're on
      # f_key - the foreign key relating the lookup table
      # (assumes the 'category_id' pattern common to Rails)
      # options - same options allowed by the select tag
      # html_options - same html_options allowed by the select tag
      #
      # Example:
      # <% form_tag :url => project_path(@project) do -%>
      # Title: <%= text_field :project, :title -%>
      # Category: <%= lookup_for :project, :category_id -%>
      # <% end -%>
      #
      # Note: lookup_for will attempt to find the association that
      # uses the given foreign key +f_key+, but will fallback to
      # classifying the +f_key+ by removing the _id portion
      def lookup_for(obj, f_key, options={}, html_options={})
        object = options[:object] || instance_variable_get("@#{obj}")
        klass = nil
        
        # find association that matches the foreign key
        object.class.reflect_on_all_associations.each do |reflection|
          if reflection.primary_key_name == f_key.to_s
            klass = reflection.class_name.constantize
            break
          end
        end unless object.nil?
        
        begin
          klass ||= f_key.to_s.gsub(/_id$/, '').classify.constantize
        rescue NameError
          raise(Error::InvalidModel, Error::InvalidModel.message)
        end
        raise(Error::InvalidLookup, Error::InvalidLookup.message) unless klass && klass.respond_to?(:options_for_select)
        select(obj.to_sym, f_key.to_sym, klass.options_for_select, options, html_options)
      end
      
      # allows `lookup_for' to be used with the
      # <% form_for @object do |f| %> syntax
      module FormBuilder
        # Example:
        # <% form_for :project do |f| -%>
        # Title: <%= f.text_field :title -%>
        # Category: <%= f.lookup_for :category_id -%>
        # <% end -%>
        def lookup_for(f_key, options={}, html_options={})
          @template.lookup_for(@object_name, f_key, options.merge(:object => @object), html_options)
        end
      end
    end
  end
end