Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/shoulda/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'shoulda/matchers/error'
require 'shoulda/matchers/independent'
require 'shoulda/matchers/integrations'
require 'shoulda/matchers/matcher_collection'
require 'shoulda/matchers/matcher_context'
require 'shoulda/matchers/rails_shim'
require 'shoulda/matchers/util'
Expand Down
2 changes: 1 addition & 1 deletion lib/shoulda/matchers/active_model/allow_value_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def failure_message
message << '.'
else
message << " producing these validation errors:\n\n"
message << validator.all_formatted_validation_error_messages
message << validator.formatted_validation_error_messages
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/shoulda/matchers/active_model/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ def pretty_error_messages(object)
format_validation_errors(object.errors)
end

def format_validation_errors(errors)
def format_validation_errors(errors, attr = nil)
list_items = errors.to_hash.keys.map do |attribute|
next if attr && attr.to_sym != attribute.to_sym

messages = errors[attribute]
"* #{attribute}: #{messages}"
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,33 @@ module ActiveModel
# with_message("there shall be peace on Earth")
# end
#
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class Robot
# include ActiveModel::Model
# attr_accessor :arms, :legs
#
# validates_absence_of :arms, :legs
# end
#
# # RSpec
# RSpec.describe Robot, type: :model do
# it { should validate_absence_of(:arms, :legs) }
# end
#
# # Minitest (Shoulda)
# class RobotTest < ActiveSupport::TestCase
# should validate_absence_of(:arms, :legs)
# end
#
# @return [ValidateAbsenceOfMatcher}
#
def validate_absence_of(attr)
ValidateAbsenceOfMatcher.new(attr)
def validate_absence_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateAbsenceOfMatcher.new(attr) }
end

# @private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,33 @@ module ActiveModel
# with_message('You must accept the terms of service')
# end
#
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class User
# include ActiveModel::Model
# attr_accessor :terms, :privacy_policy
#
# validates_acceptance_of :terms, :privacy_policy
# end
#
# # RSpec
# RSpec.describe User, type: :model do
# it { should validate_acceptance_of(:terms, :privacy_policy) }
# end
#
# # Minitest (Shoulda)
# class UserTest < ActiveSupport::TestCase
# should validate_acceptance_of(:terms, :privacy_policy)
# end
#
# @return [ValidateAcceptanceOfMatcher]
#
def validate_acceptance_of(attr)
ValidateAcceptanceOfMatcher.new(attr)
def validate_acceptance_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateAcceptanceOfMatcher.new(attr) }
end

# @private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,33 @@ module ActiveModel
# should validate_comparison_of(:age).is_greater_than(0).allow_nil
# end
#
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class Item
# include ActiveModel::Model
# attr_accessor :width, :height
#
# validates_comparison_of :width, :height, greater_than: 0
# end
#
# # RSpec
# RSpec.describe Item, type: :model do
# it { should validate_comparison_of(:width, :height).is_greater_than(0) }
# end
#
# # Minitest (Shoulda)
# class ItemTest < ActiveSupport::TestCase
# should validate_comparison_of(:width, :height).is_greater_than(0)
# end
#
# @return [ValidateComparisonOfMatcher]
#
def validate_comparison_of(attr)
ValidateComparisonOfMatcher.new(attr)
def validate_comparison_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateComparisonOfMatcher.new(attr) }
end

# @private
Expand Down Expand Up @@ -378,6 +401,13 @@ def failure_message_when_negated
end
end

def failure_reason
raw_submatcher_failure_reason_for(
first_submatcher_that_fails_to_match,
:failure_message,
)
end

def given_numeric_column?
attribute_is_active_record_column? &&
[:integer, :float, :decimal].include?(column_type)
Expand Down Expand Up @@ -485,21 +515,26 @@ def build_submatcher_failure_message_for(
submatcher,
failure_message_method
)
Shoulda::Matchers.word_wrap(
raw_submatcher_failure_reason_for(submatcher, failure_message_method),
indent: 2,
)
end

def raw_submatcher_failure_reason_for(submatcher, failure_message_method)
failure_message = submatcher.public_send(failure_message_method)
submatcher_description = submatcher.simple_description.
sub(/\bvalidate that\b/, 'validates').
sub(/\bdisallow\b/, 'disallows').
sub(/\ballow\b/, 'allows')
submatcher_message =
if number_of_submatchers_for_failure_message > 1
"In checking that #{model.name} #{submatcher_description}, " +
failure_message[0].downcase +
failure_message[1..]
else
failure_message
end

Shoulda::Matchers.word_wrap(submatcher_message, indent: 2)
if number_of_submatchers_for_failure_message > 1
"In checking that #{model.name} #{submatcher_description}, " +
failure_message[0].downcase +
failure_message[1..]
else
failure_message
end
end

def comparison_descriptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,33 @@ module ActiveModel
# with_message('Please re-enter your password')
# end
#
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class User
# include ActiveModel::Model
# attr_accessor :password, :password_confirmation, :email, :email_confirmation
#
# validates_confirmation_of :password, :email
# end
#
# # RSpec
# RSpec.describe User, type: :model do
# it { should validate_confirmation_of(:password, :email) }
# end
#
# # Minitest (Shoulda)
# class UserTest < ActiveSupport::TestCase
# should validate_confirmation_of(:password, :email)
# end
#
# @return [ValidateConfirmationOfMatcher]
#
def validate_confirmation_of(attr)
ValidateConfirmationOfMatcher.new(attr)
def validate_confirmation_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateConfirmationOfMatcher.new(attr) }
end

# @private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,33 @@ module ActiveModel
# with_message('You chose a puny weapon')
# end
#
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class Article
# include ActiveModel::Model
# attr_accessor :slug, :handle
#
# validates_exclusion_of :slug, :handle, in: %w[admin root]
# end
#
# # RSpec
# RSpec.describe Article, type: :model do
# it { should validate_exclusion_of(:slug, :handle).in_array(%w[admin root]) }
# end
#
# # Minitest (Shoulda)
# class ArticleTest < ActiveSupport::TestCase
# should validate_exclusion_of(:slug, :handle).in_array(%w[admin root])
# end
#
# @return [ValidateExclusionOfMatcher]
#
def validate_exclusion_of(attr)
ValidateExclusionOfMatcher.new(attr)
def validate_exclusion_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateExclusionOfMatcher.new(attr) }
end

# @private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,33 @@ module ActiveModel
# allow_blank
# end
#
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class Article
# include ActiveModel::Model
# attr_accessor :status, :category
#
# validates_inclusion_of :status, :category, in: %w[draft published]
# end
#
# # RSpec
# RSpec.describe Article, type: :model do
# it { should validate_inclusion_of(:status, :category).in_array(%w[draft published]) }
# end
#
# # Minitest (Shoulda)
# class ArticleTest < ActiveSupport::TestCase
# should validate_inclusion_of(:status, :category).in_array(%w[draft published])
# end
#
# @return [ValidateInclusionOfMatcher]
#
def validate_inclusion_of(attr)
ValidateInclusionOfMatcher.new(attr)
def validate_inclusion_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateInclusionOfMatcher.new(attr) }
end

# @private
Expand Down
29 changes: 26 additions & 3 deletions lib/shoulda/matchers/active_model/validate_length_of_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ module ActiveModel
# should validate_length_of(:bio).is_at_least(15).allow_nil
# end
#
# @return [ValidateLengthOfMatcher]
#
# ##### allow_blank
#
Expand Down Expand Up @@ -286,8 +285,32 @@ module ActiveModel
# should validate_length_of(:arr).as_array.is_at_least(15)
# end
#
def validate_length_of(attr)
ValidateLengthOfMatcher.new(attr)
# #### Multiple attributes
#
# You can pass multiple attributes to assert that each one has the
# validation. Any qualifier chained on the matcher is applied to
# every attribute uniformly.
#
# class User
# include ActiveModel::Model
# attr_accessor :first_name, :last_name
#
# validates_length_of :first_name, :last_name, minimum: 2
# end
#
# # RSpec
# RSpec.describe User, type: :model do
# it { should validate_length_of(:first_name, :last_name).is_at_least(2) }
# end
#
# # Minitest (Shoulda)
# class UserTest < ActiveSupport::TestCase
# should validate_length_of(:first_name, :last_name).is_at_least(2)
# end
#
# @return [ValidateLengthOfMatcher]
def validate_length_of(*attrs)
MatcherCollection.build(attrs) { |attr| ValidateLengthOfMatcher.new(attr) }
end

# @private
Expand Down
Loading
Loading