From abb899c54e8777428b7a607774370ba29a5573bd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 16 Apr 2009 17:25:55 -0500 Subject: Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed [DHH] --- activerecord/lib/active_record/associations.rb | 68 +++++++++++++++++--------- 1 file changed, 45 insertions(+), 23 deletions(-) (limited to 'activerecord/lib/active_record/associations.rb') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 6d25b36aea..53a710537f 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -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 => "client_of" @@ -990,6 +993,8 @@ module ActiveRecord # belongs_to :attachable, :polymorphic => true # belongs_to :project, :readonly => true # belongs_to :post, :counter_cache => true + # belongs_to :company, :touch => true + # belongs_to :company, :touch => :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 = "belongs_to_counter_cache_after_create_for_#{reflection.name}".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 = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".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( - "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)" - ) - 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 = "belongs_to_counter_cache_after_create_for_#{reflection.name}".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 = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".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( + "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)" + ) + end + + def add_touch_callbacks(reflection, touch_attribute) + method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".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) -- cgit v1.2.3 From 489abfd3b23f3c4b3de86aeb3bde3970771c001b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 20 Apr 2009 13:51:11 +0100 Subject: Ensure JoinAssociation uses aliased table name when multiple associations have hash conditions on the same table --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/associations.rb') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 53a710537f..fa18822c24 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -2123,7 +2123,7 @@ module ActiveRecord klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record? [through_reflection, reflection].each do |ref| - join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions] + join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions] end join -- cgit v1.2.3 From de0ea3866370ec61581f910cf393a3cc97eba32f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 20 Apr 2009 18:12:40 +0100 Subject: Ensure :dependent => :delete_all works for association with hash conditions --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/associations.rb') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index fa18822c24..2115878e32 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1375,7 +1375,7 @@ module ActiveRecord dependent_conditions = [] dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] - dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions] + dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions] dependent_conditions << extra_conditions if extra_conditions dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") dependent_conditions = dependent_conditions.gsub('@', '\@') -- cgit v1.2.3