aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_methods.rb
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2009-08-04 23:36:05 -0500
committerJoshua Peek <josh@joshpeek.com>2009-08-04 23:36:05 -0500
commitf8d3c72c39ad209abca7f3613f91fb3a03805261 (patch)
tree2d6f5b67e3212b59be1f00391eeb64ebf5b40548 /activerecord/lib/active_record/attribute_methods.rb
parent64eecdd131c93d7d6c8ef9c6a7ae6b9d76c72a8b (diff)
downloadrails-f8d3c72c39ad209abca7f3613f91fb3a03805261.tar.gz
rails-f8d3c72c39ad209abca7f3613f91fb3a03805261.tar.bz2
rails-f8d3c72c39ad209abca7f3613f91fb3a03805261.zip
Extract generic attribute method generation to AMo
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods.rb')
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb251
1 files changed, 10 insertions, 241 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index be275f5cb6..ab7ad34b9e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -3,191 +3,13 @@ require 'active_support/core_ext/enumerable'
module ActiveRecord
module AttributeMethods #:nodoc:
extend ActiveSupport::Concern
+ include ActiveModel::AttributeMethods
- class AttributeMethodMatcher
- attr_reader :prefix, :suffix
-
- AttributeMethodMatch = Struct.new(:prefix, :base, :suffix)
-
- def initialize(options = {})
- options.symbolize_keys!
- @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
- @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
- end
-
- def match(method_name)
- if matchdata = @regex.match(method_name)
- AttributeMethodMatch.new(matchdata[1], matchdata[2], matchdata[3])
- else
- nil
- end
- end
- end
-
- # Declare and check for suffixed attribute methods.
module ClassMethods
- # Declares a method available for all attributes with the given prefix.
- # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
- #
- # #{prefix}#{attr}(*args, &block)
- #
- # to
- #
- # #{prefix}attribute(#{attr}, *args, &block)
- #
- # An <tt>#{prefix}attribute</tt> instance method must exist and accept at least
- # the +attr+ argument.
- #
- # For example:
- #
- # class Person < ActiveRecord::Base
- # attribute_method_prefix 'clear_'
- #
- # private
- # def clear_attribute(attr)
- # ...
- # end
- # end
- #
- # person = Person.find(1)
- # person.name # => 'Gem'
- # person.clear_name
- # person.name # => ''
- def attribute_method_prefix(*prefixes)
- attribute_method_matchers.concat(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.
- #
- # #{attr}#{suffix}(*args, &block)
- #
- # to
- #
- # attribute#{suffix}(#{attr}, *args, &block)
- #
- # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
- # the +attr+ argument.
- #
- # For example:
- #
- # class Person < ActiveRecord::Base
- # attribute_method_suffix '_short?'
- #
- # private
- # def attribute_short?(attr)
- # ...
- # end
- # end
- #
- # person = Person.find(1)
- # person.name # => 'Gem'
- # person.name_short? # => true
- def attribute_method_suffix(*suffixes)
- attribute_method_matchers.concat(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
- # the method.
- #
- # #{prefix}#{attr}#{suffix}(*args, &block)
- #
- # to
- #
- # #{prefix}attribute#{suffix}(#{attr}, *args, &block)
- #
- # An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
- # accept at least the +attr+ argument.
- #
- # For example:
- #
- # class Person < ActiveRecord::Base
- # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
- #
- # private
- # def reset_attribute_to_default!(attr)
- # ...
- # end
- # end
- #
- # person = Person.find(1)
- # 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] })
- undefine_attribute_methods
- end
-
- def matching_attribute_methods(method_name)
- attribute_method_matchers.collect { |method| method.match(method_name) }.compact
- end
-
- # 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 method.
- #
- # The original method will be aliased, with the new name being
- # prefixed with "original_". This allows the new method to
- # access the original value.
- #
- # Example:
- #
- # class A < ActiveRecord::Base
- # define_attr_method :primary_key, "sysid"
- # define_attr_method( :inheritance_column ) do
- # original_inheritance_column + "_id"
- # end
- # end
- def define_attr_method(name, value=nil, &block)
- sing = metaclass
- sing.send :alias_method, "original_#{name}", name
- if block_given?
- sing.send :define_method, name, &block
- else
- # use eval instead of a block to work around a memory leak in dev
- # mode in fcgi
- sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
- end
- end
-
- def generated_methods #:nodoc:
- @generated_methods ||= begin
- mod = Module.new
- include mod
- mod
- end
- end
-
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods
- return unless generated_methods.instance_methods.empty?
- columns_hash.keys.each do |name|
- attribute_method_matchers.each do |method|
- method_name = "#{method.prefix}#{name}#{method.suffix}"
- unless instance_method_already_implemented?(method_name)
- generate_method = "define_method_#{method.prefix}attribute#{method.suffix}"
-
- if respond_to?(generate_method)
- send(generate_method, name)
- else
- generated_methods.module_eval("def #{method_name}(*args); send(:#{method.prefix}attribute#{method.suffix}, '#{name}', *args); end", __FILE__, __LINE__)
- end
- end
- end
- end
- end
-
- def undefine_attribute_methods
- generated_methods.module_eval do
- instance_methods.each { |m| undef_method(m) }
- end
+ super(columns_hash.keys)
end
# Checks whether the method is defined in the model or any of its subclasses
@@ -200,83 +22,30 @@ module ActiveRecord
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
@_defined_class_methods.include?(method_name)
end
-
- private
- # Default to *=, *? and *_before_type_cast
- def attribute_method_matchers
- @@attribute_method_matchers ||= []
- end
- end
-
- # 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.matching_attribute_methods(method_name).find do |match|
- match.base == 'id' || @attributes.include?(match.base)
- 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= and never directly use the attributes hash -- except for multiple assigns with
- # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
- # the completed attribute is not +nil+ or 0.
- #
- # It's also possible to instantiate related objects, so a Client class belonging to the clients
- # table with a +master_id+ foreign key can instantiate master through Client#master.
def method_missing(method_id, *args, &block)
- method_name = method_id.to_s
-
# If we haven't generated any methods yet, generate them, then
# see if we've created the method we're looking for.
- if self.class.generated_methods.instance_methods.empty?
+ if !self.class.attribute_methods_generated?
self.class.define_attribute_methods
+ method_name = method_id.to_s
guard_private_attribute_method!(method_name, args)
- if self.class.generated_methods.instance_methods.include?(method_name)
+ if self.class.generated_attribute_methods.instance_methods.include?(method_name)
return self.send(method_id, *args, &block)
end
end
-
- if match = match_attribute_method?(method_name)
- guard_private_attribute_method!(method_name, args)
- return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block)
- end
super
end
- # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
- # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # which will all return +true+.
- alias :respond_to_without_attributes? :respond_to?
- def respond_to?(method, include_private_methods = false)
- method_name = method.to_s
- if super
- return true
- elsif !include_private_methods && super(method, true)
- # If we're here then we haven't found among non-private methods
- # but found among all methods. Which means that given method is private.
- return false
- elsif self.class.generated_methods.instance_methods.empty?
- self.class.define_attribute_methods
- if self.class.generated_methods.instance_methods.include?(method_name)
- return true
- end
- elsif match_attribute_method?(method_name)
- return true
- end
+ def respond_to?(*args)
+ self.class.define_attribute_methods
super
end
- private
- # 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)
- end
- end
-
- def missing_attribute(attr_name, stack)
- raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
+ protected
+ def attribute_method?(attr_name)
+ attr_name == 'id' || attributes.include?(attr_name)
end
end
end