<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,6 +1,8 @@
 *Edge*
 
-* Added ActiveRecord::Base#touch to update the updated_at/on attributes with the current time [DHH]
+* Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed [DHH]
+
+* Added ActiveRecord::Base#touch to update the updated_at/on attributes (or another specified timestamp) with the current time [DHH]
 
 
 *2.3.2 [Final] (March 15, 2009)*</diff>
      <filename>activerecord/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -981,6 +981,9 @@ module ActiveRecord
       #   If false, don't validate the associated objects when saving the parent object. +false+ by default.
       # [:autosave]
       #   If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
+      # [:touch]
+      #   If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
+      #   destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
       #
       # Option examples:
       #   belongs_to :firm, :foreign_key =&gt; &quot;client_of&quot;
@@ -990,6 +993,8 @@ module ActiveRecord
       #   belongs_to :attachable, :polymorphic =&gt; true
       #   belongs_to :project, :readonly =&gt; true
       #   belongs_to :post, :counter_cache =&gt; true
+      #   belongs_to :company, :touch =&gt; true
+      #   belongs_to :company, :touch =&gt; :employees_last_updated_at
       def belongs_to(association_id, options = {})
         reflection = create_belongs_to_reflection(association_id, options)
 
@@ -1001,28 +1006,8 @@ module ActiveRecord
           association_constructor_method(:create, reflection, BelongsToAssociation)
         end
 
-        # Create the callbacks to update counter cache
-        if options[:counter_cache]
-          cache_column = reflection.counter_cache_column
-
-          method_name = &quot;belongs_to_counter_cache_after_create_for_#{reflection.name}&quot;.to_sym
-          define_method(method_name) do
-            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(reflection.name)
-            association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
-          end
-          before_destroy method_name
-
-          module_eval(
-            &quot;#{reflection.class_name}.send(:attr_readonly,\&quot;#{cache_column}\&quot;.intern) if defined?(#{reflection.class_name}) &amp;&amp; #{reflection.class_name}.respond_to?(:attr_readonly)&quot;
-          )
-        end
+        add_counter_cache_callbacks(reflection)          if options[:counter_cache]
+        add_touch_callbacks(reflection, options[:touch]) if options[:touch]
 
         configure_dependency_for_belongs_to(reflection)
       end
@@ -1329,6 +1314,43 @@ module ActiveRecord
           end
         end
 
+        def add_counter_cache_callbacks(reflection)
+          cache_column = reflection.counter_cache_column
+
+          method_name = &quot;belongs_to_counter_cache_after_create_for_#{reflection.name}&quot;.to_sym
+          define_method(method_name) do
+            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(reflection.name)
+            association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
+          end
+          before_destroy(method_name)
+
+          module_eval(
+            &quot;#{reflection.class_name}.send(:attr_readonly,\&quot;#{cache_column}\&quot;.intern) if defined?(#{reflection.class_name}) &amp;&amp; #{reflection.class_name}.respond_to?(:attr_readonly)&quot;
+          )
+        end
+        
+        def add_touch_callbacks(reflection, touch_attribute)
+          method_name = &quot;belongs_to_touch_after_save_or_destroy_for_#{reflection.name}&quot;.to_sym
+          define_method(method_name) do
+            association = send(reflection.name)
+            
+            if touch_attribute == true
+              association.touch unless association.nil?
+            else
+              association.touch(touch_attribute) unless association.nil?
+            end
+          end
+          after_save(method_name)
+          after_destroy(method_name)
+        end
+
         def find_with_associations(options = {})
           catch :invalid_query do
             join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
@@ -1499,7 +1521,7 @@ module ActiveRecord
         @@valid_keys_for_belongs_to_association = [
           :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
           :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
-          :validate
+          :validate, :touch
         ]
 
         def create_belongs_to_reflection(association_id, options)</diff>
      <filename>activerecord/lib/active_record/associations.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,11 +18,21 @@ module ActiveRecord
     
     # Saves the record with the updated_at/on attributes set to the current time.
     # If the save fails because of validation errors, an ActiveRecord::RecordInvalid exception is raised.
-    def touch
+    # If an attribute name is passed, that attribute is used for the touch instead of the updated_at/on attributes.
+    #
+    # Examples:
+    #
+    #   product.touch               # updates updated_at
+    #   product.touch(:designed_at) # updates the designed_at attribute
+    def touch(attribute = nil)
       current_time = current_time_from_proper_timezone
 
-      write_attribute('updated_at', current_time) if respond_to?(:updated_at)
-      write_attribute('updated_on', current_time) if respond_to?(:updated_on)
+      if attribute
+        write_attribute(attribute, current_time)
+      else
+        write_attribute('updated_at', current_time) if respond_to?(:updated_at)
+        write_attribute('updated_on', current_time) if respond_to?(:updated_on)
+      end
 
       save!
     end</diff>
      <filename>activerecord/lib/active_record/timestamp.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,10 @@
 require 'cases/helper'
 require 'models/developer'
+require 'models/owner'
+require 'models/pet'
 
 class TimestampTest &lt; ActiveRecord::TestCase
-  fixtures :developers
+  fixtures :developers, :owners, :pets
 
   def setup
     @developer = Developer.first
@@ -27,4 +29,47 @@ class TimestampTest &lt; ActiveRecord::TestCase
     
     assert @previously_updated_at != @developer.updated_at
   end
+  
+  def test_touching_a_different_attribute
+    previously_created_at = @developer.created_at
+    @developer.touch(:created_at)
+
+    assert previously_created_at != @developer.created_at
+  end
+  
+  def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
+    pet   = Pet.first
+    owner = pet.owner
+    previously_owner_updated_at = owner.updated_at
+    
+    pet.name = &quot;Fluffy the Third&quot;
+    pet.save
+    
+    assert previously_owner_updated_at != pet.owner.updated_at
+  end
+
+  def test_destroying_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
+    pet   = Pet.first
+    owner = pet.owner
+    previously_owner_updated_at = owner.updated_at
+    
+    pet.destroy
+    
+    assert previously_owner_updated_at != pet.owner.updated_at
+  end
+  
+  def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
+    Pet.belongs_to :owner, :touch =&gt; :happy_at
+
+    pet   = Pet.first
+    owner = pet.owner
+    previously_owner_happy_at = owner.happy_at
+    
+    pet.name = &quot;Fluffy the Third&quot;
+    pet.save
+    
+    assert previously_owner_happy_at != pet.owner.happy_at
+  ensure
+    Pet.belongs_to :owner, :touch =&gt; true
+  end
 end
\ No newline at end of file</diff>
      <filename>activerecord/test/cases/timestamp_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 class Pet &lt; ActiveRecord::Base
   set_primary_key :pet_id
-  belongs_to :owner
+  belongs_to :owner, :touch =&gt; true
   has_many :toys
 end</diff>
      <filename>activerecord/test/models/pet.rb</filename>
    </modified>
    <modified>
      <diff>@@ -281,6 +281,8 @@ ActiveRecord::Schema.define do
 
   create_table :owners, :primary_key =&gt; :owner_id ,:force =&gt; true do |t|
     t.string :name
+    t.column :updated_at, :datetime
+    t.column :happy_at,   :datetime
   end
 
 </diff>
      <filename>activerecord/test/schema/schema.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>fdb61f02c54bda0ad5ff6d0259209113202b9307</id>
    </parent>
  </parents>
  <author>
    <name>David Heinemeier Hansson</name>
    <login>dhh</login>
    <email>david@loudthinking.com</email>
  </author>
  <url>http://github.com/rails/rails/commit/abb899c54e8777428b7a607774370ba29a5573bd</url>
  <id>abb899c54e8777428b7a607774370ba29a5573bd</id>
  <committed-date>2009-04-16T15:25:55-07:00</committed-date>
  <authored-date>2009-04-16T15:25:55-07:00</authored-date>
  <message>Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed [DHH]</message>
  <tree>7fe740e4df2db5f96900e1d536db733da8edd42f</tree>
  <committer>
    <name>David Heinemeier Hansson</name>
    <login>dhh</login>
    <email>david@loudthinking.com</email>
  </committer>
</commit>
