aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/attribute_methods.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model/attribute_methods.rb')
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb100
1 files changed, 54 insertions, 46 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index ef04f1fa49..f336c759d2 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,3 +1,5 @@
+require 'thread_safe'
+require 'mutex_m'
module ActiveModel
# Raised when an attribute is not defined.
@@ -8,22 +10,24 @@ module ActiveModel
#
# user = User.first
# user.pets.select(:id).first.user_id
- # # => ActiveModel::MissingAttributeError: missing attribute: user_id
+ # # => ActiveModel::MissingAttributeError: missing attribute: user_id
class MissingAttributeError < NoMethodError
end
- # == Active Model Attribute Methods
+
+ # == Active \Model Attribute Methods
#
# <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and
- # suffixes to your methods as well as handling the creation of Active Record
- # like class methods such as +table_name+.
+ # suffixes to your methods as well as handling the creation of
+ # <tt>ActiveRecord::Base</tt>-like class methods such as +table_name+.
#
- # The requirements to implement ActiveModel::AttributeMethods are to:
+ # The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
- # * <tt>include ActiveModel::AttributeMethods</tt> in your object.
- # * Call each Attribute Method module method you want to add, such as
- # +attribute_method_suffix+ or +attribute_method_prefix+.
+ # * <tt>include ActiveModel::AttributeMethods</tt> in your class.
+ # * Call each of its method you want to add, such as +attribute_method_suffix+
+ # or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
+ # * Define an +attributes+ method, see below.
#
# A minimal implementation could be:
#
@@ -37,6 +41,10 @@ module ActiveModel
#
# attr_accessor :name
#
+ # def attributes
+ # {'name' => @name}
+ # end
+ #
# private
#
# def attribute_contrived?(attr)
@@ -52,10 +60,10 @@ module ActiveModel
# end
# end
#
- # Note that whenever you include ActiveModel::AttributeMethods in your class,
- # it requires you to implement an +attributes+ method which returns a hash
- # with each attribute name in your model as hash key and the attribute value as
- # hash value.
+ # Note that whenever you include <tt>ActiveModel::AttributeMethods</tt> in
+ # your class, it requires you to implement an +attributes+ method which
+ # returns a hash with each attribute name in your model as hash key and the
+ # attribute value as hash value.
#
# Hash keys must be strings.
module AttributeMethods
@@ -178,7 +186,6 @@ module ActiveModel
undefine_attribute_methods
end
-
# Allows you to make aliases for attributes.
#
# class Person
@@ -202,7 +209,7 @@ module ActiveModel
# person.name # => "Bob"
# person.nickname # => "Bob"
# person.name_short? # => true
- # person.nickname_short? # => true
+ # person.nickname_short? # => true
def alias_attribute(new_name, old_name)
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
attribute_method_matchers.each do |matcher|
@@ -212,6 +219,16 @@ module ActiveModel
end
end
+ # Is +new_name+ an alias?
+ def attribute_alias?(new_name)
+ attribute_aliases.key? new_name.to_s
+ end
+
+ # Returns the original name for the alias +name+
+ def attribute_alias(name)
+ attribute_aliases[name.to_s]
+ end
+
# Declares the attributes that should be prefixed and suffixed by
# ActiveModel::AttributeMethods.
#
@@ -316,9 +333,10 @@ module ActiveModel
attribute_method_matchers_cache.clear
end
- # Returns true if the attribute methods defined have been generated.
def generated_attribute_methods #:nodoc:
- @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
+ @generated_attribute_methods ||= Module.new {
+ extend Mutex_m
+ }.tap { |mod| include mod }
end
protected
@@ -337,17 +355,17 @@ module ActiveModel
# significantly (in our case our test suite finishes 10% faster with
# this cache).
def attribute_method_matchers_cache #:nodoc:
- @attribute_method_matchers_cache ||= {}
+ @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
end
def attribute_method_matcher(method_name) #:nodoc:
- attribute_method_matchers_cache.fetch(method_name) do |name|
+ attribute_method_matchers_cache.compute_if_absent(method_name) do
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
match = nil
- matchers.detect { |method| match = method.match(name) }
- attribute_method_matchers_cache[name] = match
+ matchers.detect { |method| match = method.match(method_name) }
+ match
end
end
@@ -382,15 +400,6 @@ module ActiveModel
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
def initialize(options = {})
- if options[:prefix] == '' || options[:suffix] == ''
- ActiveSupport::Deprecation.warn(
- "Specifying an empty prefix/suffix for an attribute method is no longer " \
- "necessary. If the un-prefixed/suffixed version of the method has not been " \
- "defined when `define_attribute_methods` is called, it will be defined " \
- "automatically."
- )
- end
-
@prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@@ -413,17 +422,16 @@ module ActiveModel
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.
+ # Allows access to the object attributes, which are held in the hash
+ # returned by <tt>attributes</tt>, as though they were first-class
+ # methods. So a +Person+ class with a +name+ attribute can for example use
+ # <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
+ # the attributes hash -- except for multiple assigns with
+ # <tt>ActiveRecord::Base#attributes=</tt>.
#
- # 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.
+ # It's also possible to instantiate related objects, so a <tt>Client</tt>
+ # class belonging to the +clients+ table with a +master_id+ foreign key
+ # can instantiate master through <tt>Client#master</tt>.
def method_missing(method, *args, &block)
if respond_to_without_attributes?(method, true)
super
@@ -433,17 +441,17 @@ module ActiveModel
end
end
- # attribute_missing is like method_missing, but for attributes. When method_missing is
- # called we check to see if there is a matching attribute method. If so, we call
- # attribute_missing to dispatch the attribute. This method can be overloaded to
- # customise the behaviour.
+ # +attribute_missing+ is like +method_missing+, but for attributes. When
+ # +method_missing+ is called we check to see if there is a matching
+ # attribute method. If so, we tell +attribute_missing+ to dispatch the
+ # attribute. This method can be overloaded to customize the behavior.
def attribute_missing(match, *args, &block)
__send__(match.target, match.attr_name, *args, &block)
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+.
+ # A +Person+ instance 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)
if super