Skip to content
This repository
Browse code

Add :validate option to associations. [#301 state:resolved]

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
  • Loading branch information...
commit 7f140bbddaf70abc61570f6cfdcbfba5771ffc78 1 parent f728e57
Jan De Poorter authored June 11, 2008 lifo committed June 11, 2008
2  activerecord/CHANGELOG
... ...
@@ -1,5 +1,7 @@
1 1
 *Edge*
2 2
 
  3
+* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
  4
+
3 5
 * PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later.  [Jeremy Kemper]
4 6
 
5 7
 * Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
24  activerecord/lib/active_record/associations.rb
@@ -690,6 +690,7 @@ module ClassMethods
690 690
       #   association is a polymorphic +belongs_to+.
691 691
       # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
692 692
       # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
  693
+      # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
693 694
       #
694 695
       # Option examples:
695 696
       #   has_many :comments, :order => "posted_on"
@@ -710,7 +711,7 @@ def has_many(association_id, options = {}, &extension)
710 711
 
711 712
         configure_dependency_for_has_many(reflection)
712 713
 
713  
-        add_multiple_associated_save_callbacks(reflection.name)
  714
+        add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
714 715
         add_association_callbacks(reflection.name, reflection.options)
715 716
 
716 717
         if options[:through]
@@ -769,6 +770,7 @@ def has_many(association_id, options = {}, &extension)
769 770
       # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
770 771
       #   association is a polymorphic +belongs_to+.      
771 772
       # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
  773
+      # * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
772 774
       #
773 775
       # Option examples:
774 776
       #   has_one :credit_card, :dependent => :destroy  # destroys the associated credit card
@@ -799,7 +801,7 @@ def has_one(association_id, options = {})
799 801
           end
800 802
           after_save method_name
801 803
 
802  
-          add_single_associated_save_callbacks(reflection.name) 
  804
+          add_single_associated_save_callbacks(reflection.name) if options[:validate] == true
803 805
           association_accessor_methods(reflection, HasOneAssociation)
804 806
           association_constructor_method(:build,  reflection, HasOneAssociation)
805 807
           association_constructor_method(:create, reflection, HasOneAssociation)
@@ -857,6 +859,7 @@ def has_one(association_id, options = {})
857 859
       #   Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
858 860
       #   to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
859 861
       # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
  862
+      # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
860 863
       #
861 864
       # Option examples:
862 865
       #   belongs_to :firm, :foreign_key => "client_of"
@@ -937,6 +940,8 @@ def belongs_to(association_id, options = {})
937 940
           )
938 941
         end
939 942
 
  943
+        add_single_associated_save_callbacks(reflection.name) unless options[:validate] == false
  944
+
940 945
         configure_dependency_for_belongs_to(reflection)
941 946
       end
942 947
 
@@ -1025,6 +1030,7 @@ def belongs_to(association_id, options = {})
1025 1030
       # * <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
1026 1031
       #   but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
1027 1032
       # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
  1033
+      # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
1028 1034
       #
1029 1035
       # Option examples:
1030 1036
       #   has_and_belongs_to_many :projects
@@ -1037,7 +1043,7 @@ def belongs_to(association_id, options = {})
1037 1043
       def has_and_belongs_to_many(association_id, options = {}, &extension)
1038 1044
         reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1039 1045
 
1040  
-        add_multiple_associated_save_callbacks(reflection.name)
  1046
+        add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
1041 1047
         collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
1042 1048
 
1043 1049
         # Don't use a before_destroy callback since users' before_destroy
@@ -1343,7 +1349,8 @@ def create_has_many_reflection(association_id, options, &extension)
1343 1349
             :uniq,
1344 1350
             :finder_sql, :counter_sql,
1345 1351
             :before_add, :after_add, :before_remove, :after_remove,
1346  
-            :extend, :readonly
  1352
+            :extend, :readonly,
  1353
+            :validate
1347 1354
           )
1348 1355
 
1349 1356
           options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1353,7 +1360,7 @@ def create_has_many_reflection(association_id, options, &extension)
1353 1360
 
1354 1361
         def create_has_one_reflection(association_id, options)
1355 1362
           options.assert_valid_keys(
1356  
-            :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
  1363
+            :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
1357 1364
           )
1358 1365
 
1359 1366
           create_reflection(:has_one, association_id, options, self)
@@ -1361,7 +1368,7 @@ def create_has_one_reflection(association_id, options)
1361 1368
         
1362 1369
         def create_has_one_through_reflection(association_id, options)
1363 1370
           options.assert_valid_keys(
1364  
-            :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
  1371
+            :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
1365 1372
           )
1366 1373
           create_reflection(:has_one, association_id, options, self)
1367 1374
         end
@@ -1369,7 +1376,7 @@ def create_has_one_through_reflection(association_id, options)
1369 1376
         def create_belongs_to_reflection(association_id, options)
1370 1377
           options.assert_valid_keys(
1371 1378
             :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
1372  
-            :counter_cache, :extend, :polymorphic, :readonly
  1379
+            :counter_cache, :extend, :polymorphic, :readonly, :validate
1373 1380
           )
1374 1381
 
1375 1382
           reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1388,7 +1395,8 @@ def create_has_and_belongs_to_many_reflection(association_id, options, &extensio
1388 1395
             :uniq,
1389 1396
             :finder_sql, :delete_sql, :insert_sql,
1390 1397
             :before_add, :after_add, :before_remove, :after_remove,
1391  
-            :extend, :readonly
  1398
+            :extend, :readonly,
  1399
+            :validate
1392 1400
           )
1393 1401
 
1394 1402
           options[:extend] = create_extension_modules(association_id, extension, options[:extend])
19  activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -409,4 +409,23 @@ def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_recor
409 409
     sponsor.sponsorable = new_member
410 410
     assert_equal nil, sponsor.sponsorable_id
411 411
   end
  412
+
  413
+  def test_save_fails_for_invalid_belongs_to
  414
+    assert log = AuditLog.create(:developer_id=>0,:message=>"")
  415
+
  416
+    log.developer = Developer.new
  417
+    assert !log.developer.valid?
  418
+    assert !log.valid?
  419
+    assert !log.save
  420
+    assert_equal "is invalid", log.errors.on("developer")
  421
+  end
  422
+
  423
+  def test_save_succeeds_for_invalid_belongs_to_with_validate_false
  424
+    assert log = AuditLog.create(:developer_id=>0,:message=>"")
  425
+
  426
+    log.unvalidated_developer = Developer.new
  427
+    assert !log.unvalidated_developer.valid?
  428
+    assert log.valid?
  429
+    assert log.save
  430
+  end
412 431
 end
11  activerecord/test/cases/associations/has_many_associations_test.rb
@@ -342,6 +342,17 @@ def test_invalid_adding_before_save
342 342
     assert new_firm.new_record?
343 343
   end
344 344
 
  345
+  def test_invalid_adding_with_validate_false
  346
+    firm = Firm.find(:first)
  347
+    client = Client.new
  348
+    firm.unvalidated_clients_of_firm << Client.new
  349
+
  350
+    assert firm.valid?
  351
+    assert !client.valid?
  352
+    assert firm.save
  353
+    assert client.new_record?
  354
+  end
  355
+
345 356
   def test_build
346 357
     company = companies(:first_firm)
347 358
     new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
12  activerecord/test/cases/associations/has_one_associations_test.rb
@@ -275,6 +275,18 @@ def test_save_fails_for_invalid_has_one
275 275
     assert_equal "is invalid", firm.errors.on("account")
276 276
   end
277 277
 
  278
+
  279
+  def test_save_succeeds_for_invalid_has_one_with_validate_false
  280
+    firm = Firm.find(:first)
  281
+    assert firm.valid?
  282
+
  283
+    firm.unvalidated_account = Account.new
  284
+
  285
+    assert !firm.unvalidated_account.valid?
  286
+    assert firm.valid?
  287
+    assert firm.save
  288
+  end
  289
+
278 290
   def test_assignment_before_either_saved
279 291
     firm = Firm.new("name" => "GlobalMegaCorp")
280 292
     firm.account = a = Account.new("credit_limit" => 1000)
6  activerecord/test/cases/reflection_test.rb
@@ -160,9 +160,9 @@ def test_association_reflection_in_modules
160 160
 
161 161
   def test_reflection_of_all_associations
162 162
     # FIXME these assertions bust a lot
163  
-    assert_equal 20, Firm.reflect_on_all_associations.size
164  
-    assert_equal 16, Firm.reflect_on_all_associations(:has_many).size
165  
-    assert_equal 4, Firm.reflect_on_all_associations(:has_one).size
  163
+    assert_equal 22, Firm.reflect_on_all_associations.size
  164
+    assert_equal 17, Firm.reflect_on_all_associations(:has_many).size
  165
+    assert_equal 5, Firm.reflect_on_all_associations(:has_one).size
166 166
     assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
167 167
   end
168 168
 
4  activerecord/test/models/company.rb
@@ -26,6 +26,7 @@ class Firm < Company
26 26
       "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
27 27
   has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
28 28
   has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
  29
+  has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
29 30
   has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
30 31
   has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
31 32
   has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1
@@ -46,7 +47,8 @@ class Firm < Company
46 47
   has_many :plain_clients, :class_name => 'Client'
47 48
   has_many :readonly_clients, :class_name => 'Client', :readonly => true
48 49
 
49  
-  has_one :account, :foreign_key => "firm_id", :dependent => :destroy
  50
+  has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true
  51
+  has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
50 52
   has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account'
51 53
   has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
52 54
 end
1  activerecord/test/models/developer.rb
@@ -57,6 +57,7 @@ def log=(message)
57 57
 
58 58
 class AuditLog < ActiveRecord::Base
59 59
   belongs_to :developer
  60
+  belongs_to :unvalidated_developer, :class_name => 'Developer', :validate => false
60 61
 end
61 62
 
62 63
 DeveloperSalary = Struct.new(:amount)

0 notes on commit 7f140bb

Please sign in to comment.
Something went wrong with that request. Please try again.