mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Add validates_absence_of matcher
This commit is contained in:
parent
aff7bd7e2a
commit
2cdae242ca
5 changed files with 253 additions and 1 deletions
2
NEWS.md
2
NEWS.md
|
@ -21,6 +21,8 @@
|
|||
* You can now test that your `has_many :through` or `has_one :through`
|
||||
associations are defined with a `:source` option.
|
||||
|
||||
* Add `validates_absence_of` matcher.
|
||||
|
||||
# v 2.4.0
|
||||
|
||||
* Fix a bug with the `validate_numericality_of` matcher that would not allow the
|
||||
|
|
35
README.md
35
README.md
|
@ -38,7 +38,7 @@ Different matchers apply to different parts of Rails:
|
|||
|
||||
### ActiveModel Matchers
|
||||
|
||||
*Jump to: [allow_mass_assignment_of](#allow_mass_assignment_of), [allow_value / disallow_value](#allow_value--disallow_value), [ensure_inclusion_of](#ensure_inclusion_of), [ensure_exclusion_of](#ensure_exclusion_of), [ensure_length_of](#ensure_length_of), [have_secure_password](#have_secure_password), [validate_acceptance_of](#validate_acceptance_of), [validate_confirmation_of](#validate_confirmation_of), [validate_numericality_of](#validate_numericality_of), [validate_presence_of](#validate_presence_of), [validate_uniqueness_of](#validate_uniqueness_of)*
|
||||
*Jump to: [allow_mass_assignment_of](#allow_mass_assignment_of), [allow_value / disallow_value](#allow_value--disallow_value), [ensure_inclusion_of](#ensure_inclusion_of), [ensure_exclusion_of](#ensure_exclusion_of), [ensure_length_of](#ensure_length_of), [have_secure_password](#have_secure_password), [validate_absence_of](#validate_absence_of), [validate_acceptance_of](#validate_acceptance_of), [validate_confirmation_of](#validate_confirmation_of), [validate_numericality_of](#validate_numericality_of), [validate_presence_of](#validate_presence_of), [validate_uniqueness_of](#validate_uniqueness_of)*
|
||||
|
||||
Note that all of the examples in this section are based on an ActiveRecord
|
||||
model for simplicity, but these matchers will work just as well using an
|
||||
|
@ -312,6 +312,39 @@ class UserTest < ActiveSupport::TestCase
|
|||
end
|
||||
```
|
||||
|
||||
#### validate_absence_of
|
||||
|
||||
The `validate_absence_of` matcher tests the usage of the
|
||||
`validates_absence_of` validation.
|
||||
|
||||
```ruby
|
||||
class Tank
|
||||
include ActiveModel::Model
|
||||
|
||||
validates_absence_of :arms
|
||||
validates_absence_of :legs,
|
||||
message: "Tanks don't have legs."
|
||||
end
|
||||
|
||||
# RSpec
|
||||
describe Tank do
|
||||
it { should validate_absence_of(:arms) }
|
||||
|
||||
it do
|
||||
should validate_absence_of(:legs).
|
||||
with_message("Tanks don't have legs.")
|
||||
end
|
||||
end
|
||||
|
||||
# Test::Unit
|
||||
class TankTest < ActiveSupport::TestCase
|
||||
should validate_absence_of(:arms)
|
||||
|
||||
should validate_absence_of(:legs).
|
||||
with_message("Tanks don't have legs.")
|
||||
end
|
||||
```
|
||||
|
||||
#### validate_acceptance_of
|
||||
|
||||
The `validate_acceptance_of` matcher tests usage of the
|
||||
|
|
|
@ -7,6 +7,7 @@ require 'shoulda/matchers/active_model/disallow_value_matcher'
|
|||
require 'shoulda/matchers/active_model/ensure_length_of_matcher'
|
||||
require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
|
||||
require 'shoulda/matchers/active_model/ensure_exclusion_of_matcher'
|
||||
require 'shoulda/matchers/active_model/validate_absence_of_matcher'
|
||||
require 'shoulda/matchers/active_model/validate_presence_of_matcher'
|
||||
require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
|
||||
require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveModel # :nodoc:
|
||||
|
||||
# Ensures that the model is not valid if the given attribute is present.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
|
||||
# Defaults to the translation for <tt>:present</tt>.
|
||||
#
|
||||
# Examples:
|
||||
# it { should validate_absence_of(:name) }
|
||||
# it { should validate_absence_of(:name).
|
||||
# with_message(/may not be set/) }
|
||||
def validate_absence_of(attr)
|
||||
ValidateAbsenceOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateAbsenceOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :present
|
||||
disallows_value_of(value, @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"require #{@attribute} to not be set"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def value
|
||||
if reflection
|
||||
obj = reflection.klass.new
|
||||
if collection?
|
||||
[ obj ]
|
||||
else
|
||||
obj
|
||||
end
|
||||
elsif attribute_class == Fixnum
|
||||
1
|
||||
elsif !attribute_class || attribute_class == String
|
||||
'an arbitrary value'
|
||||
else
|
||||
attribute_class.new
|
||||
end
|
||||
end
|
||||
|
||||
def attribute_class
|
||||
@subject.class.respond_to?(:columns_hash) &&
|
||||
@subject.class.columns_hash[@attribute].respond_to?(:klass) &&
|
||||
@subject.class.columns_hash[@attribute].klass
|
||||
end
|
||||
|
||||
def collection?
|
||||
if reflection
|
||||
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def reflection
|
||||
@subject.class.respond_to?(:reflect_on_association) &&
|
||||
@subject.class.reflect_on_association(@attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,139 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher do
|
||||
if active_model_4_0?
|
||||
context 'a model with an absence validation' do
|
||||
it 'accepts' do
|
||||
validating_absence_of(:attr).should validate_absence_of(:attr)
|
||||
end
|
||||
|
||||
it 'does not override the default message with a present' do
|
||||
validating_absence_of(:attr).should validate_absence_of(:attr).with_message(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a model without an absence validation' do
|
||||
it 'rejects' do
|
||||
model = define_model(:example, attr: :string).new
|
||||
model.should_not validate_absence_of(:attr)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an ActiveModel class with an absence validation' do
|
||||
it 'accepts' do
|
||||
active_model_validating_absence_of(:attr).should validate_absence_of(:attr)
|
||||
end
|
||||
|
||||
it 'does not override the default message with a blank' do
|
||||
active_model_validating_absence_of(:attr).should validate_absence_of(:attr).with_message(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an ActiveModel class without an absence validation' do
|
||||
it 'rejects' do
|
||||
active_model_with(:attr).should_not validate_absence_of(:attr)
|
||||
end
|
||||
|
||||
it 'provides the correct failure message' do
|
||||
message = %{Expected errors to include "must be blank" when attr is set to "an arbitrary value", got no errors}
|
||||
|
||||
expect { active_model_with(:attr).should validate_absence_of(:attr) }.to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a has_many association with an absence validation' do
|
||||
it 'requires the attribute to not be set' do
|
||||
having_many(:children, absence: true).should validate_absence_of(:children)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a has_many association without an absence validation' do
|
||||
it 'does not require the attribute to not be set' do
|
||||
having_many(:children, absence: false).
|
||||
should_not validate_absence_of(:children)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an absent has_and_belongs_to_many association' do
|
||||
it 'accepts' do
|
||||
model = having_and_belonging_to_many(:children, absence: true)
|
||||
model.should validate_absence_of(:children)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a non-absent has_and_belongs_to_many association' do
|
||||
it 'rejects' do
|
||||
model = having_and_belonging_to_many(:children, absence: false)
|
||||
model.should_not validate_absence_of(:children)
|
||||
end
|
||||
end
|
||||
|
||||
context "an i18n translation containing %{attribute} and %{model}" do
|
||||
after { I18n.backend.reload! }
|
||||
|
||||
it "does not raise an exception" do
|
||||
stub_translation("activerecord.errors.messages.present",
|
||||
"%{attribute} must be blank in a %{model}")
|
||||
|
||||
expect {
|
||||
validating_absence_of(:attr).should validate_absence_of(:attr)
|
||||
}.to_not raise_exception
|
||||
end
|
||||
end
|
||||
|
||||
context "an attribute with a context-dependent validation" do
|
||||
context "without the validation context" do
|
||||
it "does not match" do
|
||||
validating_absence_of(:attr, on: :customisable).should_not validate_absence_of(:attr)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the validation context" do
|
||||
it "matches" do
|
||||
validating_absence_of(:attr, on: :customisable).should validate_absence_of(:attr).on(:customisable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validating_absence_of(attr, options = {})
|
||||
define_model :example, attr => :string do
|
||||
validates_absence_of attr, options
|
||||
end.new
|
||||
end
|
||||
|
||||
def active_model_with(attr, &block)
|
||||
define_active_model_class('Example', accessors: [attr], &block).new
|
||||
end
|
||||
|
||||
def active_model_validating_absence_of(attr)
|
||||
active_model_with(attr) do
|
||||
validates_absence_of attr
|
||||
end
|
||||
end
|
||||
|
||||
def having_many(plural_name, options = {})
|
||||
define_model plural_name.to_s.singularize
|
||||
define_model :parent do
|
||||
has_many plural_name
|
||||
if options[:absence]
|
||||
validates_absence_of plural_name
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
def having_and_belonging_to_many(plural_name, options = {})
|
||||
create_table 'children_parents', id: false do |t|
|
||||
t.integer "#{plural_name.to_s.singularize}_id"
|
||||
t.integer :parent_id
|
||||
end
|
||||
|
||||
define_model plural_name.to_s.singularize
|
||||
define_model :parent do
|
||||
has_and_belongs_to_many plural_name
|
||||
if options[:absence]
|
||||
validates_absence_of plural_name
|
||||
end
|
||||
end.new
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue