module Lolita module Configuration module Field # Fields with Array is used to # * create select for belongs_to association or # * has_and_belongs_to_many field for selecting all associated objects or # * for polymorphic belongs_to associations # ==Polymorphic builder (:polymorphic) # Create two select boxes one of all related classes and second with related records for that class. # Related classes can have polymorphic_select_name instance method, that is used to populate second # select with visible values by default it calles text_method. It will fallback first content column. Class should respond to one # of these. class Array < Lolita::Configuration::Field::Base include Lolita::Hooks MAX_RECORD_COUNT = 300 add_hook :after_association_loaded lolita_accessor :text_method,:value_method,:association,:include_blank lolita_accessor :related_classes def initialize dbi,name,*args, &block @include_blank=true super self.find_dbi_field unless self.dbi_field @association ||= self.dbi_field ? self.dbi_field.association : detect_association self.run(:after_association_loaded) self.builder = detect_builder unless @builder self.name = recognize_real_name end # For details see Lolita::Configuration::Search def search *args, &block if (args && args.any?) || block_given? self.after_association_loaded do @search = create_search(*args,&block) end end @search end def create_search *args, &block Lolita::Configuration::Search.new(Lolita::DBI::Base.create(@association.klass),*args,&block) end def values=(value=nil) @values=value end # Use this with block if values are dynamicly collected. def values value=nil, &block @values=value || block if value || block_given? @values end # Collect values for array type field. # Uses text_method for content. By default it search for # first _String_ type field in DB. Uses value_method for value, # by default it it is id. def association_values(record = nil) #TODO test @association_values=if values values elsif search search.run("") elsif @association && @association.polymorphic? polymorphic_association_values(record) elsif @association klass=@association.klass options_array(collect_records_for(klass)) else [] end @association_values end # Collect values for polymorphic association, you may pass # * :klass - class that's records are used # * :record - record class that has polymorphic association. It is used to call to detect related object class. def polymorphic_association_values(options={}) options ||= {} options[:klass] ||= options[:record] && options[:record].send(self.name) ? options[:record].send(self.name).class : nil if options[:klass] options_array(collect_records_for(options[:klass])) else [] end end def options_array(collection) klass = collection.last ? collection.last.class : nil collection.map{|r| [r.send(current_text_method(klass)),r.send(current_value_method)] } end def current_text_method(klass) @text_method || default_text_method(klass) end def current_value_method @value_method || :id end # used in views for shorter accessing to values def view_values(view) record = view.send(:current_form).object values = association_values(record) if values.respond_to?(:call) values.call(view) else association_values(record) end end def detect_builder if @association if @association.polymorphic? "polymorphic" elsif @association.macro == :many_to_many "autocomplete" else "select" end else "select" end end def detect_association unless @association dbi.associations[self.name.to_sym] else @association end end def polymorphic_classes if @related_classes @related_classes.map do |klass| [klass.constantize.lolita_model_name.human, klass.to_s] end else [] end end def recognize_real_name if @association && !@association.polymorphic? && @association.macro == :one @real_name = self.name self.name = @association.key else @name end end private def default_text_method(klass) assoc_dbi=Lolita::DBI::Base.create(klass) rescue nil if assoc_dbi field = assoc_dbi.fields.detect{|f| f.name.to_s == "title"} field ||= assoc_dbi.fields.detect{|f| f.name.to_s == "name"} field ||= assoc_dbi.fields.detect{|f| f.type.to_s=="string"} if field field.name else raise Lolita::FieldTypeError, %^ Can't find any content field in #{assoc_dbi.klass}. Use text_method in #{klass} to set one. ^ end else warn("Not a ORM class (#{klass.inspect})") end end def collect_records_for(klass) if klass.count > MAX_RECORD_COUNT raise ArgumentError.new("#{@dbi.klass} field #{@name} association has too many records(#{klass.count})") else klass.all end end end end end end