diff options
Diffstat (limited to 'activerecord/lib/active_record/associations.rb')
-rwxr-xr-x | activerecord/lib/active_record/associations.rb | 56 |
1 files changed, 37 insertions, 19 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5f42b5a459..7ad7802cbc 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -663,6 +663,7 @@ module ActiveRecord # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id" # as the default <tt>:foreign_key</tt>. + # * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+. # * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated @@ -678,7 +679,7 @@ module ActiveRecord # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned. # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, 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. + # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>). # * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt> @@ -691,6 +692,7 @@ module ActiveRecord # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>. # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default. + # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>). # # Option examples: # has_many :comments, :order => "posted_on" @@ -711,7 +713,8 @@ module ActiveRecord configure_dependency_for_has_many(reflection) - add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false + 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) if options[:through] @@ -757,6 +760,7 @@ module ActiveRecord # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id" # as the default <tt>:foreign_key</tt>. + # * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+. # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded. # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>). # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join @@ -771,6 +775,7 @@ module ActiveRecord # association is a polymorphic +belongs_to+. # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. # * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default. + # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>). # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -801,7 +806,7 @@ module ActiveRecord end after_save method_name - add_single_associated_save_callbacks(reflection.name) if options[:validate] == true + add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) @@ -860,6 +865,7 @@ module ActiveRecord # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default. + # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>). # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -940,7 +946,7 @@ module ActiveRecord ) end - add_single_associated_save_callbacks(reflection.name) if options[:validate] == true + add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true configure_dependency_for_belongs_to(reflection) end @@ -1031,6 +1037,7 @@ module ActiveRecord # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default. + # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>). # # Option examples: # has_and_belongs_to_many :projects @@ -1043,7 +1050,8 @@ module ActiveRecord def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) - add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false + add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false + add_multiple_associated_save_callbacks(reflection.name) collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) # Don't use a before_destroy callback since users' before_destroy @@ -1105,6 +1113,8 @@ module ActiveRecord association = association_proxy_class.new(self, reflection) end + new_value = reflection.klass.new(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash) + if association_proxy_class == HasOneThroughAssociation association.create_through_record(new_value) self.send(reflection.name, new_value) @@ -1141,7 +1151,7 @@ module ActiveRecord end define_method("#{reflection.name.to_s.singularize}_ids") do - send(reflection.name).map(&:id) + send(reflection.name).map { |record| record.id } end end @@ -1163,7 +1173,7 @@ module ActiveRecord end end - def add_single_associated_save_callbacks(association_name) + def add_single_associated_validation_callbacks(association_name) method_name = "validate_associated_records_for_#{association_name}".to_sym define_method(method_name) do association = instance_variable_get("@#{association_name}") @@ -1175,7 +1185,7 @@ module ActiveRecord validate method_name end - def add_multiple_associated_save_callbacks(association_name) + def add_multiple_associated_validation_callbacks(association_name) method_name = "validate_associated_records_for_#{association_name}".to_sym ivar = "@#{association_name}" @@ -1196,6 +1206,10 @@ module ActiveRecord end validate method_name + end + + def add_multiple_associated_save_callbacks(association_name) + ivar = "@#{association_name}" method_name = "before_save_associated_records_for_#{association_name}".to_sym define_method(method_name) do @@ -1217,7 +1231,6 @@ module ActiveRecord else [] end - records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank? # reconstruct the SQL queries now that we know the owner's id @@ -1342,7 +1355,7 @@ module ActiveRecord def create_has_many_reflection(association_id, options, &extension) options.assert_valid_keys( - :class_name, :table_name, :foreign_key, + :class_name, :table_name, :foreign_key, :primary_key, :dependent, :select, :conditions, :include, :order, :group, :limit, :offset, :as, :through, :source, :source_type, @@ -1350,7 +1363,7 @@ module ActiveRecord :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, :extend, :readonly, - :validate + :validate, :accessible ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1360,7 +1373,7 @@ module ActiveRecord def create_has_one_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key, :accessible ) create_reflection(:has_one, association_id, options, self) @@ -1376,7 +1389,7 @@ module ActiveRecord 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 + :counter_cache, :extend, :polymorphic, :readonly, :validate, :accessible ) reflection = create_reflection(:belongs_to, association_id, options, self) @@ -1396,7 +1409,7 @@ module ActiveRecord :finder_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, :extend, :readonly, - :validate + :validate, :accessible ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1473,25 +1486,30 @@ module ActiveRecord join_dependency.joins_for_table_name(table) }.flatten.compact.uniq + order = options[:order] + if scoped_order = (scope && scope[:order]) + order = order ? "#{order}, #{scoped_order}" : scoped_order + end + is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order) sql = "SELECT " if is_distinct - sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order]) + sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order) else sql << primary_key end sql << " FROM #{connection.quote_table_name table_name} " if is_distinct - sql << distinct_join_associations.collect(&:association_join).join + sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join add_joins!(sql, options, scope) end add_conditions!(sql, options[:conditions], scope) add_group!(sql, options[:group], scope) - if options[:order] && is_distinct - connection.add_order_by_for_association_limiting!(sql, options) + if order && is_distinct + connection.add_order_by_for_association_limiting!(sql, :order => order) else add_order!(sql, options[:order], scope) end @@ -1514,7 +1532,7 @@ module ActiveRecord end def order_tables(options) - order = options[:order] + order = [options[:order], scope(:find, :order) ].join(", ") return [] unless order && order.is_a?(String) order.scan(/([\.\w]+).?\./).flatten end |