lib/active_model/attribute_methods.rb in activemodel-3.0.20 vs lib/active_model/attribute_methods.rb in activemodel-3.1.0.beta1

- old
+ new

@@ -1,7 +1,7 @@ require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/class/attribute' module ActiveModel class MissingAttributeError < NoMethodError end # == Active Model Attribute Methods @@ -54,12 +54,17 @@ # Hash keys must be strings. # module AttributeMethods extend ActiveSupport::Concern - COMPILABLE_REGEXP = /^[a-zA-Z_]\w*[!?=]?$/ + COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/ + included do + class_attribute :attribute_method_matchers, :instance_writer => false + self.attribute_method_matchers = [] + end + module ClassMethods # Defines an "attribute" method (like +inheritance_column+ or +table_name+). # A new (class) method will be created with the given name. If a value is # specified, the new method will return that value (as a string). # Otherwise, the given block will be used to compute the value of the @@ -93,11 +98,11 @@ # AttributePerson.inheritance_column # # => 'address_id' def define_attr_method(name, value=nil, &block) sing = singleton_class sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 - if method_defined?(:'original_#{name}') + if method_defined?('original_#{name}') undef :'original_#{name}' end alias_method :'original_#{name}', :'#{name}' eorb if block_given? @@ -149,11 +154,11 @@ # person.name = "Bob" # person.name # => "Bob" # person.clear_name # person.name # => nil def attribute_method_prefix(*prefixes) - attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }) + self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix } undefine_attribute_methods end # Declares a method available for all attributes with the given suffix. # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method. @@ -186,11 +191,11 @@ # person = Person.new # person.name = "Bob" # person.name # => "Bob" # person.name_short? # => true def attribute_method_suffix(*suffixes) - attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }) + self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix } undefine_attribute_methods end # Declares a method available for all attributes with the given prefix # and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite @@ -224,11 +229,11 @@ # person = Person.new # person.name # => 'Gem' # person.reset_name_to_default! # person.name # => 'Gemma' def attribute_method_affix(*affixes) - attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }) + self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] } undefine_attribute_methods end def alias_attribute(new_name, old_name) attribute_method_matchers.each do |matcher| @@ -247,11 +252,11 @@ end end end end - # Declares a the attributes that should be prefixed and suffixed by + # Declares the attributes that should be prefixed and suffixed by # ActiveModel::AttributeMethods. # # To use, pass in an array of attribute names (as strings or symbols), # be sure to declare +define_attribute_methods+ after you define any # prefix, suffix or affix methods, or they will not hook in. @@ -272,53 +277,52 @@ # def clear_attribute(attr) # ... # end # end def define_attribute_methods(attr_names) - return if attribute_methods_generated? - attr_names.each do |attr_name| - attribute_method_matchers.each do |matcher| - unless instance_method_already_implemented?(matcher.method_name(attr_name)) - generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}" + attr_names.each { |attr_name| define_attribute_method(attr_name) } + end - if respond_to?(generate_method) - send(generate_method, attr_name) - else - method_name = matcher.method_name(attr_name) + def define_attribute_method(attr_name) + attribute_method_matchers.each do |matcher| + unless instance_method_already_implemented?(matcher.method_name(attr_name)) + generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}" + if respond_to?(generate_method) + send(generate_method, attr_name) + else + method_name = matcher.method_name(attr_name) + + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + if method_defined?('#{method_name}') + undef :'#{method_name}' + end + RUBY + + if method_name.to_s =~ COMPILABLE_REGEXP generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 - if method_defined?('#{method_name}') - undef :'#{method_name}' + def #{method_name}(*args) + send(:#{matcher.method_missing_target}, '#{attr_name}', *args) end RUBY - - if method_name.to_s =~ COMPILABLE_REGEXP - generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{method_name}(*args) - send(:#{matcher.method_missing_target}, '#{attr_name}', *args) - end - RUBY - else - generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 - define_method('#{method_name}') do |*args| - send('#{matcher.method_missing_target}', '#{attr_name}', *args) - end - RUBY - end + else + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + define_method('#{method_name}') do |*args| + send('#{matcher.method_missing_target}', '#{attr_name}', *args) + end + RUBY end end end end - @attribute_methods_generated = true end # Removes all the previously dynamically defined methods from the class def undefine_attribute_methods generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } end - @attribute_methods_generated = nil end # Returns true if the attribute methods defined have been generated. def generated_attribute_methods #:nodoc: @generated_attribute_methods ||= begin @@ -326,52 +330,41 @@ include mod mod end end - # Returns true if the attribute methods defined have been generated. - def attribute_methods_generated? - @attribute_methods_generated ||= nil - end - protected def instance_method_already_implemented?(method_name) method_defined?(method_name) end private class AttributeMethodMatcher - attr_reader :prefix, :suffix + attr_reader :prefix, :suffix, :method_missing_target AttributeMethodMatch = Struct.new(:target, :attr_name) def initialize(options = {}) options.symbolize_keys! @prefix, @suffix = options[:prefix] || '', options[:suffix] || '' @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/ + @method_missing_target = "#{@prefix}attribute#{@suffix}" + @method_name = "#{prefix}%s#{suffix}" end def match(method_name) - if matchdata = @regex.match(method_name) - AttributeMethodMatch.new(method_missing_target, matchdata[2]) + if @regex =~ method_name + AttributeMethodMatch.new(method_missing_target, $2) else nil end end def method_name(attr_name) - "#{prefix}#{attr_name}#{suffix}" + @method_name % attr_name end - - def method_missing_target - :"#{prefix}attribute#{suffix}" - end end - - def attribute_method_matchers #:nodoc: - read_inheritable_attribute(:attribute_method_matchers) || write_inheritable_attribute(:attribute_method_matchers, []) - end end # Allows access to the object attributes, which are held in the # <tt>@attributes</tt> hash, as though they were first-class methods. So a # Person class with a name attribute can use Person#name and Person#name= @@ -416,21 +409,21 @@ private # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. def match_attribute_method?(method_name) - self.class.send(:attribute_method_matchers).each do |method| + self.class.attribute_method_matchers.each do |method| if (match = method.match(method_name)) && attribute_method?(match.attr_name) return match end end nil end # prevent method_missing from calling private methods with #send def guard_private_attribute_method!(method_name, args) if self.class.private_method_defined?(method_name) - raise NoMethodError.new("Attempt to call private method", method_name, args) + raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args) end end def missing_attribute(attr_name, stack) raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack