public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Search Repo:
Add :validate option to associations. [#301 state:resolved]

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

Comments

    No one has commented yet.