diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index ac71b9d1dcddad1a2223c79be077e84d6b871eb9..014500fa28b8b5d19cb1d47598ac0a07e9a6e733 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,4 +1,23 @@ ## Rails 4.0.0 (unreleased) ## +* Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the + absence of attributes. + + class Person < ActiveRecord::Base + validates_absence_of :first_name + end + + person = Person.new + person.first_name = "John" + person.valid? + => false + # first_name must be blank + + * Roberto Vasquez Angel* + +* Added `ActiveModel::Errors#add_on_present` method. Adds error messages to + present attributes. + + *Roberto Vasquez Angel* * `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!` diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 963e52bff3544a4f6f670ee82c9583efb1e7edd1..b713e99e25ad32b23dc465c6bb4e748e4b8926e1 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -328,6 +328,19 @@ def add_on_blank(attributes, options = {}) end end + # Will add an error message to each of the attributes in +attributes+ that + # is present (using Object#present?). + # + # person.errors.add_on_present(:name) + # person.errors.messages + # # => { :name => ["must be blank"] } + def add_on_present(attributes, options = {}) + Array(attributes).flatten.each do |attribute| + value = @base.send(:read_attribute_for_validation, attribute) + add(attribute, :not_blank, options) if value.present? + end + end + # Returns +true+ if an error on the attribute with the given message is # present, +false+ otherwise. +message+ is treated the same as for +add+. # diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index d17848c861f56c827ab91519bd2568c342bf4705..8ea34b84f5fc04e306421ac77779e9efe6a2d45b 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -13,6 +13,7 @@ en: accepted: "must be accepted" empty: "can't be empty" blank: "can't be blank" + not_blank: "must be blank" too_long: "is too long (maximum is %{count} characters)" too_short: "is too short (minimum is %{count} characters)" wrong_length: "is the wrong length (should be %{count} characters)" diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb new file mode 100644 index 0000000000000000000000000000000000000000..6790554907c82057f2272b6843331fab09cb33b8 --- /dev/null +++ b/activemodel/lib/active_model/validations/absence.rb @@ -0,0 +1,31 @@ +module ActiveModel + module Validations + # == Active Model Absence Validator + class AbsenceValidator < EachValidator #:nodoc: + def validate(record) + record.errors.add_on_present(attributes, options) + end + end + + module HelperMethods + # Validates that the specified attributes are blank (as defined by + # Object#blank?). Happens by default on save. + # + # class Person < ActiveRecord::Base + # validates_absence_of :first_name + # end + # + # The first_name attribute must be in the object and it must be blank. + # + # Configuration options: + # * :message - A custom error message (default is: "must be blank"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See ActiveModel::Validation#validates for more information + def validates_absence_of(*attr_names) + validates_with AbsenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..c05d71de5a318aeda41a61ef60b1403882e85b09 --- /dev/null +++ b/activemodel/test/cases/validations/absence_validation_test.rb @@ -0,0 +1,67 @@ +# encoding: utf-8 +require 'cases/helper' +require 'models/topic' +require 'models/person' +require 'models/custom_reader' + +class AbsenceValidationTest < ActiveModel::TestCase + teardown do + Topic.reset_callbacks(:validate) + Person.reset_callbacks(:validate) + CustomReader.reset_callbacks(:validate) + end + + def test_validate_absences + Topic.validates_absence_of(:title, :content) + t = Topic.new + t.title = "foo" + t.content = "bar" + assert t.invalid? + assert_equal ["must be blank"], t.errors[:title] + assert_equal ["must be blank"], t.errors[:content] + t.title = "" + t.content = "something" + assert t.invalid? + assert_equal ["must be blank"], t.errors[:content] + t.content = "" + assert t.valid? + end + + def test_accepts_array_arguments + Topic.validates_absence_of %w(title content) + t = Topic.new + t.title = "foo" + t.content = "bar" + assert t.invalid? + assert_equal ["must be blank"], t.errors[:title] + assert_equal ["must be blank"], t.errors[:content] + end + + def test_validates_acceptance_of_with_custom_error_using_quotes + Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes" + p = Person.new + p.karma = "good" + assert p.invalid? + assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last + end + + def test_validates_absence_of_for_ruby_class + Person.validates_absence_of :karma + p = Person.new + p.karma = "good" + assert p.invalid? + assert_equal ["must be blank"], p.errors[:karma] + p.karma = nil + assert p.valid? + end + + def test_validates_absence_of_for_ruby_class_with_custom_reader + CustomReader.validates_absence_of :karma + p = CustomReader.new + p[:karma] = "excellent" + assert p.invalid? + assert_equal ["must be blank"], p.errors[:karma] + p[:karma] = "" + assert p.valid? + end +end