From 51390b8524a644aa8655691fc7bf56e2a174d140 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 15 Jan 2005 17:52:08 +0000 Subject: [PATCH] Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@418 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 9 +++++ activerecord/lib/active_record/validations.rb | 33 +++++++++++++++++++ activerecord/test/validations_test.rb | 21 ++++++++++++ 3 files changed, 63 insertions(+) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index d43e6a0537c96..ba5cf01178639 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,14 @@ *SVN* +* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example: + + class Book < ActiveRecord::Base + has_many :pages + belongs_to :library + + validates_associated :pages, :library + end + * Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition: == Unsaved objects and associations diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 1da73d6dc56e6..d4f75052d38d6 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -271,6 +271,39 @@ def validates_inclusion_of(*attr_names) end end end + + # Validates whether the associated object or objects are all themselves valid. Works with any kind of assocation. + # + # class Book < ActiveRecord::Base + # has_many :pages + # belongs_to :library + # + # validates_associated :pages, :library + # end + # + # Warning: If, after the above definition, you then wrote: + # + # class Page < ActiveRecord::Base + # belongs_to :book + # + # validates_associated :book + # end + # + # this would specify a circular dependency and cause infinite recursion. The Rails team recommends against this practice. + # + # Configuration options: + # * on Specifies when this validation is active (default is :save, other options :create, :update) + def validates_associated(*attr_names) + configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save } + configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) + + for attr_name in attr_names + class_eval(%(#{validation_method(configuration[:on])} %{ + errors.add("#{attr_name}", "#{configuration[:message]}") unless + (#{attr_name}.is_a?(Array) ? #{attr_name} : [#{attr_name}]).inject(true){ |memo, record| memo and (record.nil? or record.valid?) } + })) + end + end private diff --git a/activerecord/test/validations_test.rb b/activerecord/test/validations_test.rb index ed9d76bd3e68f..052cf3d6ca5cf 100755 --- a/activerecord/test/validations_test.rb +++ b/activerecord/test/validations_test.rb @@ -389,6 +389,27 @@ def test_validates_length_of_custom_errors_for_is_with_wrong_length assert_equal "hoo 5", t.errors["title"] end + def test_validates_associated_many + Topic.validates_associated( :replies ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + t.replies << [r = Reply.create("title" => "A reply"), Reply.create("title" => "Another reply", "content" => "with content!")] + assert !t.valid? + assert t.errors.on(:replies) + r.content = "non-empty" + assert t.valid? + end + + def test_validates_associated_one + Reply.validates_associated( :topic ) + Topic.validates_presence_of( :content ) + r = Reply.create("title" => "A reply", "content" => "with content!") + r.topic = Topic.create("title" => "uhohuhoh") + assert !r.valid? + assert r.errors.on(:topic) + r.topic.content = "non-empty" + assert r.valid? + end + def test_throw_away_typing d = Developer.create "name" => "David", "salary" => "100,000" assert !d.valid?