From 18cc8b3a62eadbc1db928a61879b0579197501e5 Mon Sep 17 00:00:00 2001 From: Jan De Poorter Date: Tue, 3 Jun 2008 12:24:21 +0200 Subject: [PATCH] Added :validate to has_many, has_one, has_and_belongs_to_many associations, allowing you to disable the automatic validation of child-objects. --- activerecord/lib/active_record/associations.rb | 19 ++++++++++++------- .../associations/has_one_associations_test.rb | 9 ++++++++- activerecord/test/fixtures/relationships.yml | 3 +++ activerecord/test/models/relationship.rb | 20 ++++++++++++++++++++ activerecord/test/schema/schema.rb | 6 ++++++ 5 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 activerecord/test/fixtures/relationships.yml create mode 100644 activerecord/test/models/relationship.rb diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a3d1bbb..6be3b62 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -690,6 +690,7 @@ module ActiveRecord # association is a polymorphic +belongs_to+. # * :uniq - If true, duplicates will be omitted from the collection. Useful in conjunction with :through. # * :readonly - If true, all the associated objects are readonly through the association. + # * :validate - If false, don't validate the associated objects when saving the parent object. true by default. # # Option examples: # has_many :comments, :order => "posted_on" @@ -710,7 +711,7 @@ module ActiveRecord configure_dependency_for_has_many(reflection) - add_multiple_associated_save_callbacks(reflection.name) + add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false add_association_callbacks(reflection.name, reflection.options) if options[:through] @@ -769,6 +770,7 @@ module ActiveRecord # * :source_type - Specifies type of the source association used by has_one :through queries where the source # association is a polymorphic +belongs_to+. # * :readonly - If true, the associated object is readonly through the association. + # * :validate - If false, don't validate the associated object when saving the parent object. true by default. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -799,7 +801,7 @@ module ActiveRecord end after_save method_name - add_single_associated_save_callbacks(reflection.name) + add_single_associated_save_callbacks(reflection.name) unless options[:validate] == false association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) @@ -1025,6 +1027,7 @@ module ActiveRecord # * :select - By default, this is * as in SELECT * FROM, 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. # * :readonly - If true, all the associated objects are readonly through the association. + # * :validate - If false, don't validate the associated objects when saving the parent object. true by default. # # Option examples: # has_and_belongs_to_many :projects @@ -1037,7 +1040,7 @@ 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) + add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) # Don't use a before_destroy callback since users' before_destroy @@ -1343,7 +1346,8 @@ module ActiveRecord :uniq, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly + :extend, :readonly, + :validate ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1353,7 +1357,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 + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate ) create_reflection(:has_one, association_id, options, self) @@ -1361,7 +1365,7 @@ module ActiveRecord def create_has_one_through_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate ) create_reflection(:has_one, association_id, options, self) end @@ -1388,7 +1392,8 @@ module ActiveRecord :uniq, :finder_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly + :extend, :readonly, + :validate ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index abc7ee7..a69dfc5 100755 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -2,9 +2,10 @@ require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' +require 'models/relationship' class HasOneAssociationsTest < ActiveRecord::TestCase - fixtures :accounts, :companies, :developers, :projects, :developers_projects + fixtures :accounts, :companies, :developers, :projects, :developers_projects, :relationships def setup Account.destroyed_account_ids.clear @@ -319,5 +320,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! } assert companies(:first_firm).readonly_account.readonly? end + + def test_should_not_raise_system_stack_error_on_create + assert_nothing_raised do + Relationship.create!( :user_id => 2, :contact_id => 1 ) + end + end end diff --git a/activerecord/test/fixtures/relationships.yml b/activerecord/test/fixtures/relationships.yml new file mode 100644 index 0000000..053afb9 --- /dev/null +++ b/activerecord/test/fixtures/relationships.yml @@ -0,0 +1,3 @@ +one: + user_id: 1 + contact_id: 2 diff --git a/activerecord/test/models/relationship.rb b/activerecord/test/models/relationship.rb new file mode 100644 index 0000000..2fc5d74 --- /dev/null +++ b/activerecord/test/models/relationship.rb @@ -0,0 +1,20 @@ +class Relationship < ActiveRecord::Base + + has_one :inverse, :class_name => 'Relationship', :foreign_key => 'inverse_id', :dependent => :nullify, :validate => false + + def self.find_for( user_id, contact_id ) + Relationship.find( :first, :conditions => [ 'user_id = ? AND contact_id =?', user_id, contact_id ] ) + end + + def after_create + if !self.inverse + self.inverse = Relationship.find_for( contact_id, user_id ) + if self.inverse + self.save! + self.inverse.inverse = self + self.inverse.save! + end + end + end + +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 423929f..a186361 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -406,6 +406,12 @@ ActiveRecord::Schema.define do create_table :guids, :force => true do |t| t.column :key, :string end + + create_table :relationships, :force => true do |t| + t.column :user_id, :integer + t.column :contact_id, :integer + t.column :inverse_id, :integer + end except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk -- 1.5.6.rc0.29.g3beb5