<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,3 +1,5 @@
+* Make sure of use Memcached as test store. Compatibility changes for ActiveRecord 2.2.0
+
 * Make sure of require frameworks configured in environment.rb
 
 </diff>
      <filename>CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -8,7 +8,7 @@ Check for news and tutorials at the {project home page}[http://www.lucaguidi.com
 
 = Usage
 
-Using Memcached and Rails 2.1.1
+Using Memcached and Rails 2.2.0
 
 Make sure to configure your current environment with:
 
@@ -77,7 +77,12 @@ Standalone:
     require 'cached-models'
 
     ActiveRecord::Base.rails_cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, 'localhost')
-  
+
+
+
+= Test
+  Make sure your current store is Memcached
+
 
 
 = Contribute</diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -6,107 +6,12 @@ require File.dirname(__FILE__) + '/associations/has_many_association'
 module ActiveRecord
   module Associations
     module ClassMethods
-      # Adds the following methods for retrieval and query of collections of associated objects:
-      # +collection+ is replaced with the symbol passed as the first argument, so
-      # &lt;tt&gt;has_many :clients&lt;/tt&gt; would add among others &lt;tt&gt;clients.empty?&lt;/tt&gt;.
-      # * &lt;tt&gt;collection(force_reload = false)&lt;/tt&gt; - Returns an array of all the associated objects.
-      #   An empty array is returned if none are found.
-      # * &lt;tt&gt;collection&lt;&lt;(object, ...)&lt;/tt&gt; - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
-      # * &lt;tt&gt;collection.delete(object, ...)&lt;/tt&gt; - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
-      #   This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
-      # * &lt;tt&gt;collection=objects&lt;/tt&gt; - Replaces the collections content by deleting and adding objects as appropriate.
-      # * &lt;tt&gt;collection_singular_ids&lt;/tt&gt; - Returns an array of the associated objects' ids
-      # * &lt;tt&gt;collection_singular_ids=ids&lt;/tt&gt; - Replace the collection with the objects identified by the primary keys in +ids+
-      # * &lt;tt&gt;collection.clear&lt;/tt&gt; - Removes every object from the collection. This destroys the associated objects if they
-      #   are associated with &lt;tt&gt;:dependent =&gt; :destroy&lt;/tt&gt;, deletes them directly from the database if &lt;tt&gt;:dependent =&gt; :delete_all&lt;/tt&gt;,
-      #   otherwise sets their foreign keys to +NULL+.
-      # * &lt;tt&gt;collection.empty?&lt;/tt&gt; - Returns +true+ if there are no associated objects.
-      # * &lt;tt&gt;collection.size&lt;/tt&gt; - Returns the number of associated objects.
-      # * &lt;tt&gt;collection.find&lt;/tt&gt; - Finds an associated object according to the same rules as Base.find.
-      # * &lt;tt&gt;collection.build(attributes = {}, ...)&lt;/tt&gt; - Returns one or more new objects of the collection type that have been instantiated
-      #   with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
-      #   associated object already exists, not if it's +nil+!
-      # * &lt;tt&gt;collection.create(attributes = {})&lt;/tt&gt; - Returns a new object of the collection type that has been instantiated
-      #   with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
-      #   *Note:* This only works if an associated object already exists, not if it's +nil+!
-      #
-      # Example: A Firm class declares &lt;tt&gt;has_many :clients&lt;/tt&gt;, which will add:
-      # * &lt;tt&gt;Firm#clients&lt;/tt&gt; (similar to &lt;tt&gt;Clients.find :all, :conditions =&gt; &quot;firm_id = #{id}&quot;&lt;/tt&gt;)
-      # * &lt;tt&gt;Firm#clients&lt;&lt;&lt;/tt&gt;
-      # * &lt;tt&gt;Firm#clients.delete&lt;/tt&gt;
-      # * &lt;tt&gt;Firm#clients=&lt;/tt&gt;
-      # * &lt;tt&gt;Firm#client_ids&lt;/tt&gt;
-      # * &lt;tt&gt;Firm#client_ids=&lt;/tt&gt;
-      # * &lt;tt&gt;Firm#clients.clear&lt;/tt&gt;
-      # * &lt;tt&gt;Firm#clients.empty?&lt;/tt&gt; (similar to &lt;tt&gt;firm.clients.size == 0&lt;/tt&gt;)
-      # * &lt;tt&gt;Firm#clients.size&lt;/tt&gt; (similar to &lt;tt&gt;Client.count &quot;firm_id = #{id}&quot;&lt;/tt&gt;)
-      # * &lt;tt&gt;Firm#clients.find&lt;/tt&gt; (similar to &lt;tt&gt;Client.find(id, :conditions =&gt; &quot;firm_id = #{id}&quot;)&lt;/tt&gt;)
-      # * &lt;tt&gt;Firm#clients.build&lt;/tt&gt; (similar to &lt;tt&gt;Client.new(&quot;firm_id&quot; =&gt; id)&lt;/tt&gt;)
-      # * &lt;tt&gt;Firm#clients.create&lt;/tt&gt; (similar to &lt;tt&gt;c = Client.new(&quot;firm_id&quot; =&gt; id); c.save; c&lt;/tt&gt;)
-      # The declaration can also include an options hash to specialize the behavior of the association.
-      #
-      # Options are:
-      # * &lt;tt&gt;:class_name&lt;/tt&gt; - Specify the class name of the association. Use it only if that name can't be inferred
-      #   from the association name. So &lt;tt&gt;has_many :products&lt;/tt&gt; will by default be linked to the Product class, but
-      #   if the real class name is SpecialProduct, you'll have to specify it with this option.
-      # * &lt;tt&gt;:conditions&lt;/tt&gt; - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
-      #   SQL fragment, such as &lt;tt&gt;price &gt; 5 AND name LIKE 'B%'&lt;/tt&gt;.  Record creations from the association are scoped if a hash
-      #   is used.  &lt;tt&gt;has_many :posts, :conditions =&gt; {:published =&gt; true}&lt;/tt&gt; will create published posts with &lt;tt&gt;@blog.posts.create&lt;/tt&gt;
-      #   or &lt;tt&gt;@blog.posts.build&lt;/tt&gt;.
-      # * &lt;tt&gt;:order&lt;/tt&gt; - Specify the order in which the associated objects are returned as an &lt;tt&gt;ORDER BY&lt;/tt&gt; SQL fragment,
-      #   such as &lt;tt&gt;last_name, first_name DESC&lt;/tt&gt;.
-      # * &lt;tt&gt;:foreign_key&lt;/tt&gt; - Specify the foreign key used for the association. By default this is guessed to be the name
-      #   of this class in lower-case and &quot;_id&quot; suffixed. So a Person class that makes a +has_many+ association will use &quot;person_id&quot;
-      #   as the default &lt;tt&gt;:foreign_key&lt;/tt&gt;.
-      # * &lt;tt&gt;:dependent&lt;/tt&gt; - If set to &lt;tt&gt;:destroy&lt;/tt&gt; all the associated objects are destroyed
-      #   alongside this object by calling their +destroy+ method.  If set to &lt;tt&gt;:delete_all&lt;/tt&gt; all associated
-      #   objects are deleted *without* calling their +destroy+ method.  If set to &lt;tt&gt;:nullify&lt;/tt&gt; all associated
-      #   objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
-      #   the &lt;tt&gt;:through&lt;/tt&gt; option.
-      # * &lt;tt&gt;:finder_sql&lt;/tt&gt; - Specify a complete SQL statement to fetch the association. This is a good way to go for complex
-      #   associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
-      # * &lt;tt&gt;:counter_sql&lt;/tt&gt; - Specify a complete SQL statement to fetch the size of the association. If &lt;tt&gt;:finder_sql&lt;/tt&gt; is
-      #   specified but not &lt;tt&gt;:counter_sql&lt;/tt&gt;, &lt;tt&gt;:counter_sql&lt;/tt&gt; will be generated by replacing &lt;tt&gt;SELECT ... FROM&lt;/tt&gt; with &lt;tt&gt;SELECT COUNT(*) FROM&lt;/tt&gt;.
-      # * &lt;tt&gt;:extend&lt;/tt&gt; - Specify a named module for extending the proxy. See &quot;Association extensions&quot;.
-      # * &lt;tt&gt;:include&lt;/tt&gt; - Specify second-order associations that should be eager loaded when the collection is loaded.
-      # * &lt;tt&gt;:group&lt;/tt&gt; - An attribute name by which the result should be grouped. Uses the &lt;tt&gt;GROUP BY&lt;/tt&gt; SQL-clause.
-      # * &lt;tt&gt;:limit&lt;/tt&gt; - An integer determining the limit on the number of rows that should be returned.
-      # * &lt;tt&gt;:offset&lt;/tt&gt; - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
-      # * &lt;tt&gt;:select&lt;/tt&gt; - By default, this is &lt;tt&gt;*&lt;/tt&gt; as in &lt;tt&gt;SELECT * FROM&lt;/tt&gt;, but can be changed if you, for example, want to do a join
-      #   but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
-      # * &lt;tt&gt;:as&lt;/tt&gt; - Specifies a polymorphic interface (See &lt;tt&gt;belongs_to&lt;/tt&gt;).
-      # * &lt;tt&gt;:through&lt;/tt&gt; - Specifies a Join Model through which to perform the query.  Options for &lt;tt&gt;:class_name&lt;/tt&gt; and &lt;tt&gt;:foreign_key&lt;/tt&gt;
-      #   are ignored, as the association uses the source reflection. You can only use a &lt;tt&gt;:through&lt;/tt&gt; query through a &lt;tt&gt;belongs_to&lt;/tt&gt;
-      #   or &lt;tt&gt;has_many&lt;/tt&gt; association on the join model.
-      # * &lt;tt&gt;:source&lt;/tt&gt; - Specifies the source association name used by &lt;tt&gt;has_many :through&lt;/tt&gt; queries.  Only use it if the name cannot be
-      #   inferred from the association.  &lt;tt&gt;has_many :subscribers, :through =&gt; :subscriptions&lt;/tt&gt; will look for either &lt;tt&gt;:subscribers&lt;/tt&gt; or
-      #   &lt;tt&gt;:subscriber&lt;/tt&gt; on Subscription, unless a &lt;tt&gt;:source&lt;/tt&gt; is given.
-      # * &lt;tt&gt;:source_type&lt;/tt&gt; - Specifies type of the source association used by &lt;tt&gt;has_many :through&lt;/tt&gt; queries where the source
-      #   association is a polymorphic +belongs_to+.
-      # * &lt;tt&gt;:uniq&lt;/tt&gt; - If true, duplicates will be omitted from the collection. Useful in conjunction with &lt;tt&gt;:through&lt;/tt&gt;.
-      # * &lt;tt&gt;:readonly&lt;/tt&gt; - If true, all the associated objects are readonly through the association.
-      # * &lt;tt&gt;:cached&lt;/tt&gt; - If true, all the associated objects will be cached.
-      #
-      # Option examples:
-      #   has_many :comments, :order =&gt; &quot;posted_on&quot;
-      #   has_many :comments, :include =&gt; :author
-      #   has_many :people, :class_name =&gt; &quot;Person&quot;, :conditions =&gt; &quot;deleted = 0&quot;, :order =&gt; &quot;name&quot;
-      #   has_many :tracks, :order =&gt; &quot;position&quot;, :dependent =&gt; :destroy
-      #   has_many :comments, :dependent =&gt; :nullify
-      #   has_many :tags, :as =&gt; :taggable
-      #   has_many :reports, :readonly =&gt; true
-      #   has_many :posts, :cached =&gt; true
-      #   has_many :subscribers, :through =&gt; :subscriptions, :source =&gt; :user
-      #   has_many :subscribers, :class_name =&gt; &quot;Person&quot;, :finder_sql =&gt;
-      #       'SELECT DISTINCT people.* ' +
-      #       'FROM people p, post_subscriptions ps ' +
-      #       'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
-      #       'ORDER BY p.first_name'
-      def has_many(association_id, options = {}, &amp;extension)
+      def has_many(association_id, options = {}, &amp;extension) #:nodoc:
         reflection = create_has_many_reflection(association_id, options, &amp;extension)
 
         configure_dependency_for_has_many(reflection)
 
+        add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
         add_multiple_associated_save_callbacks(reflection.name)
         add_association_callbacks(reflection.name, reflection.options)
 
@@ -116,70 +21,10 @@ module ActiveRecord
           collection_accessor_methods(reflection, HasManyAssociation, options)
         end
 
-        add_cache_callbacks if options[:cached]
+        add_has_many_cache_callbacks if options[:cached]
       end
 
-      # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
-      # +association+ is replaced with the symbol passed as the first argument, so
-      # &lt;tt&gt;belongs_to :author&lt;/tt&gt; would add among others &lt;tt&gt;author.nil?&lt;/tt&gt;.
-      # * &lt;tt&gt;association(force_reload = false)&lt;/tt&gt; - Returns the associated object. +nil+ is returned if none is found.
-      # * &lt;tt&gt;association=(associate)&lt;/tt&gt; - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
-      # * &lt;tt&gt;association.nil?&lt;/tt&gt; - Returns +true+ if there is no associated object.
-      # * &lt;tt&gt;build_association(attributes = {})&lt;/tt&gt; - Returns a new object of the associated type that has been instantiated
-      #   with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
-      # * &lt;tt&gt;create_association(attributes = {})&lt;/tt&gt; - Returns a new object of the associated type that has been instantiated
-      #   with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
-      #
-      # Example: A Post class declares &lt;tt&gt;belongs_to :author&lt;/tt&gt;, which will add:
-      # * &lt;tt&gt;Post#author&lt;/tt&gt; (similar to &lt;tt&gt;Author.find(author_id)&lt;/tt&gt;)
-      # * &lt;tt&gt;Post#author=(author)&lt;/tt&gt; (similar to &lt;tt&gt;post.author_id = author.id&lt;/tt&gt;)
-      # * &lt;tt&gt;Post#author?&lt;/tt&gt; (similar to &lt;tt&gt;post.author == some_author&lt;/tt&gt;)
-      # * &lt;tt&gt;Post#author.nil?&lt;/tt&gt;
-      # * &lt;tt&gt;Post#build_author&lt;/tt&gt; (similar to &lt;tt&gt;post.author = Author.new&lt;/tt&gt;)
-      # * &lt;tt&gt;Post#create_author&lt;/tt&gt; (similar to &lt;tt&gt;post.author = Author.new; post.author.save; post.author&lt;/tt&gt;)
-      # The declaration can also include an options hash to specialize the behavior of the association.
-      #
-      # Options are:
-      # * &lt;tt&gt;:class_name&lt;/tt&gt; - Specify the class name of the association. Use it only if that name can't be inferred
-      #   from the association name. So &lt;tt&gt;has_one :author&lt;/tt&gt; will by default be linked to the Author class, but
-      #   if the real class name is Person, you'll have to specify it with this option.
-      # * &lt;tt&gt;:conditions&lt;/tt&gt; - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
-      #   SQL fragment, such as &lt;tt&gt;authorized = 1&lt;/tt&gt;.
-      # * &lt;tt&gt;:select&lt;/tt&gt; - By default, this is &lt;tt&gt;*&lt;/tt&gt; as in &lt;tt&gt;SELECT * FROM&lt;/tt&gt;, but can be changed if, for example, you want to do a join
-      #   but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
-      # * &lt;tt&gt;:foreign_key&lt;/tt&gt; - Specify the foreign key used for the association. By default this is guessed to be the name
-      #   of the association with an &quot;_id&quot; suffix. So a class that defines a &lt;tt&gt;belongs_to :person&lt;/tt&gt; association will use
-      #   &quot;person_id&quot; as the default &lt;tt&gt;:foreign_key&lt;/tt&gt;. Similarly, &lt;tt&gt;belongs_to :favorite_person, :class_name =&gt; &quot;Person&quot;&lt;/tt&gt;
-      #   will use a foreign key of &quot;favorite_person_id&quot;.
-      # * &lt;tt&gt;:dependent&lt;/tt&gt; - If set to &lt;tt&gt;:destroy&lt;/tt&gt;, the associated object is destroyed when this object is. If set to
-      #   &lt;tt&gt;:delete&lt;/tt&gt;, the associated object is deleted *without* calling its destroy method. This option should not be specified when
-      #   &lt;tt&gt;belongs_to&lt;/tt&gt; is used in conjunction with a &lt;tt&gt;has_many&lt;/tt&gt; relationship on another class because of the potential to leave
-      #   orphaned records behind.
-      # * &lt;tt&gt;:counter_cache&lt;/tt&gt; - Caches the number of belonging objects on the associate class through the use of +increment_counter+
-      #   and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
-      #   destroyed. This requires that a column named &lt;tt&gt;#{table_name}_count&lt;/tt&gt; (such as +comments_count+ for a belonging Comment class)
-      #   is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
-      #   a column name instead of a +true+/+false+ value to this option (e.g., &lt;tt&gt;:counter_cache =&gt; :my_custom_counter&lt;/tt&gt;.)
-      #   When creating a counter cache column, the database statement or migration must specify a default value of &lt;tt&gt;0&lt;/tt&gt;, failing to do 
-      #   this results in a counter with +NULL+ value, which will never increment.
-      #   Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
-      # * &lt;tt&gt;:include&lt;/tt&gt; - Specify second-order associations that should be eager loaded when this object is loaded.
-      # * &lt;tt&gt;:polymorphic&lt;/tt&gt; - Specify this association is a polymorphic association by passing +true+.
-      #   Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
-      #   to the +attr_readonly+ list in the associated classes (e.g. &lt;tt&gt;class Post; attr_readonly :comments_count; end&lt;/tt&gt;).
-      # * &lt;tt&gt;:readonly&lt;/tt&gt; - If true, the associated object is readonly through the association.
-      # * &lt;tt&gt;:validate&lt;/tt&gt; - If false, don't validate the associated objects when saving the parent object. +false+ by default.
-      #
-      # Option examples:
-      #   belongs_to :firm, :foreign_key =&gt; &quot;client_of&quot;
-      #   belongs_to :author, :class_name =&gt; &quot;Person&quot;, :foreign_key =&gt; &quot;author_id&quot;
-      #   belongs_to :valid_coupon, :class_name =&gt; &quot;Coupon&quot;, :foreign_key =&gt; &quot;coupon_id&quot;,
-      #              :conditions =&gt; 'discounts &gt; #{payments_count}'
-      #   belongs_to :attachable, :polymorphic =&gt; true
-      #   belongs_to :project, :readonly =&gt; true
-      #   belongs_to :post, :counter_cache =&gt; true
-      #   belongs_to :blog, :cached =&gt; true
-      def belongs_to(association_id, options = {})
+      def belongs_to(association_id, options = {}) #:nodoc:
         reflection = create_belongs_to_reflection(association_id, options)
 
         ivar = &quot;@#{reflection.name}&quot;
@@ -189,7 +34,7 @@ module ActiveRecord
 
           method_name = &quot;polymorphic_belongs_to_before_save_for_#{reflection.name}&quot;.to_sym
           define_method(method_name) do
-            association = instance_variable_get(&quot;#{ivar}&quot;) if instance_variable_defined?(&quot;#{ivar}&quot;)
+            association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
 
             if association &amp;&amp; association.target
               if association.new_record?
@@ -197,8 +42,8 @@ module ActiveRecord
               end
 
               if association.updated?
-                self[&quot;#{reflection.primary_key_name}&quot;] = association.id
-                self[&quot;#{reflection.options[:foreign_type]}&quot;] = association.class.base_class.name.to_s
+                self[reflection.primary_key_name] = association.id
+                self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
               end
             end
           end
@@ -210,7 +55,7 @@ module ActiveRecord
 
           method_name = &quot;belongs_to_before_save_for_#{reflection.name}&quot;.to_sym
           define_method(method_name) do
-            association = instance_variable_get(&quot;#{ivar}&quot;) if instance_variable_defined?(&quot;#{ivar}&quot;)
+            association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
 
             if !association.nil?
               if association.new_record?
@@ -218,7 +63,7 @@ module ActiveRecord
               end
 
               if association.updated?
-                self[&quot;#{reflection.primary_key_name}&quot;] = association.id
+                self[reflection.primary_key_name] = association.id
               end
             end
           end
@@ -233,15 +78,15 @@ module ActiveRecord
 
           method_name = &quot;belongs_to_counter_cache_after_create_for_#{reflection.name}&quot;.to_sym
           define_method(method_name) do
-            association = send(&quot;#{reflection.name}&quot;)
-            association.class.increment_counter(&quot;#{cache_column}&quot;, send(&quot;#{reflection.primary_key_name}&quot;)) unless association.nil?
+            association = send(reflection.name)
+            association.class.increment_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
           end
           after_create method_name
 
           method_name = &quot;belongs_to_counter_cache_before_destroy_for_#{reflection.name}&quot;.to_sym
           define_method(method_name) do
-            association = send(&quot;#{reflection.name}&quot;)
-            association.class.decrement_counter(&quot;#{cache_column}&quot;, send(&quot;#{reflection.primary_key_name}&quot;)) unless association.nil?
+            association = send(reflection.name)
+            association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
           end
           before_destroy method_name
 
@@ -250,17 +95,7 @@ module ActiveRecord
           )
         end
 
-        if options[:cached]
-          after_save_method_name = &quot;belongs_to_after_save_for_#{reflection.name}&quot;.to_sym
-          after_destroy_method_name = &quot;belongs_to_after_destroy_for_#{reflection.name}&quot;.to_sym
-          define_method(after_save_method_name) do
-            send(reflection.name).expire_cache_for(self.class.name)
-          end
-
-          alias_method after_destroy_method_name, after_save_method_name
-          after_save after_save_method_name
-          after_destroy after_destroy_method_name
-        end
+        add_belongs_to_cache_callbacks(association_id) if options[:cached]
 
         add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
 
@@ -300,17 +135,19 @@ module ActiveRecord
         define_method(method_name) do
           if options[:cached]
             cache_fetch(&quot;#{cache_key}/#{method_name}&quot;, send(&quot;calculate_#{method_name}&quot;))
-          else
+          elsif send(reflection.name).loaded? || reflection.options[:finder_sql]
             send(&quot;calculate_#{method_name}&quot;)
+          else
+            send(reflection.name).all(:select =&gt; &quot;#{reflection.quoted_table_name}.#{reflection.klass.primary_key}&quot;).map(&amp;:id)
           end
         end
-        
+
         define_method(&quot;calculate_#{method_name}&quot;) do
-          send(reflection.name).map { |record| record.id }
+          send(reflection.name).map(&amp;:id)
         end
       end
 
-      def has_and_belongs_to_many(association_id, options = {}, &amp;extension)
+      def has_and_belongs_to_many(association_id, options = {}, &amp;extension) #:nodoc:
         reflection = create_has_and_belongs_to_many_reflection(association_id, options, &amp;extension)
 
         add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
@@ -352,42 +189,11 @@ module ActiveRecord
         end
       end
 
-      def create_has_many_reflection(association_id, options, &amp;extension)
-        options.assert_valid_keys(
-          :class_name, :table_name, :foreign_key, :primary_key,
-          :dependent,
-          :select, :conditions, :include, :order, :group, :limit, :offset,
-          :as, :through, :source, :source_type,
-          :uniq,
-          :finder_sql, :counter_sql,
-          :before_add, :after_add, :before_remove, :after_remove,
-          :extend, :readonly,
-          :validate, :accessible,
-          :cached
-        )
-
-        options[:extend] = create_extension_modules(association_id, extension, options[:extend])
-
-        create_reflection(:has_many, association_id, options, self)
-      end
-
-      def create_belongs_to_reflection(association_id, options)
-        options.assert_valid_keys(
-          :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
-          :counter_cache, :extend, :polymorphic, :readonly, :validate, :cached
-        )
+      valid_keys_for_has_many_association &lt;&lt; :cached
+      valid_keys_for_belongs_to_association &lt;&lt; :cached
 
-        reflection = create_reflection(:belongs_to, association_id, options, self)
-
-        if options[:polymorphic]
-          reflection.options[:foreign_type] ||= reflection.class_name.underscore + &quot;_type&quot;
-        end
-
-        reflection
-      end
-
-      def add_cache_callbacks
-        method_name = :after_save_cache_expire
+      def add_has_many_cache_callbacks
+        method_name = :has_many_after_save_cache_expire
         return if respond_to? method_name
 
         define_method(method_name) do
@@ -399,6 +205,20 @@ module ActiveRecord
         end
         after_save method_name
       end
+
+      def add_belongs_to_cache_callbacks(reflection_name)
+        after_save_method_name = &quot;belongs_to_after_save_for_#{reflection_name}&quot;.to_sym
+        after_destroy_method_name = &quot;belongs_to_after_destroy_for_#{reflection_name}&quot;.to_sym
+        return if respond_to? after_save_method_name
+
+        define_method(after_save_method_name) do
+          send(reflection_name).expire_cache_for(self.class.name)
+        end
+
+        alias_method after_destroy_method_name, after_save_method_name
+        after_save after_save_method_name
+        after_destroy after_destroy_method_name
+      end
     end
   end
 end</diff>
      <filename>lib/activerecord/lib/active_record/associations.rb</filename>
    </modified>
    <modified>
      <diff>@@ -54,7 +54,7 @@ module ActiveRecord
         result = true
         load_target if @owner.new_record?
 
-        @owner.transaction do
+        transaction do
           flatten_deeper(records).each do |record|
             raise_on_type_mismatch(record)
             add_record_to_target_with_callbacks(record) do |r|
@@ -68,12 +68,18 @@ module ActiveRecord
         result &amp;&amp; self
       end
 
-      # Remove +records+ from this association.  Does not destroy +records+.
+      # Removes +records+ from this association calling +before_remove+ and
+      # +after_remove+ callbacks.
+      #
+      # This method is abstract in the sense that +delete_records+ has to be
+      # provided by descendants. Note this method does not imply the records
+      # are actually removed from the database, that depends precisely on
+      # +delete_records+. They are in any case removed from the collection.
       def delete(*records)
         records = flatten_deeper(records)
         records.each { |record| raise_on_type_mismatch(record) }
 
-        @owner.transaction do
+        transaction do
           records.each { |record| callback(:before_remove, record) }
 
           old_records = records.reject {|r| r.new_record? }
@@ -94,7 +100,7 @@ module ActiveRecord
 
         if @reflection.options[:dependent] &amp;&amp; @reflection.options[:dependent] == :destroy
           destroy_all
-        else
+        else 
           delete_all
         end
 
@@ -103,9 +109,16 @@ module ActiveRecord
         self
       end
 
-      # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
-      # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
-      # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
+      # Returns the size of the collection by executing a SELECT COUNT(*)
+      # query if the collection hasn't been loaded, and calling
+      # &lt;tt&gt;collection.size&lt;/tt&gt; if it has.
+      #
+      # If the collection has been already loaded +size+ and +length+ are
+      # equivalent. If not and you are going to need the records anyway
+      # +length+ will take one less query. Otherwise +size+ is more efficient.
+      #
+      # This method is abstract in the sense that it relies on
+      # +count_records+, which is a method descendants have to provide.
       def size
         if @reflection.options[:cached]
           returning result = self.to_ary do
@@ -115,6 +128,8 @@ module ActiveRecord
 
         if @owner.new_record? || (loaded? &amp;&amp; !@reflection.options[:uniq])
           @target.size
+        elsif !loaded? &amp;&amp; @reflection.options[:group]
+          load_target.size
         elsif !loaded? &amp;&amp; !@reflection.options[:uniq] &amp;&amp; @target.is_a?(Array)
           unsaved_records = @target.select { |r| r.new_record? }
           unsaved_records.size + count_records</diff>
      <filename>lib/activerecord/lib/active_record/associations/association_collection.rb</filename>
    </modified>
    <modified>
      <diff>@@ -216,85 +216,83 @@ class HasManyAssociationTest &lt; Test::Unit::TestCase
     end
   end
 
-  uses_memcached 'HasManyAssociationTest' do
-    def test_should_refresh_caches_when_pushing_element_to_association_belonging_to_another_model
-      post = authors(:chuck).cached_posts.last
-      authors(:luca).cached_posts &lt;&lt; post
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-      assert_equal posts_by_author(:chuck), authors(:chuck).cached_posts
-    end
+  def test_should_refresh_caches_when_pushing_element_to_association_belonging_to_another_model
+    post = authors(:chuck).cached_posts.last
+    authors(:luca).cached_posts &lt;&lt; post
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
+    assert_equal posts_by_author(:chuck), authors(:chuck).cached_posts
+  end
 
-    def test_should_refresh_caches_when_pushing_element_to_polymorphic_association_belonging_to_another_model
-      tag = posts(:welcome).cached_tags.last
-      posts(:cached_models).cached_tags &lt;&lt; tag
+  def test_should_refresh_caches_when_pushing_element_to_polymorphic_association_belonging_to_another_model
+    tag = posts(:welcome).cached_tags.last
+    posts(:cached_models).cached_tags &lt;&lt; tag
 
-      # NOTE for some weird reason the assertion fails, even if the collections are equals.
-      # I forced the comparision between the ids.
-      assert_equal tags_by_post(:cached_models).map(&amp;:id).sort,
-        posts(:cached_models).cached_tags.map(&amp;:id).sort
-    end
+    # NOTE for some weird reason the assertion fails, even if the collections are equals.
+    # I forced the comparision between the ids.
+    assert_equal tags_by_post(:cached_models).map(&amp;:id).sort,
+      posts(:cached_models).cached_tags.map(&amp;:id).sort
+  end
 
-    def test_should_update_cache_when_pushing_element_with_build
-      author = authors(:luca)
-      post = author.cached_posts.build post_options
-      post.save
-      assert_equal posts_by_author(:luca), author.cached_posts
-    end
+  def test_should_update_cache_when_pushing_element_with_build
+    author = authors(:luca)
+    post = author.cached_posts.build post_options
+    post.save
+    assert_equal posts_by_author(:luca), author.cached_posts
+  end
 
-    def test_should_update_cache_when_pushing_element_with_create
-      author = authors(:luca)
-      author.cached_posts.create post_options(:title =&gt; &quot;CM Overview&quot;)
-      assert_equal posts_by_author(:luca), author.cached_posts
-    end
+  def test_should_update_cache_when_pushing_element_with_create
+    author = authors(:luca)
+    author.cached_posts.create post_options(:title =&gt; &quot;CM Overview&quot;)
+    assert_equal posts_by_author(:luca), author.cached_posts
+  end
 
-    def test_should_update_cache_when_pushing_element_with_create_bang_method
-      author = authors(:luca)
-      author.cached_posts.create! post_options(:title =&gt; &quot;CM Overview!!&quot;)
-      assert_equal posts_by_author(:luca), author.cached_posts
-    end
+  def test_should_update_cache_when_pushing_element_with_create_bang_method
+    author = authors(:luca)
+    author.cached_posts.create! post_options(:title =&gt; &quot;CM Overview!!&quot;)
+    assert_equal posts_by_author(:luca), author.cached_posts
+  end
 
-    def test_should_expire_cache_when_delete_all_elements_from_collection
-      authors(:luca).cached_posts.delete_all
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-    end
+  def test_should_expire_cache_when_delete_all_elements_from_collection
+    authors(:luca).cached_posts.delete_all
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
+  end
 
-    def test_should_expire_cache_when_destroy_all_elements_from_collection
-      authors(:luca).cached_posts.destroy_all
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-    end
+  def test_should_expire_cache_when_destroy_all_elements_from_collection
+    authors(:luca).cached_posts.destroy_all
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
+  end
 
-    def test_should_update_cache_when_clearing_collection
-      authors(:luca).cached_posts.clear      
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-    end
+  def test_should_update_cache_when_clearing_collection
+    authors(:luca).cached_posts.clear
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
+  end
 
-    def test_should_update_cache_when_clearing_collection_with_dependent_destroy_option
-      authors(:luca).cached_dependent_posts.clear
-      assert_equal posts_by_author(:luca), authors(:luca).cached_dependent_posts
-    end
+  def test_should_update_cache_when_clearing_collection_with_dependent_destroy_option
+    authors(:luca).cached_dependent_posts.clear
+    assert_equal posts_by_author(:luca), authors(:luca).cached_dependent_posts
+  end
 
-    def test_should_update_cache_when_deleting_element_from_collection
-      authors(:luca).cached_posts.delete(posts_by_author(:luca).first)
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-    end
+  def test_should_update_cache_when_deleting_element_from_collection
+    authors(:luca).cached_posts.delete(posts_by_author(:luca).first)
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
+  end
 
-    def test_should_update_cache_when_replace_collection
-      post = create_post; post.save
-      posts = [ posts_by_author(:luca).first, post ]
-      authors(:luca).cached_posts.replace(posts)
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-    end
+  def test_should_update_cache_when_replace_collection
+    post = create_post; post.save
+    posts = [ posts_by_author(:luca).first, post ]
+    authors(:luca).cached_posts.replace(posts)
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
+  end
 
-    def test_should_not_expire_cache_on_update_on_missing_updated_at
-      author = authors(:luca)
-      old_cache_key = author.cache_key
+  def test_should_not_expire_cache_on_update_on_missing_updated_at
+    author = authors(:luca)
+    old_cache_key = author.cache_key
 
-      author.cached_posts # force cache loading
-      author.update_attributes :first_name =&gt; author.first_name.upcase
+    author.cached_posts # force cache loading
+    author.update_attributes :first_name =&gt; author.first_name.upcase
 
-      # assert_not_equal old_cache_key, author.cache_key
-      assert_equal posts_by_author(:luca), authors(:luca).cached_posts
-    end
+    # assert_not_equal old_cache_key, author.cache_key
+    assert_equal posts_by_author(:luca), authors(:luca).cached_posts
   end
 
   private</diff>
      <filename>test/active_record/associations/has_many_association_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,6 +18,18 @@ require 'post'
 
 Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + &quot;/fixtures&quot;
 ActionController::IntegrationTest.fixture_path = Test::Unit::TestCase.fixture_path
+silence_warnings do
+  cache = ActiveSupport::Cache.lookup_store :mem_cache_store
+  Object.const_set &quot;RAILS_CACHE&quot;, cache
+  ActiveRecord::Base.rails_cache = cache
+end
+
+begin
+  require 'memcache'
+  MemCache.new('localhost').stats
+rescue MemCache::MemCacheError
+  $stderr.puts &quot;[WARNING] Memcache is not running!&quot;
+end
 
 module WillPaginate #:nodoc:
   def paginate(*args)
@@ -65,14 +77,6 @@ rescue LoadError
   $stderr.puts &quot;Skipping #{description} tests. `gem install mocha` and try again.&quot;
 end
 
-def uses_memcached(description)
-  require 'memcache'
-  MemCache.new('localhost').stats
-  yield
-rescue MemCache::MemCacheError
-  $stderr.puts &quot;Skipping #{description} tests. Start memcached and try again.&quot;
-end
-
 if ENV['SKIP_MOCHA'] == 'true'
   class Object
     def expects(*args)</diff>
      <filename>test/test_helper.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>9519ba55603afb4e6dd7cece1337e80f780767d3</id>
    </parent>
  </parents>
  <author>
    <name>Luca Guidi</name>
    <email>guidi.luca@gmail.com</email>
  </author>
  <url>http://github.com/jodosha/cached-models/commit/9bf3af8ff324049149916316dbf7eb0a80a22010</url>
  <id>9bf3af8ff324049149916316dbf7eb0a80a22010</id>
  <committed-date>2008-10-30T03:57:47-07:00</committed-date>
  <authored-date>2008-10-30T03:57:47-07:00</authored-date>
  <message>Make sure of use Memcached as test store. Compatibility changes for ActiveRecord 2.2.0</message>
  <tree>e08bb95b41aa6269cbc9a9ba032983c6e9192157</tree>
  <committer>
    <name>Luca Guidi</name>
    <email>guidi.luca@gmail.com</email>
  </committer>
</commit>
