aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG9
-rwxr-xr-xactiverecord/lib/active_record/validations.rb33
-rwxr-xr-xactiverecord/test/validations_test.rb21
3 files changed, 63 insertions, 0 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index d43e6a0537..ba5cf01178 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 1da73d6dc5..d4f75052d3 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -271,6 +271,39 @@ module ActiveRecord
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:
+ # * <tt>on</tt> 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 ed9d76bd3e..052cf3d6ca 100755
--- a/activerecord/test/validations_test.rb
+++ b/activerecord/test/validations_test.rb
@@ -389,6 +389,27 @@ class ValidationsTest < Test::Unit::TestCase
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?