module DentzSinclair module ColumnWithSpaces def self.included(base) base.extend ClassMethods end # Intercepts call to ActiveRecord::AttributeMethods.method_missing # instance method. def method_missing(method_id, *args, &block) method_name = self.class.get_attribute_name(method_id.to_s) super(method_name,*args,&block) end module ClassMethods # Intercepts call and rewrites the method_defintion string that is class_evaled to # generate accessor and query methods for ActiveRecord::Base models def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name) m1, m2, m3 = method_definition.match(/(def\s+)([^;]*)(;.*)/).captures method_definition = "#{m1.strip} #{get_attribute_name(m2)}#{m3}" if m2 super(attr_name, method_definition, method_name) end # The following is ugly and brittle, but because of the way that the # attribute_names are written into the arguments to the construct_attributes_from_arguments # method calls it was necessary to bring in the entire method_missing from # ActiveRecord::Base in order to enable dynamic finders to take attribute names # with spaces in them. def method_missing(method_id, *arguments) if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s) finder = determine_finder(match) attribute_names = extract_attribute_names_from_match(match) super unless all_attributes_exists?(attribute_names) self.class_eval %{ def self.#{method_id}(*args) options = args.last.is_a?(Hash) ? args.pop : {} attributes = construct_attributes_from_arguments(#{attribute_names.inspect}, args) finder_options = { :conditions => attributes } validate_find_options(options) set_readonly_option!(options) if options[:conditions] with_scope(:find => finder_options) do ActiveSupport::Deprecation.silence { send(:#{finder}, options) } end else ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) } end end }, __FILE__, __LINE__ send(method_id, *arguments) elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s) instantiator = determine_instantiator(match) attribute_names = extract_attribute_names_from_match(match) super unless all_attributes_exists?(attribute_names) self.class_eval %{ def self.#{method_id}(*args) if args[0].is_a?(Hash) attributes = args[0].with_indifferent_access find_attributes = attributes.slice(*#{attribute_names.inspect}) else find_attributes = attributes = construct_attributes_from_arguments(#{attribute_names.inspect}, args) end options = { :conditions => find_attributes } set_readonly_option!(options) record = find_initial(options) if record.nil? record = self.new { |r| r.send(:attributes=, attributes, false) } #{'record.save' if instantiator == :create} record else record end end }, __FILE__, __LINE__ send(method_id, *arguments) else super end end # Intercepts call in ActiveRecord::Base.method_missing, which is ovridden # above. def extract_attribute_names_from_match(match) match.captures.last.split('_and_').map do |name| get_attribute_name(name) end end # This class method does the real work to determine if there is an database column # label with spaces that might match the AR.attribute_method_call by substituting # undercores with spaces. def get_attribute_name(name) if column_names.include?(name) return name elsif column_names.include?(name_spaced = name.gsub(/_/,' ')) return name_spaced elsif match = match_attribute_method?(name) if column_names.include?(name_spaced = match.pre_match.gsub(/_/,' ')) return (name_spaced + match.to_s) end else return name end end end end end