aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/validations.rb1
-rw-r--r--activerecord/lib/active_record/validations/presence.rb64
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb44
4 files changed, 116 insertions, 0 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a965fe0494..f41ccfe56f 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -31,6 +31,13 @@
*kennyj*
+* Changed validates_presence_of on an association so that children objects
+ do not validate as being present if they are marked for destruction. This
+ prevents you from saving the parent successfully and thus putting the parent
+ in an invalid state.
+
+ *Nick Monje & Brent Wheeldon*
+
* `FinderMethods#exists?` now returns `false` with the `false` argument.
*Egor Lynko*
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index d06020b3ce..cef2bbd563 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -81,3 +81,4 @@ end
require "active_record/validations/associated"
require "active_record/validations/uniqueness"
+require "active_record/validations/presence"
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
new file mode 100644
index 0000000000..056527b512
--- /dev/null
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -0,0 +1,64 @@
+module ActiveRecord
+ module Validations
+ class PresenceValidator < ActiveModel::Validations::PresenceValidator
+ def validate(record)
+ super
+ attributes.each do |attribute|
+ next unless record.class.reflect_on_association(attribute)
+ value = record.send(attribute)
+ if Array(value).all? { |r| r.marked_for_destruction? }
+ record.errors.add(attribute, :blank, options)
+ end
+ end
+ end
+ end
+
+ module ClassMethods
+ # Validates that the specified attributes are not blank (as defined by
+ # Object#blank?), and, if the attribute is an association, that the
+ # associated object is not marked for destruction. Happens by default
+ # on save.
+ #
+ # class Person < ActiveRecord::Base
+ # has_one :face
+ # validates_presence_of :face
+ # end
+ #
+ # The face attribute must be in the object and it cannot be blank or marked
+ # for destruction.
+ #
+ # If you want to validate the presence of a boolean field (where the real values
+ # are true and false), you will want to use
+ # <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
+ #
+ # This is due to the way Object#blank? handles boolean values:
+ # <tt>false.blank? # => true</tt>.
+ #
+ # This validator defers to the ActiveModel validation for presence, adding the
+ # check to see that an associated object is not marked for destruction. This
+ # prevents the parent object from validating successfully and saving, which then
+ # deletes the associated object, thus putting the parent object into an invalid
+ # state.
+ #
+ # Configuration options:
+ # * <tt>:message</tt> - A custom error message (default is: "can't be blank").
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ def validates_presence_of(*attr_names)
+ validates_with PresenceValidator, _merge_attributes(attr_names)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
new file mode 100644
index 0000000000..cd9175f454
--- /dev/null
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+require "cases/helper"
+require 'models/man'
+require 'models/face'
+require 'models/interest'
+
+class PresenceValidationTest < ActiveRecord::TestCase
+ class Boy < Man; end
+
+ repair_validations(Boy)
+
+ def test_validates_presence_of_non_association
+ Boy.validates_presence_of(:name)
+ b = Boy.new
+ assert b.invalid?
+
+ b.name = "Alex"
+ assert b.valid?
+ end
+
+ def test_validates_presence_of_has_one_marked_for_destruction
+ Boy.validates_presence_of(:face)
+ b = Boy.new
+ f = Face.new
+ b.face = f
+ assert b.valid?
+
+ f.mark_for_destruction
+ assert b.invalid?
+ end
+
+ def test_validates_presence_of_has_many_marked_for_destruction
+ Boy.validates_presence_of(:interests)
+ b = Boy.new
+ b.interests << [i1 = Interest.new, i2 = Interest.new]
+ assert b.valid?
+
+ i1.mark_for_destruction
+ assert b.valid?
+
+ i2.mark_for_destruction
+ assert b.invalid?
+ end
+end