module AnafHabtm module ActiveRecord def autocomplete_format(&block) class_eval do @autocomplete_block = block def self.to_autocomplete(items) items.map{|t| @autocomplete_block.call(t)}.to_json end end end # options = {:named=>"name", # :ar_options=>{:autosave=>true}} def anaf_habtm(association, options={}, &block) raise "Must specify :find or a code block." if (block.nil? && !options.has_key?(:find) && !options[:create]) class_eval do # Define a proc that will look up the (potentially) existing object finder = proc {|id| association.to_s.singularize.camelize.constantize.where(:id=>id).first } # Define a proc that will set the association collection set_collection = proc {|me, coll| me.send("#{association.to_s.tableize}=", coll)} has_and_belongs_to_many association.to_sym, options[:ar_options]||={} # Define the actual association setter. define_method "#{association.to_s.tableize}_attributes=", lambda{|attributes_for_association| coll = [] klass = association.to_s.singularize.camelize.constantize attributes_for_association.each_value do |params| next if params["_destroy"] == "1" obj = finder.call(params["id"]) if params.has_key?("id") params.extend(AnafHabtm::HashExtension) params_copy = params.copy_without_destroy # ActiveRecord::Base.attributes=() doesn't like extra parameters. if block.nil? unless obj srch = klass options[:find].each do |scope, name| if name.class==Hash srch = klass.send(scope, params[name.first[0].to_s][name.first[1].to_s]) else srch = klass.send(scope, params[name.to_s]) end obj = srch.first end obj = obj ||= klass.new if options[:create] unless obj raise "Couldn't find or create #{association} named #{name}." end end obj.attributes = params_copy obj.save coll << obj else coll << block.call(params_copy, obj) end end set_collection.call(self, coll) } end end def association_text(association, opts={}) raise "Must give a name for text_association" unless opts.has_key?(:name) class_eval do if opts.has_key?(:class_name) klass = opts[:class_name].constantize else klass = association.to_s.singularize.camelize.constantize end define_method "#{association.to_s}_text=", lambda{ |text| return if text.empty? coll = StringReader.new.read_items(text) do |name, commentary| a = klass.undecorate(name) if klass.methods.include?("undecorate") if opts.has_key?(:scope) obj = self.send(association).scopes[opts[:scope]].call(name).first end params = {opts[:name]=>name} params[opts[:commentary]] = commentary if opts.has_key?(:commentary) obj = obj ||= klass.new obj = klass.find(obj) unless obj.new_record? obj.decoration = a if a obj.attributes = params obj.save obj end self.send("#{association.to_s}=", coll) } define_method "#{association.to_s}_text", lambda{ StringReader.new.write_items(self.send(association.to_s)) do |item| name = item.send(opts[:name]) name = item.decorate(name) if item.methods.include?("decorate") [name, opts.has_key?(:commentary) ? item.send(opts[:commentary]) : ""] end } end end def named_association(member, attribute, opts={}) member = member.to_s klass = (opts.has_key?(:class_name) ? opts[:class_name] : member.to_s.singularize.camelize).constantize attribute = attribute.to_s if opts.has_key?(:create) class_eval do define_method "#{member}_#{attribute}=", lambda{|value| return if value.blank? obj = klass.named(value) obj = obj ||= klass.create(attribute => value) self.send("#{member}=", obj) } end else class_eval do define_method "#{member}_#{attribute}=", lambda{|value| self.send("#{member}=",klass.named(value)) unless value.blank? } end end class_eval "def #{member}_#{attribute}; #{member}.#{attribute} if #{member}; end;" end def search_on(*cols) class_eval "def self.search_columns; #{cols.map{|t| t.to_s}.to_ary.inspect}; end;" class_eval do scope :search, lambda{|str| items = like_condition(str.downcase) if scopes.has_key?(:search_mod) items = items.search_mod end items } scope :with_name, lambda{|str| equals_condition(str.downcase).limit(1) } end end def lookup(params) str = params[:id] if str.match(/\D/) named(str) else find(str) end end def like_condition(str) where(condition("ilike '%#{str}%'")) end def equals_condition(str) where(condition("= '#{str.downcase}'")) end def named(str) with_name(str).first end def condition(cond) search_columns.map{|c| "trim(lower(#{c})) #{cond}"}.join(" or ") end end end