diff options
Diffstat (limited to 'activerecord/lib/active_record/aggregations.rb')
-rw-r--r-- | activerecord/lib/active_record/aggregations.rb | 57 |
1 files changed, 35 insertions, 22 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index e576ec4d40..be88c7c9e8 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -1,14 +1,31 @@ module ActiveRecord - # = Active Record Aggregations - module Aggregations # :nodoc: + # See ActiveRecord::Aggregations::ClassMethods for documentation + module Aggregations extend ActiveSupport::Concern - def clear_aggregation_cache #:nodoc: - @aggregation_cache.clear if persisted? + def initialize_dup(*) # :nodoc: + @aggregation_cache = {} + super end - # Active Record implements aggregation through a macro-like class method called +composed_of+ - # for representing attributes as value objects. It expresses relationships like "Account [is] + def reload(*) # :nodoc: + clear_aggregation_cache + super + end + + private + + def clear_aggregation_cache # :nodoc: + @aggregation_cache.clear if persisted? + end + + def init_internals # :nodoc: + @aggregation_cache = {} + super + end + + # Active Record implements aggregation through a macro-like class method called #composed_of + # for representing attributes as value objects. It expresses relationships like "Account [is] # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call # to the macro adds a description of how the value objects are created from the attributes of # the entity object (when the entity is initialized either as a new object or from finding an @@ -87,11 +104,6 @@ module ActiveRecord # customer.address_city = "Copenhagen" # customer.address # => Address.new("Hyancintvej", "Copenhagen") # - # customer.address_street = "Vesterbrogade" - # customer.address # => Address.new("Hyancintvej", "Copenhagen") - # customer.clear_aggregation_cache - # customer.address # => Address.new("Vesterbrogade", "Copenhagen") - # # customer.address = Address.new("May Street", "Chicago") # customer.address_street # => "May Street" # customer.address_city # => "Chicago" @@ -108,12 +120,12 @@ module ActiveRecord # # It's also important to treat the value objects as immutable. Don't allow the Money object to have # its amount changed after creation. Create a new Money object with the new value instead. The - # Money#exchange_to method is an example of this. It returns a new value object instead of changing + # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing # its own values. Active Record won't persist value objects that have been changed through means # other than the writer method. # # The immutable requirement is enforced by Active Record by freezing any object assigned as a value - # object. Attempting to change it afterwards will result in a RuntimeError. + # object. Attempting to change it afterwards will result in a +RuntimeError+. # # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable @@ -122,17 +134,17 @@ module ActiveRecord # # By default value objects are initialized by calling the <tt>new</tt> constructor of the value # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt> - # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows + # option, as arguments. If the value class doesn't support this convention then #composed_of allows # a custom constructor to be specified. # # When a new value is assigned to the value object, the default assumption is that the new value # is an instance of the value class. Specifying a custom converter allows the new value to be automatically # converted to an instance of value class if necessary. # - # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be - # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html). + # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be + # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. - # New values can be assigned to the value object using either another NetAddr::CIDR object, a string + # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet # these requirements: # @@ -161,7 +173,7 @@ module ActiveRecord # # == Finding records by a value object # - # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database + # Once a #composed_of relationship is specified for a model, records can be loaded from the database # by specifying an instance of the value object in the conditions hash. The following example # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": # @@ -174,7 +186,7 @@ module ActiveRecord # Options are: # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked - # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it + # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it # with this option. # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value # object. Each mapping is represented as an array where the first item is the name of the @@ -230,8 +242,8 @@ module ActiveRecord private def reader_method(name, class_name, mapping, allow_nil, constructor) define_method(name) do - if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? }) - attrs = mapping.collect {|key, _| read_attribute(key)} + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? }) + attrs = mapping.collect {|key, _| _read_attribute(key)} object = constructor.respond_to?(:call) ? constructor.call(*attrs) : class_name.constantize.send(constructor, *attrs) @@ -245,7 +257,8 @@ module ActiveRecord define_method("#{name}=") do |part| klass = class_name.constantize if part.is_a?(Hash) - part = klass.new(*part.values) + raise ArgumentError unless part.size == part.keys.max + part = klass.new(*part.sort.map(&:last)) end unless part.is_a?(klass) || converter.nil? || part.nil? |