Skip to content
This repository

Removing composed_of #6743

Merged
merged 1 commit into from almost 2 years ago
Steve Klabnik
Collaborator

composed_of is a complicated feature that's rarely used. So let's deprecate it in 3-2 and remove it in master.

Related: #6742

/cc @jonleighton @tenderlove

José Valim
Owner
Jon Leighton
Owner

@josevalim we were thinking that it might be okay to simply remove as it's a seldom used feature and this is a major release. also I think moving to a plugin might not be straightforward because there is interaction between aggregates and various other parts of rails, e.g. finders:

Fare.find(price: Money.new(5))

How strongly do you feel that it should be extracted?

Jon Leighton
Owner

@josevalim also note that we were thinking to add deprecations to 3.2

Jon Leighton
Owner

an alternative would be to deprecate in 4.0 and remove in 4.1. extracting a plugin seems hard though.

Steve Klabnik
Collaborator

As a note, this is a pretty naive removal, so please, give it a good code review. I'd like to give it a much better commit message before it gets merged too.

I've updated it to remove the AggregateReflection too. I'm pretty sure that this means that the AssociationReflection stuff and MacroReflection stuff can be simplified.

José Valim
Owner
Iain Hecker
iain commented June 15, 2012

I use this feature in some projects. I would have no problem with rolling my own implementation, like this:

class Fare
  include ActiveRecord::Model
  def price
    Money.new(super)
  end
end

About the finder case: maybe add a convention for usage in finders? Like "if Arel doesn't know the object, it will try to call the to_query method on it".

Jon Leighton
Owner

I don't really think we need to find some alternative implementation for the finder. If they want people can do e.g.

class Fare
  def self.costing(money)
    where(price: money.value)
  end
end
Tom Stuart

I use composed_of often — because it's there! — but I agree it has a complex implementation with too much generality and an API which is hard to understand. In any specific case it's usually easy to roll your own specific implementation (per @iain's comment) which would almost always produce clearer code than the "magic" composed_of equivalent.

I believe it would send a useful message to simply remove it rather than extracting it. Namely: this particular abstraction rarely makes things simpler, and you are better off writing plain old Ruby code (which you can understand and maintain) to achieve what you want.

José Valim
Owner

I like @iain proposal to duck type and call to to_rel (to_arel, to_sql or whatever) in the objects given to where.

@tomstuart agreed. we just need to make the process clear. maybe write a section in the updating guide or release notes that people can refer to.

Tom Stuart

@josevalim I think a small dose of before-and-after example code can go a long way. For example, :class_name, :constructor and :mapping are just a contrived and indirect way of arranging a particular call to a constructor method, so why not show how to write the explicit getter that makes that call?

Oriol Gual

I'm also OK with removing it. We've used it some times and the result normally it's enough complex to move it to another class/object.

José Valim
Owner

@tomstuart exactly.

Egor Homakov

composed_of is a complicated feature that's rarely used

@steveklabnik it is true, I barely seen it used in projects. But seems composed_of is awesome, very nice sugar.
Thank you so much for opening the issue I will use it since now!

p.s. sure, I'm against of removing it. It is rarely used but should be popular

Iain Hecker
iain commented June 15, 2012

@homakov I agree that extracting stuff into composed value objects should be popular. However, passing procs in hashes is a pain, and is for me a big reason not to use it. At least not how it's currently implemented.

Jon Leighton
Owner

Note also that for single-attribute composed_of usage (e.g. Money), this can be achieved via a customer serializer:

class MoneySerializer
  def dump(money)
    money.value
  end

  def load(value)
    Money.new(value)
  end
end

class Fare
  serialize :price, MoneySerializer.new
end
Egor Homakov

@iain hmm it is really dirty. composed_of can be easily replaced with:

def full_name
  "#{first_name} #{second_name}"
end

also for 1 attribute @jonleighton 's example is not bad. the only profit of composed_of for me - readers. writers(or 'load') are not so useful.

def price
  Money.new read_attribute(:price)
end

is nice too
seems it can be painlessly removed...

Iain Hecker
iain commented June 15, 2012

There is a problem with a to_arel or to_sql duck typing method. This is when the object wraps multiple fields.

class Fare
  def price
    Money.new(price_value, price_unit)
  end
end

Now what would need to happen if we wanted to call:

Fare.where(price: Money.new(4, "USD"))

Somehow, the key would need to change too. It would need to be converted to:

Fare.where(price_value: 4, price_unit: "USD")

This complexity is a good argument for the solution @jonleighton suggested a couple of comments back. Just create the finder yourself. Another pro of that solution is that the database logic remains inside the AR class.

If you do decide to implement this, then you could also do this for existing objects, like relations.

Now you would have to do:

Employee.where(manager_id: Manager.find(params[:manager_id]))

But when you allow changing the key as well, you could write:

Employee.where(manager: Manager.find(params[:manager_id]))

Still, this sounds complex and magical to me.

José Valim
Owner
Carlos Antonio da Silva

Although I've already used composed_of in some projects, it's just because it was there. I'm ok with the removal :+1:.

@steveklabnik awesome, thanks for your work on that :)

@iain agreed, seems better to go with @jonleighton's suggestion, create the finder yourself in such cases.

Just want to point that this conversion should happen with the current sanitization code - from the docs:

# Given:
#     class Person < ActiveRecord::Base
#       composed_of :address, :class_name => "Address",
#         :mapping => [%w(address_street street), %w(address_city city)]
#     end
# Then:
#     { :address => Address.new("813 abc st.", "chicago") }
#       # => { :address_street => "813 abc st.", :address_city => "chicago" }
Steve Klabnik
Collaborator

Removed some extra stuff from some extra places.

expand_hash_conditions_for_aggregates seems like it should be removed, but it's used in sanitize_sql_hash_for_conditions, and I'm not sure of the best way to remove it.

Steve Klabnik
Collaborator

Okay, I think I've got it now. That should be removed properly.

Carlos Antonio da Silva carlosantoniodasilva commented on the diff June 15, 2012
activerecord/lib/active_record/sanitization.rb
... ...
@@ -88,8 +88,6 @@ def expand_hash_conditions_for_aggregates(attrs)
88 88
       #   { :address => Address.new("123 abc st.", "chicago") }
89 89
       #     # => "address_street='123 abc st.' and address_city='chicago'"
90 90
       def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
91  
-        attrs = expand_hash_conditions_for_aggregates(attrs)
92  
-
2
Carlos Antonio da Silva Owner

I think you can remove the expand_hash_conditions_for_aggregates method now.

Steve Klabnik Collaborator

Ah! Yes. An awkward git checkout. Pushing that now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Steve Klabnik
Collaborator

Some tests used Customer stuff. So I killed all the tests relating to Aggregates, and left in a Customer without aggregate stuff and killed some classes that just had aggregate stuff in it.

Everton Cardoso
verto commented June 15, 2012

I think that plain objects can be an alternative to define complex domain model without exists an AR and composed_of method works for it.

I really love composed_of because i've working with hibernate components for my java projects. what is alternative to use something like this: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/components.html ?

Steve Klabnik
Collaborator

Rebaed so that it merges cleanly again.

Patrick Negri
pnegri commented June 16, 2012

I still use this feature a lot in multi country ecommerce carts where we use composed_of to handle custom currencies of items. Its a complicated feature, but its a very good one when you learn how to use it.

Lar Van Der Jagt

I use this feature quite a bit. I agree that it's under documented and overly complex, but would rather it remain in as is than rip it out without establishing some alternate conventions for composition within ActiveModel.

Patrick Negri
pnegri commented June 16, 2012

Also, i dont think these users at #1436, #2084 and #3807 are seeing this as a solution for their issues. Removing a thing doesnt fix bugs.

Rafael Mendonça França
Owner

@pnegri you don't need to use it. You can use plain ruby methods to archive the same behavior. composed_of is only a poor wrapper to give you:

def price
  @price ||= Money.new(value, currency)
end

def price=(price)
  @price = price

  self[:value] = price.value
  self[:currency] = price.currency
end

One of the solutions for these issues is stop to use composed_of.

Right now this feature is not often used. We have to take into account the trade-off between value and maintainability cost, and the value of this feature is dubious since you can reproduce it easily.

Franck Verrot

@pnegri @rafaelfranca The "plain ruby method" way of achieving a replacement for composed_of is still
verbose and thus error-prone.

IMHO it's just a matter of whether we want to make Value Objects first-class citizens in Rails or not, and if
it means removing composed_of I'd love to see this feature either maintained as a plugin as per @josevalim comment or improved (with a "multiple-field" serializer maybe?)

Rafael Mendonça França
Owner

@cesario yes, I agree that is verbose and error-prone, but It is easier to understand and customize, without use options like :converter and :constructor that you never understand what they means, or how to use.

And, yes, I agree that is a metter of we want to make Value Object's first-class in the Rails, and given the actual user base of this feature, I would say that it is not adding any real value to the framework.

I'll really appreciate if someone could extract this to a plugin, but I don't think that the Rails team should maintain that plugin.

@steveklabnik could you move this forward improving the commit message? You could add some examples how the users can archive this behavior without use the composed_of method.

Steve Klabnik
Collaborator

@rafaelfranca Give me a few hours, I'll get that updated.

Steve Klabnik
Collaborator

@rafaelfranca updated

Steve Klabnik Removing composed_of from ActiveRecord.
This feature adds a lot of complication to ActiveRecord for dubious
value. Let's talk about what it does currently:

class Customer < ActiveRecord::Base
  composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
end

Instead, you can do something like this:

    def balance
      @balance ||= Money.new(value, currency)
    end

    def balance=(balance)
      self[:value] = balance.value
      self[:currency] = balance.currency
      @balance = balance
    end

Since that's fairly easy code to write, and doesn't need anything
extra from the framework, if you use composed_of today, you'll
have to add accessors/mutators like that.

Closes #1436
Closes #2084
Closes #3807
14fc8b3
Rafael Mendonça França rafaelfranca merged commit 14fc8b3 into from June 18, 2012
Rafael Mendonça França rafaelfranca closed this June 18, 2012
Wojtek Kruszewski

with 9 additions and 768 deletions. -- thanks man. Keeping a framework from getting bloated is really hard work.

Hakan Ensari hakanensari referenced this pull request from a commit in hakanensari/money June 24, 2012
Hakan Ensari Remove composed_of example in README a66ddfa
Hakan Ensari hakanensari referenced this pull request in RubyMoney/money June 24, 2012
Merged

Remove composed_of example in README #179

Trung Lê

@steveklabnik it is about time to remove it in master

Saurabh Nanda

I just discovered composed_of in the Rails docs to subsequently stumble upon this issue. I'm not sure whether this is a deprecated feature or will composed_of live on. Any help?

Rafael Mendonça França
Owner

In my opinion we should remove it. But right now it will live on.

Steve Klabnik
Collaborator

yup, we want to get rid of it, but not yet.

Ivgeni

Seems like this is a feature that does not lend itself to many use cases, but for the cases that it's meant for, it's a perfect fit. I do not believe that its prudent to remove an entire interface (which can always be improved, at least in theory) just because the benefits are not immediately obvious. As far as general purpose frameworks go, the competition is in the offered tools. This is a tool. Its only crime here is that the current implementation is still lacking according to this thread.

Furthermore, value objects are an accepted, well defined, concept in of itself. It seems a bit odd to support the entity but not the value as a concept.

Filip Kostovski

Let's remove everything that is not popular in rails.

Ivgeni

Popular for whom?

Filip Kostovski

Honestly removing a feature from rails just because it is not popular it is a bad thing to do. I found about this feature today and i was really happy till a colleague of mine told me that they plan to deprecate this feature. A feature that allows an object to act as a decorator for a property and offers a feature that cant be replaced by anything in rails atm. If you want to remove something well offer a better alternative dont just cripple the framework cause that feature is not popular enough.

Jeremy Kemper
Owner

No worries: composed_of is sticking around. It nails certain cases like money and time zones perfectly.

If we're saying this is deprecated anywhere, please point it out and we'll correct it.

Filip Kostovski

Thanks a lot guys you just made my day. @pftg thanks for the tip will check it out.

Rafael Mendonça França
Owner

:+1: It will be sticking around. (and I think I updated the blog post to point this)

Ivgeni

Good to hear. What I think this discussion brings to light is that it's completely unclear what the actual API should be for the feature. Just about every comment seems to boil down to the fact that it's clunky in it's current state. So then the question is really, what's the expectation for this feature? How far can the integration go? How far should configuration go? What should it all look like? What kind of use cases are encountered regularly (beyond the famous example for money)? Essentially, what are we really missing if we assume this will stay?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jun 18, 2012
Steve Klabnik Removing composed_of from ActiveRecord.
This feature adds a lot of complication to ActiveRecord for dubious
value. Let's talk about what it does currently:

class Customer < ActiveRecord::Base
  composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
end

Instead, you can do something like this:

    def balance
      @balance ||= Money.new(value, currency)
    end

    def balance=(balance)
      self[:value] = balance.value
      self[:currency] = balance.currency
      @balance = balance
    end

Since that's fairly easy code to write, and doesn't need anything
extra from the framework, if you use composed_of today, you'll
have to add accessors/mutators like that.

Closes #1436
Closes #2084
Closes #3807
14fc8b3
This page is out of date. Refresh to see the latest.
12  activerecord/README.rdoc
Source Rendered
@@ -46,18 +46,6 @@ A short rundown of some of the major features:
46 46
   {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
47 47
 
48 48
 
49  
-* Aggregations of value objects.
50  
-
51  
-   class Account < ActiveRecord::Base
52  
-     composed_of :balance, :class_name => "Money",
53  
-                 :mapping => %w(balance amount)
54  
-     composed_of :address,
55  
-                 :mapping => [%w(address_street street), %w(address_city city)]
56  
-   end
57  
-
58  
-  {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
59  
-
60  
-
61 49
 * Validation rules that can differ for new or existing objects.
62 50
 
63 51
     class Account < ActiveRecord::Base
1  activerecord/lib/active_record.rb
@@ -36,7 +36,6 @@ module ActiveRecord
36 36
     autoload :ConnectionNotEstablished, 'active_record/errors'
37 37
     autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
38 38
 
39  
-    autoload :Aggregations
40 39
     autoload :Associations
41 40
     autoload :AttributeMethods
42 41
     autoload :AttributeAssignment
261  activerecord/lib/active_record/aggregations.rb
... ...
@@ -1,261 +0,0 @@
1  
-module ActiveRecord
2  
-  # = Active Record Aggregations
3  
-  module Aggregations # :nodoc:
4  
-    extend ActiveSupport::Concern
5  
-
6  
-    def clear_aggregation_cache #:nodoc:
7  
-      @aggregation_cache.clear if persisted?
8  
-    end
9  
-
10  
-    # Active Record implements aggregation through a macro-like class method called +composed_of+
11  
-    # for representing attributes  as value objects. It expresses relationships like "Account [is]
12  
-    # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
13  
-    # to the macro adds a description of how the value objects are created from the attributes of
14  
-    # the entity object (when the entity is initialized either as a new object or from finding an
15  
-    # existing object) and how it can be turned back into attributes (when the entity is saved to
16  
-    # the database).
17  
-    #
18  
-    #   class Customer < ActiveRecord::Base
19  
-    #     composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
20  
-    #     composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
21  
-    #   end
22  
-    #
23  
-    # The customer class now has the following methods to manipulate the value objects:
24  
-    # * <tt>Customer#balance, Customer#balance=(money)</tt>
25  
-    # * <tt>Customer#address, Customer#address=(address)</tt>
26  
-    #
27  
-    # These methods will operate with value objects like the ones described below:
28  
-    #
29  
-    #  class Money
30  
-    #    include Comparable
31  
-    #    attr_reader :amount, :currency
32  
-    #    EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
33  
-    #
34  
-    #    def initialize(amount, currency = "USD")
35  
-    #      @amount, @currency = amount, currency
36  
-    #    end
37  
-    #
38  
-    #    def exchange_to(other_currency)
39  
-    #      exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
40  
-    #      Money.new(exchanged_amount, other_currency)
41  
-    #    end
42  
-    #
43  
-    #    def ==(other_money)
44  
-    #      amount == other_money.amount && currency == other_money.currency
45  
-    #    end
46  
-    #
47  
-    #    def <=>(other_money)
48  
-    #      if currency == other_money.currency
49  
-    #        amount <=> other_money.amount
50  
-    #      else
51  
-    #        amount <=> other_money.exchange_to(currency).amount
52  
-    #      end
53  
-    #    end
54  
-    #  end
55  
-    #
56  
-    #  class Address
57  
-    #    attr_reader :street, :city
58  
-    #    def initialize(street, city)
59  
-    #      @street, @city = street, city
60  
-    #    end
61  
-    #
62  
-    #    def close_to?(other_address)
63  
-    #      city == other_address.city
64  
-    #    end
65  
-    #
66  
-    #    def ==(other_address)
67  
-    #      city == other_address.city && street == other_address.street
68  
-    #    end
69  
-    #  end
70  
-    #
71  
-    # Now it's possible to access attributes from the database through the value objects instead. If
72  
-    # you choose to name the composition the same as the attribute's name, it will be the only way to
73  
-    # access that attribute. That's the case with our +balance+ attribute. You interact with the value
74  
-    # objects just like you would with any other attribute:
75  
-    #
76  
-    #   customer.balance = Money.new(20)     # sets the Money value object and the attribute
77  
-    #   customer.balance                     # => Money value object
78  
-    #   customer.balance.exchange_to("DKK")  # => Money.new(120, "DKK")
79  
-    #   customer.balance > Money.new(10)     # => true
80  
-    #   customer.balance == Money.new(20)    # => true
81  
-    #   customer.balance < Money.new(5)      # => false
82  
-    #
83  
-    # Value objects can also be composed of multiple attributes, such as the case of Address. The order
84  
-    # of the mappings will determine the order of the parameters.
85  
-    #
86  
-    #   customer.address_street = "Hyancintvej"
87  
-    #   customer.address_city   = "Copenhagen"
88  
-    #   customer.address        # => Address.new("Hyancintvej", "Copenhagen")
89  
-    #
90  
-    #   customer.address_street = "Vesterbrogade"
91  
-    #   customer.address        # => Address.new("Hyancintvej", "Copenhagen")
92  
-    #   customer.clear_aggregation_cache
93  
-    #   customer.address        # => Address.new("Vesterbrogade", "Copenhagen")
94  
-    #
95  
-    #   customer.address = Address.new("May Street", "Chicago")
96  
-    #   customer.address_street # => "May Street"
97  
-    #   customer.address_city   # => "Chicago"
98  
-    #
99  
-    # == Writing value objects
100  
-    #
101  
-    # Value objects are immutable and interchangeable objects that represent a given value, such as
102  
-    # a Money object representing $5. Two Money objects both representing $5 should be equal (through
103  
-    # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
104  
-    # unlike entity objects where equality is determined by identity. An entity class such as Customer can
105  
-    # easily have two different objects that both have an address on Hyancintvej. Entity identity is
106  
-    # determined by object or relational unique identifiers (such as primary keys). Normal
107  
-    # ActiveRecord::Base classes are entity objects.
108  
-    #
109  
-    # It's also important to treat the value objects as immutable. Don't allow the Money object to have
110  
-    # its amount changed after creation. Create a new Money object with the new value instead. The
111  
-    # Money#exchange_to method is an example of this. It returns a new value object instead of changing
112  
-    # its own values. Active Record won't persist value objects that have been changed through means
113  
-    # other than the writer method.
114  
-    #
115  
-    # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
116  
-    # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
117  
-    #
118  
-    # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
119  
-    # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
120  
-    #
121  
-    # == Custom constructors and converters
122  
-    #
123  
-    # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
124  
-    # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
125  
-    # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
126  
-    # a custom constructor to be specified.
127  
-    #
128  
-    # When a new value is assigned to the value object, the default assumption is that the new value
129  
-    # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
130  
-    # converted to an instance of value class if necessary.
131  
-    #
132  
-    # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
133  
-    # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
134  
-    # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
135  
-    # values can be assigned to the value object using either another NetAddr::CIDR object, a string
136  
-    # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
137  
-    # these requirements:
138  
-    #
139  
-    #   class NetworkResource < ActiveRecord::Base
140  
-    #     composed_of :cidr,
141  
-    #                 :class_name => 'NetAddr::CIDR',
142  
-    #                 :mapping => [ %w(network_address network), %w(cidr_range bits) ],
143  
-    #                 :allow_nil => true,
144  
-    #                 :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
145  
-    #                 :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
146  
-    #   end
147  
-    #
148  
-    #   # This calls the :constructor
149  
-    #   network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
150  
-    #
151  
-    #   # These assignments will both use the :converter
152  
-    #   network_resource.cidr = [ '192.168.2.1', 8 ]
153  
-    #   network_resource.cidr = '192.168.0.1/24'
154  
-    #
155  
-    #   # This assignment won't use the :converter as the value is already an instance of the value class
156  
-    #   network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
157  
-    #
158  
-    #   # Saving and then reloading will use the :constructor on reload
159  
-    #   network_resource.save
160  
-    #   network_resource.reload
161  
-    #
162  
-    # == Finding records by a value object
163  
-    #
164  
-    # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
165  
-    # by specifying an instance of the value object in the conditions hash. The following example
166  
-    # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
167  
-    #
168  
-    #   Customer.where(:balance => Money.new(20, "USD")).all
169  
-    #
170  
-    module ClassMethods
171  
-      # Adds reader and writer methods for manipulating a value object:
172  
-      # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
173  
-      #
174  
-      # Options are:
175  
-      # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
176  
-      #   can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
177  
-      #   to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
178  
-      #   with this option.
179  
-      # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
180  
-      #   object. Each mapping is represented as an array where the first item is the name of the
181  
-      #   entity attribute and the second item is the name of the attribute in the value object. The
182  
-      #   order in which mappings are defined determines the order in which attributes are sent to the
183  
-      #   value class constructor.
184  
-      # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
185  
-      #   attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
186  
-      #   mapped attributes.
187  
-      #   This defaults to +false+.
188  
-      # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
189  
-      #   is called to initialize the value object. The constructor is passed all of the mapped attributes,
190  
-      #   in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
191  
-      #   to instantiate a <tt>:class_name</tt> object.
192  
-      #   The default is <tt>:new</tt>.
193  
-      # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
194  
-      #   or a Proc that is called when a new value is assigned to the value object. The converter is
195  
-      #   passed the single value that is used in the assignment and is only called if the new value is
196  
-      #   not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
197  
-      #   can return nil to skip the assignment.
198  
-      #
199  
-      # Option examples:
200  
-      #   composed_of :temperature, :mapping => %w(reading celsius)
201  
-      #   composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
202  
-      #                         :converter => Proc.new { |balance| balance.to_money }
203  
-      #   composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
204  
-      #   composed_of :gps_location
205  
-      #   composed_of :gps_location, :allow_nil => true
206  
-      #   composed_of :ip_address,
207  
-      #               :class_name => 'IPAddr',
208  
-      #               :mapping => %w(ip to_i),
209  
-      #               :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
210  
-      #               :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
211  
-      #
212  
-      def composed_of(part_id, options = {})
213  
-        options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
214  
-
215  
-        name        = part_id.id2name
216  
-        class_name  = options[:class_name]  || name.camelize
217  
-        mapping     = options[:mapping]     || [ name, name ]
218  
-        mapping     = [ mapping ] unless mapping.first.is_a?(Array)
219  
-        allow_nil   = options[:allow_nil]   || false
220  
-        constructor = options[:constructor] || :new
221  
-        converter   = options[:converter]
222  
-
223  
-        reader_method(name, class_name, mapping, allow_nil, constructor)
224  
-        writer_method(name, class_name, mapping, allow_nil, converter)
225  
-
226  
-        create_reflection(:composed_of, part_id, options, self)
227  
-      end
228  
-
229  
-      private
230  
-        def reader_method(name, class_name, mapping, allow_nil, constructor)
231  
-          define_method(name) do
232  
-            if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
233  
-              attrs = mapping.collect {|pair| read_attribute(pair.first)}
234  
-              object = constructor.respond_to?(:call) ?
235  
-                constructor.call(*attrs) :
236  
-                class_name.constantize.send(constructor, *attrs)
237  
-              @aggregation_cache[name] = object
238  
-            end
239  
-            @aggregation_cache[name]
240  
-          end
241  
-        end
242  
-
243  
-        def writer_method(name, class_name, mapping, allow_nil, converter)
244  
-          define_method("#{name}=") do |part|
245  
-            klass = class_name.constantize
246  
-            unless part.is_a?(klass) || converter.nil? || part.nil?
247  
-              part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
248  
-            end
249  
-
250  
-            if part.nil? && allow_nil
251  
-              mapping.each { |pair| self[pair.first] = nil }
252  
-              @aggregation_cache[name] = nil
253  
-            else
254  
-              mapping.each { |pair| self[pair.first] = part.send(pair.last) }
255  
-              @aggregation_cache[name] = part.freeze
256  
-            end
257  
-          end
258  
-        end
259  
-    end
260  
-  end
261  
-end
4  activerecord/lib/active_record/attribute_assignment.rb
@@ -132,7 +132,7 @@ def mass_assignment_role
132 132
     private
133 133
 
134 134
     # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
135  
-    # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
  135
+    # by calling new on the column type or aggregation type object with these parameters.
136 136
     # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
137 137
     # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
138 138
     # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
@@ -167,7 +167,7 @@ def execute_callstack_for_multiparameter_attributes(callstack)
167 167
     end
168 168
 
169 169
     def read_value_from_parameter(name, values_hash_from_param)
170  
-      klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
  170
+      klass = column_for_attribute(name).klass
171 171
       if values_hash_from_param.values.all?{|v|v.nil?}
172 172
         nil
173 173
       elsif klass == Time
2  activerecord/lib/active_record/core.rb
@@ -266,7 +266,6 @@ def initialize_dup(other) # :nodoc:
266 266
         @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
267 267
       end
268 268
 
269  
-      @aggregation_cache = {}
270 269
       @association_cache = {}
271 270
       @attributes_cache  = {}
272 271
 
@@ -391,7 +390,6 @@ def init_internals
391 390
 
392 391
       @attributes[pk] = nil unless @attributes.key?(pk)
393 392
 
394  
-      @aggregation_cache       = {}
395 393
       @association_cache       = {}
396 394
       @attributes_cache        = {}
397 395
       @previously_changed      = {}
2  activerecord/lib/active_record/dynamic_matchers.rb
@@ -56,7 +56,7 @@ def initialize(model, name)
56 56
       end
57 57
 
58 58
       def valid?
59  
-        attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
  59
+        attribute_names.all? { |name| model.columns_hash[name] }
60 60
       end
61 61
 
62 62
       def define
1  activerecord/lib/active_record/model.rb
@@ -89,7 +89,6 @@ def self.append_features(base)
89 89
     include ActiveModel::SecurePassword
90 90
     include AutosaveAssociation
91 91
     include NestedAttributes
92  
-    include Aggregations
93 92
     include Transactions
94 93
     include Reflection
95 94
     include Serialization
1  activerecord/lib/active_record/persistence.rb
@@ -267,7 +267,6 @@ def toggle!(attribute)
267 267
     # may do e.g. record.reload(:lock => true) to reload the same record with
268 268
     # an exclusive row lock.
269 269
     def reload(options = nil)
270  
-      clear_aggregation_cache
271 270
       clear_association_cache
272 271
 
273 272
       fresh_object =
45  activerecord/lib/active_record/reflection.rb
@@ -17,36 +17,17 @@ module Reflection # :nodoc:
17 17
     # and creates input fields for all of the attributes depending on their type
18 18
     # and displays the associations to other objects.
19 19
     #
20  
-    # MacroReflection class has info for AggregateReflection and AssociationReflection
21  
-    # classes.
  20
+    # MacroReflection class has info for the AssociationReflection
  21
+    # class.
22 22
     module ClassMethods
23 23
       def create_reflection(macro, name, options, active_record)
24  
-        case macro
25  
-        when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
26  
-          klass = options[:through] ? ThroughReflection : AssociationReflection
27  
-          reflection = klass.new(macro, name, options, active_record)
28  
-        when :composed_of
29  
-          reflection = AggregateReflection.new(macro, name, options, active_record)
30  
-        end
  24
+        klass = options[:through] ? ThroughReflection : AssociationReflection
  25
+        reflection = klass.new(macro, name, options, active_record)
31 26
 
32 27
         self.reflections = self.reflections.merge(name => reflection)
33 28
         reflection
34 29
       end
35 30
 
36  
-      # Returns an array of AggregateReflection objects for all the aggregations in the class.
37  
-      def reflect_on_all_aggregations
38  
-        reflections.values.grep(AggregateReflection)
39  
-      end
40  
-
41  
-      # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
42  
-      #
43  
-      #   Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
44  
-      #
45  
-      def reflect_on_aggregation(aggregation)
46  
-        reflection = reflections[aggregation]
47  
-        reflection if reflection.is_a?(AggregateReflection)
48  
-      end
49  
-
50 31
       # Returns an array of AssociationReflection objects for all the
51 32
       # associations in the class. If you only want to reflect on a certain
52 33
       # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
@@ -78,24 +59,20 @@ def reflect_on_all_autosave_associations
78 59
       end
79 60
     end
80 61
 
81  
-    # Abstract base class for AggregateReflection and AssociationReflection. Objects of
82  
-    # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
  62
+    # Abstract base class for AssociationReflection. Objects of AssociationReflection are returned by the Reflection::ClassMethods.
83 63
     class MacroReflection
84 64
       # Returns the name of the macro.
85 65
       #
86  
-      # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
87 66
       # <tt>has_many :clients</tt> returns <tt>:clients</tt>
88 67
       attr_reader :name
89 68
 
90 69
       # Returns the macro type.
91 70
       #
92  
-      # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
93 71
       # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
94 72
       attr_reader :macro
95 73
 
96 74
       # Returns the hash of options used for the macro.
97 75
       #
98  
-      # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
99 76
       # <tt>has_many :clients</tt> returns +{}+
100 77
       attr_reader :options
101 78
 
@@ -114,7 +91,6 @@ def initialize(macro, name, options, active_record)
114 91
 
115 92
       # Returns the class for the macro.
116 93
       #
117  
-      # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
118 94
       # <tt>has_many :clients</tt> returns the Client class
119 95
       def klass
120 96
         @klass ||= class_name.constantize
@@ -122,7 +98,6 @@ def klass
122 98
 
123 99
       # Returns the class name for the macro.
124 100
       #
125  
-      # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
126 101
       # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
127 102
       def class_name
128 103
         @class_name ||= (options[:class_name] || derive_class_name).to_s
@@ -148,16 +123,6 @@ def derive_class_name
148 123
         end
149 124
     end
150 125
 
151  
-
152  
-    # Holds all the meta-data about an aggregation as it was specified in the
153  
-    # Active Record class.
154  
-    class AggregateReflection < MacroReflection #:nodoc:
155  
-      def mapping
156  
-        mapping = options[:mapping] || [name, name]
157  
-        mapping.first.is_a?(Array) ? mapping : [mapping]
158  
-      end
159  
-    end
160  
-
161 126
     # Holds all the meta-data about an association as it was specified in the
162 127
     # Active Record class.
163 128
     class AssociationReflection < MacroReflection #:nodoc:
3  activerecord/lib/active_record/relation/query_methods.rb
@@ -562,8 +562,7 @@ def build_where(opts, other = [])
562 562
       when String, Array
563 563
         [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
564 564
       when Hash
565  
-        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
566  
-        PredicateBuilder.build_from_hash(table.engine, attributes, table)
  565
+        PredicateBuilder.build_from_hash(table.engine, opts, table)
567 566
       else
568 567
         [opts]
569 568
       end
32  activerecord/lib/active_record/sanitization.rb
@@ -43,36 +43,6 @@ def sanitize_sql_for_assignment(assignments)
43 43
         end
44 44
       end
45 45
 
46  
-      # Accepts a hash of SQL conditions and replaces those attributes
47  
-      # that correspond to a +composed_of+ relationship with their expanded
48  
-      # aggregate attribute values.
49  
-      # Given:
50  
-      #     class Person < ActiveRecord::Base
51  
-      #       composed_of :address, :class_name => "Address",
52  
-      #         :mapping => [%w(address_street street), %w(address_city city)]
53  
-      #     end
54  
-      # Then:
55  
-      #     { :address => Address.new("813 abc st.", "chicago") }
56  
-      #       # => { :address_street => "813 abc st.", :address_city => "chicago" }
57  
-      def expand_hash_conditions_for_aggregates(attrs)
58  
-        expanded_attrs = {}
59  
-        attrs.each do |attr, value|
60  
-          if aggregation = reflect_on_aggregation(attr.to_sym)
61  
-            mapping = aggregation.mapping
62  
-            mapping.each do |field_attr, aggregate_attr|
63  
-              if mapping.size == 1 && !value.respond_to?(aggregate_attr)
64  
-                expanded_attrs[field_attr] = value
65  
-              else
66  
-                expanded_attrs[field_attr] = value.send(aggregate_attr)
67  
-              end
68  
-            end
69  
-          else
70  
-            expanded_attrs[attr] = value
71  
-          end
72  
-        end
73  
-        expanded_attrs
74  
-      end
75  
-
76 46
       # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
77 47
       #   { :name => "foo'bar", :group_id => 4 }
78 48
       #     # => "name='foo''bar' and group_id= 4"
@@ -88,8 +58,6 @@ def expand_hash_conditions_for_aggregates(attrs)
88 58
       #   { :address => Address.new("123 abc st.", "chicago") }
89 59
       #     # => "address_street='123 abc st.' and address_city='chicago'"
90 60
       def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
91  
-        attrs = expand_hash_conditions_for_aggregates(attrs)
92  
-
93 61
         table = Arel::Table.new(table_name).alias(default_table_name)
94 62
         PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
95 63
           connection.visitor.accept b
158  activerecord/test/cases/aggregations_test.rb
... ...
@@ -1,158 +0,0 @@
1  
-require "cases/helper"
2  
-require 'models/customer'
3  
-require 'active_support/core_ext/exception'
4  
-
5  
-class AggregationsTest < ActiveRecord::TestCase
6  
-  fixtures :customers
7  
-
8  
-  def test_find_single_value_object
9  
-    assert_equal 50, customers(:david).balance.amount
10  
-    assert_kind_of Money, customers(:david).balance
11  
-    assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
12  
-  end
13  
-
14  
-  def test_find_multiple_value_object
15  
-    assert_equal customers(:david).address_street, customers(:david).address.street
16  
-    assert(
17  
-      customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
18  
-    )
19  
-  end
20  
-
21  
-  def test_change_single_value_object
22  
-    customers(:david).balance = Money.new(100)
23  
-    customers(:david).save
24  
-    assert_equal 100, customers(:david).reload.balance.amount
25  
-  end
26  
-
27  
-  def test_immutable_value_objects
28  
-    customers(:david).balance = Money.new(100)
29  
-    assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } }
30  
-  end
31  
-
32  
-  def test_inferred_mapping
33  
-    assert_equal "35.544623640962634", customers(:david).gps_location.latitude
34  
-    assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
35  
-
36  
-    customers(:david).gps_location = GpsLocation.new("39x-110")
37  
-
38  
-    assert_equal "39", customers(:david).gps_location.latitude
39  
-    assert_equal "-110", customers(:david).gps_location.longitude
40  
-
41  
-    customers(:david).save
42  
-
43  
-    customers(:david).reload
44  
-
45  
-    assert_equal "39", customers(:david).gps_location.latitude
46  
-    assert_equal "-110", customers(:david).gps_location.longitude
47  
-  end
48  
-
49  
-  def test_reloaded_instance_refreshes_aggregations
50  
-    assert_equal "35.544623640962634", customers(:david).gps_location.latitude
51  
-    assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
52  
-
53  
-    Customer.update_all("gps_location = '24x113'")
54  
-    customers(:david).reload
55  
-    assert_equal '24x113', customers(:david)['gps_location']
56  
-
57  
-    assert_equal GpsLocation.new('24x113'), customers(:david).gps_location
58  
-  end
59  
-
60  
-  def test_gps_equality
61  
-    assert_equal GpsLocation.new('39x110'), GpsLocation.new('39x110')
62  
-  end
63  
-
64  
-  def test_gps_inequality
65  
-    assert_not_equal GpsLocation.new('39x110'), GpsLocation.new('39x111')
66  
-  end
67  
-
68  
-  def test_allow_nil_gps_is_nil
69  
-    assert_nil customers(:zaphod).gps_location
70  
-  end
71  
-
72  
-  def test_allow_nil_gps_set_to_nil
73  
-    customers(:david).gps_location = nil
74  
-    customers(:david).save
75  
-    customers(:david).reload
76  
-    assert_nil customers(:david).gps_location
77  
-  end
78  
-
79  
-  def test_allow_nil_set_address_attributes_to_nil
80  
-    customers(:zaphod).address = nil
81  
-    assert_nil customers(:zaphod).attributes[:address_street]
82  
-    assert_nil customers(:zaphod).attributes[:address_city]
83  
-    assert_nil customers(:zaphod).attributes[:address_country]
84  
-  end
85  
-
86  
-  def test_allow_nil_address_set_to_nil
87  
-    customers(:zaphod).address = nil
88  
-    customers(:zaphod).save
89  
-    customers(:zaphod).reload
90  
-    assert_nil customers(:zaphod).address
91  
-  end
92  
-
93  
-  def test_nil_raises_error_when_allow_nil_is_false
94  
-    assert_raise(NoMethodError) { customers(:david).balance = nil }
95  
-  end
96  
-
97  
-  def test_allow_nil_address_loaded_when_only_some_attributes_are_nil
98  
-    customers(:zaphod).address_street = nil
99  
-    customers(:zaphod).save
100  
-    customers(:zaphod).reload
101  
-    assert_kind_of Address, customers(:zaphod).address
102  
-    assert_nil customers(:zaphod).address.street
103  
-  end
104  
-
105  
-  def test_nil_assignment_results_in_nil
106  
-    customers(:david).gps_location = GpsLocation.new('39x111')
107  
-    assert_not_nil customers(:david).gps_location
108  
-    customers(:david).gps_location = nil
109  
-    assert_nil customers(:david).gps_location
110  
-  end
111  
-
112  
-  def test_nil_return_from_converter_is_respected_when_allow_nil_is_true
113  
-    customers(:david).non_blank_gps_location = ""
114  
-    customers(:david).save
115  
-    customers(:david).reload
116  
-    assert_nil customers(:david).non_blank_gps_location
117  
-  end
118  
-
119  
-  def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false
120  
-    assert_raises(NoMethodError) do
121  
-      customers(:barney).gps_location = ""
122  
-    end
123  
-  end
124  
-
125  
-  def test_do_not_run_the_converter_when_nil_was_set
126  
-    customers(:david).non_blank_gps_location = nil
127  
-    assert_nil Customer.gps_conversion_was_run
128  
-  end
129  
-
130  
-  def test_custom_constructor
131  
-    assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s
132  
-    assert_kind_of Fullname, customers(:barney).fullname
133  
-  end
134  
-
135  
-  def test_custom_converter
136  
-    customers(:barney).fullname = 'Barnoit Gumbleau'
137  
-    assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
138  
-    assert_kind_of Fullname, customers(:barney).fullname
139  
-  end
140  
-end
141  
-
142  
-class OverridingAggregationsTest < ActiveRecord::TestCase
143  
-  class Name; end
144  
-  class DifferentName; end
145  
-
146  
-  class Person < ActiveRecord::Base
147  
-    composed_of :composed_of, :mapping => %w(person_first_name first_name)
148  
-  end
149  
-
150  
-  class DifferentPerson < Person
151  
-    composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name)
152  
-  end
153  
-
154  
-  def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited
155  
-    assert_not_equal Person.reflect_on_aggregation(:composed_of),
156  
-                     DifferentPerson.reflect_on_aggregation(:composed_of)
157  
-  end
158  
-end
44  activerecord/test/cases/base_test.rb
@@ -888,22 +888,6 @@ def test_multiparameter_attributes_on_time_with_empty_seconds
888 888
     assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
889 889
   end
890 890
 
891  
-  def test_multiparameter_assignment_of_aggregation
892  
-    customer = Customer.new
893  
-    address = Address.new("The Street", "The City", "The Country")
894  
-    attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
895  
-    customer.attributes = attributes
896  
-    assert_equal address, customer.address
897  
-  end
898  
-
899  
-  def test_multiparameter_assignment_of_aggregation_out_of_order
900  
-    customer = Customer.new
901  
-    address = Address.new("The Street", "The City", "The Country")
902  
-    attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
903  
-    customer.attributes = attributes
904  
-    assert_equal address, customer.address
905  
-  end
906  
-
907 891
   def test_multiparameter_assignment_of_aggregation_with_missing_values
908 892
     ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
909 893
       customer = Customer.new
@@ -914,14 +898,6 @@ def test_multiparameter_assignment_of_aggregation_with_missing_values
914 898
     assert_equal("address", ex.errors[0].attribute)
915 899
   end
916 900
 
917  
-  def test_multiparameter_assignment_of_aggregation_with_blank_values
918  
-    customer = Customer.new
919  
-    address = Address.new("The Street", "The City", "The Country")
920  
-    attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
921  
-    customer.attributes = attributes
922  
-    assert_equal Address.new(nil, "The City", "The Country"), customer.address
923  
-  end
924  
-
925 901
   def test_multiparameter_assignment_of_aggregation_with_large_index
926 902
     ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
927 903
       customer = Customer.new
@@ -1021,26 +997,6 @@ def test_dup
1021 997
     assert_equal("c", duped_topic.title)
1022 998
   end
1023 999
 
1024  
-  def test_dup_with_aggregate_of_same_name_as_attribute
1025  
-    dev = DeveloperWithAggregate.find(1)
1026  
-    assert_kind_of DeveloperSalary, dev.salary
1027  
-
1028  
-    dup = nil
1029  
-    assert_nothing_raised { dup = dev.dup }
1030  
-    assert_kind_of DeveloperSalary, dup.salary
1031  
-    assert_equal dev.salary.amount, dup.salary.amount
1032  
-    assert !dup.persisted?
1033  
-
1034  
-    # test if the attributes have been dupd
1035  
-    original_amount = dup.salary.amount
1036  
-    dev.salary.amount = 1
1037  
-    assert_equal original_amount, dup.salary.amount
1038  
-
1039  
-    assert dup.save
1040  
-    assert dup.persisted?
1041  
-    assert_not_equal dup.id, dev.id
1042  
-  end
1043  
-
1044 1000
   def test_dup_does_not_copy_associations
1045 1001
     author = authors(:david)
1046 1002
     assert_not_equal [], author.posts
76  activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -45,32 +45,6 @@ def test_find_all_by_one_attribute_which_is_a_symbol
45 45
     assert_equal [], Topic.find_all_by_title("The First Topic!!")
46 46
   end
47 47
 
48  
-  def test_find_all_by_one_attribute_that_is_an_aggregate
49  
-    balance = customers(:david).balance
50  
-    assert_kind_of Money, balance
51  
-    found_customers = Customer.find_all_by_balance(balance)
52  
-    assert_equal 1, found_customers.size
53  
-    assert_equal customers(:david), found_customers.first
54  
-  end
55  
-
56  
-  def test_find_all_by_two_attributes_that_are_both_aggregates
57  
-    balance = customers(:david).balance
58  
-    address = customers(:david).address
59  
-    assert_kind_of Money, balance
60  
-    assert_kind_of Address, address
61  
-    found_customers = Customer.find_all_by_balance_and_address(balance, address)
62  
-    assert_equal 1, found_customers.size
63  
-    assert_equal customers(:david), found_customers.first
64  
-  end
65  
-
66  
-  def test_find_all_by_two_attributes_with_one_being_an_aggregate
67  
-    balance = customers(:david).balance
68  
-    assert_kind_of Money, balance
69  
-    found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
70  
-    assert_equal 1, found_customers.size
71  
-    assert_equal customers(:david), found_customers.first
72  
-  end
73  
-
74 48
   def test_find_all_by_one_attribute_with_options
75 49
     topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
76 50
     assert_equal topics(:first), topics.last
@@ -137,14 +111,6 @@ def test_find_or_create_from_two_attributes_bang
137 111
     assert_equal 17, sig38.firm_id
138 112
   end
139 113
 
140  
-  def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
141  
-    number_of_customers = Customer.count
142  
-    created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
143  
-    assert_equal number_of_customers + 1, Customer.count
144  
-    assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
145  
-    assert created_customer.persisted?
146  
-  end
147  
-
148 114
   def test_find_or_create_from_one_attribute_and_hash
149 115
     number_of_companies = Company.count
150 116
     sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
@@ -167,38 +133,12 @@ def test_find_or_create_from_two_attributes_and_hash
167 133
     assert_equal 23, sig38.client_of
168 134
   end
169 135
 
170  
-  def test_find_or_create_from_one_aggregate_attribute
171  
-    number_of_customers = Customer.count
172  
-    created_customer = Customer.find_or_create_by_balance(Money.new(123))
173  
-    assert_equal number_of_customers + 1, Customer.count
174  
-    assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
175  
-    assert created_customer.persisted?
176  
-  end
177  
-
178  
-  def test_find_or_create_from_one_aggregate_attribute_and_hash
179  
-    number_of_customers = Customer.count
180  
-    balance = Money.new(123)
181  
-    name = "Elizabeth"
182  
-    created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
183  
-    assert_equal number_of_customers + 1, Customer.count
184  
-    assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
185  
-    assert created_customer.persisted?
186  
-    assert_equal balance, created_customer.balance
187  
-    assert_equal name, created_customer.name
188  
-  end
189  
-
190 136
   def test_find_or_initialize_from_one_attribute
191 137
     sig38 = Company.find_or_initialize_by_name("38signals")
192 138
     assert_equal "38signals", sig38.name
193 139
     assert !sig38.persisted?
194 140
   end
195 141
 
196  
-  def test_find_or_initialize_from_one_aggregate_attribute
197  
-    new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
198  
-    assert_equal 123, new_customer.balance.amount
199  
-    assert !new_customer.persisted?
200  
-  end
201  
-
202 142
   def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
203 143
     c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
204 144
     assert_equal "Fortune 1000", c.name
@@ -285,13 +225,6 @@ def test_find_or_initialize_from_two_attributes_but_passing_only_one
285 225
     assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
286 226
   end
287 227
 
288  
-  def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
289  
-    new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
290  
-    assert_equal 123, new_customer.balance.amount
291  
-    assert_equal "Elizabeth", new_customer.name
292  
-    assert !new_customer.persisted?
293  
-  end
294  
-
295 228
   def test_find_or_initialize_from_one_attribute_and_hash
296 229
     sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
297 230
     assert_equal "38signals", sig38.name
@@ -300,15 +233,6 @@ def test_find_or_initialize_from_one_attribute_and_hash
300 233
     assert !sig38.persisted?
301 234
   end
302 235
 
303  
-  def test_find_or_initialize_from_one_aggregate_attribute_and_hash
304  
-    balance = Money.new(123)
305  
-    name = "Elizabeth"
306  
-    new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
307  
-    assert_equal balance, new_customer.balance
308  
-    assert_equal name, new_customer.name
309  
-    assert !new_customer.persisted?
310  
-  end
311  
-
312 236
   def test_find_last_by_one_attribute
313 237
     assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
314 238
     assert_nil Topic.find_last_by_title("A title with no matches")
99  activerecord/test/cases/finder_test.rb
@@ -79,21 +79,6 @@ def test_exists_with_empty_table_and_no_args_given
79 79
     assert !Topic.exists?
80 80
   end
81 81
 
82  
-  def test_exists_with_aggregate_having_three_mappings
83  
-    existing_address = customers(:david).address
84  
-    assert Customer.exists?(:address => existing_address)
85  
-  end
86  
-
87  
-  def test_exists_with_aggregate_having_three_mappings_with_one_difference
88  
-    existing_address = customers(:david).address
89  
-    assert !Customer.exists?(:address =>
90  
-      Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
91  
-    assert !Customer.exists?(:address =>
92  
-      Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
93  
-    assert !Customer.exists?(:address =>