Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend AM validate_length_of matcher to support array attributes #1560

Merged
40 changes: 36 additions & 4 deletions lib/shoulda/matchers/active_model/validate_length_of_matcher.rb
Original file line number Diff line number Diff line change
@@ -260,6 +260,29 @@ module ActiveModel
# should validate_length_of(:bio).is_at_least(15).allow_blank
# end
#
# ##### as_array
#
# Use `as_array` if you have an ActiveModel model and the attribute being validated
# is designed to store an array. (This is not necessary if you have an ActiveRecord
# model with an array column, as the matcher will detect this automatically.)
#
# class User
# include ActiveModel::Model
# attribute :arr, array: true
#
# validates_length_of :arr, minimum: 15
# end
#
# # RSpec
# describe User do
# it { should validate_length_of(:arr).as_array.is_at_least(15) }
# end
#
# # Minitest (Shoulda)
# class UserTest < ActiveSupport::TestCase
# should validate_length_of(:arr).as_array.is_at_least(15)
# end
#
def validate_length_of(attr)
ValidateLengthOfMatcher.new(attr)
end
@@ -275,6 +298,11 @@ def initialize(attribute)
@long_message = nil
end

def as_array
@options[:array] = true
self
end

def is_at_least(length)
@options[:minimum] = length
@short_message ||= :too_short
@@ -451,15 +479,19 @@ def allow_nil_does_not_match?
end

def allows_length_of?(length, message)
allows_value_of(string_of_length(length), message)
allows_value_of(value_of_length(length), message)
end

def disallows_length_of?(length, message)
disallows_value_of(string_of_length(length), message)
disallows_value_of(value_of_length(length), message)
end

def value_of_length(length)
(array_column? ? ['x'] : 'x') * length
end

def string_of_length(length)
'x' * length
def array_column?
@options[:array] || super
end

def translated_short_message
6 changes: 6 additions & 0 deletions lib/shoulda/matchers/active_model/validation_matcher.rb
Original file line number Diff line number Diff line change
@@ -186,6 +186,12 @@ def expects_to_allow_blank?
def blank_values
['', ' ', "\n", "\r", "\t", "\f"]
end

def array_column?
@subject.class.respond_to?(:columns_hash) &&
@subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
@subject.class.columns_hash[@attribute.to_s].array
end
end
end
end
Original file line number Diff line number Diff line change
@@ -370,15 +370,305 @@ def configure_validation_matcher(matcher)
end
end

if database_supports_array_columns?
context 'when the column backing the attribute is an array' do
context 'an attribute with a non-zero minimum length validation' do
it 'accepts ensuring the correct minimum length' do
expect(validating_array_length(minimum: 4)).
to validate_length_of(:attr).is_at_least(4)
end

it 'rejects ensuring a lower minimum length with any message' do
expect(validating_array_length(minimum: 4)).
not_to validate_length_of(:attr).is_at_least(3).with_short_message(/.*/)
end

it 'rejects ensuring a higher minimum length with any message' do
expect(validating_array_length(minimum: 4)).
not_to validate_length_of(:attr).is_at_least(5).with_short_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(validating_array_length(minimum: 4)).
to validate_length_of(:attr).is_at_least(4).with_short_message(nil)
end

it 'fails when used in the negative' do
assertion = lambda do
expect(validating_array_length(minimum: 4)).
not_to validate_length_of(:attr).is_at_least(4)
end

message = <<-MESSAGE
Expected Example not to validate that the length of :attr is at least 4,
but this could not be proved.
After setting :attr to ‹["x", "x", "x", "x"]›, the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE

expect(&assertion).to fail_with_message(message)
end
end

context 'an attribute with a minimum length validation of 0' do
it 'accepts ensuring the correct minimum length' do
expect(validating_array_length(minimum: 0)).
to validate_length_of(:attr).is_at_least(0)
end
end

context 'an attribute with a maximum length' do
it 'accepts ensuring the correct maximum length' do
expect(validating_array_length(maximum: 4)).
to validate_length_of(:attr).is_at_most(4)
end

it 'rejects ensuring a lower maximum length with any message' do
expect(validating_array_length(maximum: 4)).
not_to validate_length_of(:attr).is_at_most(3).with_long_message(/.*/)
end

it 'rejects ensuring a higher maximum length with any message' do
expect(validating_array_length(maximum: 4)).
not_to validate_length_of(:attr).is_at_most(5).with_long_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(validating_array_length(maximum: 4)).
to validate_length_of(:attr).is_at_most(4).with_long_message(nil)
end
end

context 'an attribute with a required exact length' do
it 'accepts ensuring the correct length' do
expect(validating_array_length(is: 4)).
to validate_length_of(:attr).is_equal_to(4)
end

it 'rejects ensuring a lower maximum length with any message' do
expect(validating_array_length(is: 4)).
not_to validate_length_of(:attr).is_equal_to(3).with_message(/.*/)
end

it 'rejects ensuring a higher maximum length with any message' do
expect(validating_array_length(is: 4)).
not_to validate_length_of(:attr).is_equal_to(5).with_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(validating_array_length(is: 4)).
to validate_length_of(:attr).is_equal_to(4).with_message(nil)
end
end
end
end

context 'when column is validated as array' do
context 'an attribute with a non-zero minimum length validation' do
it 'accepts ensuring the correct minimum length' do
expect(validating_length(type: :jsonb, minimum: 4)).
to validate_length_of(:attr).as_array.is_at_least(4)
end

it 'rejects ensuring a lower minimum length with any message' do
expect(validating_length(type: :jsonb, minimum: 4)).
not_to validate_length_of(:attr).as_array.is_at_least(3).with_short_message(/.*/)
end

it 'rejects ensuring a higher minimum length with any message' do
expect(validating_length(type: :jsonb, minimum: 4)).
not_to validate_length_of(:attr).as_array.is_at_least(5).with_short_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(validating_length(type: :jsonb, minimum: 4)).
to validate_length_of(:attr).as_array.is_at_least(4).with_short_message(nil)
end

it 'fails when used in the negative' do
assertion = lambda do
expect(validating_length(type: :jsonb, minimum: 4)).
not_to validate_length_of(:attr).as_array.is_at_least(4)
end

message = <<-MESSAGE
Expected Example not to validate that the length of :attr is at least 4,
but this could not be proved.
After setting :attr to ‹["x", "x", "x", "x"]›, the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE

expect(&assertion).to fail_with_message(message)
end
end

context 'an attribute with a minimum length validation of 0' do
it 'accepts ensuring the correct minimum length' do
expect(validating_length(type: :jsonb, minimum: 0)).
to validate_length_of(:attr).as_array.is_at_least(0)
end
end

context 'an attribute with a maximum length' do
it 'accepts ensuring the correct maximum length' do
expect(validating_length(type: :jsonb, maximum: 4)).
to validate_length_of(:attr).as_array.is_at_most(4)
end

it 'rejects ensuring a lower maximum length with any message' do
expect(validating_length(type: :jsonb, maximum: 4)).
not_to validate_length_of(:attr).as_array.is_at_most(3).with_long_message(/.*/)
end

it 'rejects ensuring a higher maximum length with any message' do
expect(validating_length(type: :jsonb, maximum: 4)).
not_to validate_length_of(:attr).as_array.is_at_most(5).with_long_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(validating_length(type: :jsonb, maximum: 4)).
to validate_length_of(:attr).as_array.is_at_most(4).with_long_message(nil)
end
end

context 'an attribute with a required exact length' do
it 'accepts ensuring the correct length' do
expect(validating_length(type: :jsonb, is: 4)).
to validate_length_of(:attr).as_array.is_equal_to(4)
end

it 'rejects ensuring a lower maximum length with any message' do
expect(validating_length(type: :jsonb, is: 4)).
not_to validate_length_of(:attr).as_array.is_equal_to(3).with_message(/.*/)
end

it 'rejects ensuring a higher maximum length with any message' do
expect(validating_length(type: :jsonb, is: 4)).
not_to validate_length_of(:attr).as_array.is_equal_to(5).with_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(validating_length(type: :jsonb, is: 4)).
to validate_length_of(:attr).as_array.is_equal_to(4).with_message(nil)
end
end
end
jarenas9539 marked this conversation as resolved.
Show resolved Hide resolved

context 'whith ActiveModel attribute marked as array' do
context 'an attribute with a non-zero minimum length validation' do
it 'accepts ensuring the correct minimum length' do
expect(define_active_model_validating_length(minimum: 4)).
to validate_length_of(:attr).as_array.is_at_least(4)
end

it 'rejects ensuring a lower minimum length with any message' do
expect(define_active_model_validating_length(minimum: 4)).
not_to validate_length_of(:attr).as_array.is_at_least(3).with_short_message(/.*/)
end

it 'rejects ensuring a higher minimum length with any message' do
expect(define_active_model_validating_length(minimum: 4)).
not_to validate_length_of(:attr).as_array.is_at_least(5).with_short_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(define_active_model_validating_length(minimum: 4)).
to validate_length_of(:attr).as_array.is_at_least(4).with_short_message(nil)
end

it 'fails when used in the negative' do
assertion = lambda do
expect(define_active_model_validating_length(minimum: 4)).
not_to validate_length_of(:attr).as_array.is_at_least(4)
end

message = <<-MESSAGE
Expected Example not to validate that the length of :attr is at least 4,
but this could not be proved.
After setting :attr to ‹["x", "x", "x", "x"]›, the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE

expect(&assertion).to fail_with_message(message)
end
end

context 'an attribute with a minimum length validation of 0' do
it 'accepts ensuring the correct minimum length' do
expect(define_active_model_validating_length(minimum: 0)).
to validate_length_of(:attr).as_array.is_at_least(0)
end
end

context 'an attribute with a maximum length' do
it 'accepts ensuring the correct maximum length' do
expect(define_active_model_validating_length(maximum: 4)).
to validate_length_of(:attr).as_array.is_at_most(4)
end

it 'rejects ensuring a lower maximum length with any message' do
expect(define_active_model_validating_length(maximum: 4)).
not_to validate_length_of(:attr).as_array.is_at_most(3).with_long_message(/.*/)
end

it 'rejects ensuring a higher maximum length with any message' do
expect(define_active_model_validating_length(maximum: 4)).
not_to validate_length_of(:attr).as_array.is_at_most(5).with_long_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(define_active_model_validating_length(maximum: 4)).
to validate_length_of(:attr).as_array.is_at_most(4).with_long_message(nil)
end
end

context 'an attribute with a required exact length' do
it 'accepts ensuring the correct length' do
expect(define_active_model_validating_length(is: 4)).
to validate_length_of(:attr).as_array.is_equal_to(4)
end

it 'rejects ensuring a lower maximum length with any message' do
expect(define_active_model_validating_length(is: 4)).
not_to validate_length_of(:attr).as_array.is_equal_to(3).with_message(/.*/)
end

it 'rejects ensuring a higher maximum length with any message' do
expect(define_active_model_validating_length(is: 4)).
not_to validate_length_of(:attr).as_array.is_equal_to(5).with_message(/.*/)
end

it 'does not override the default message with a blank' do
expect(define_active_model_validating_length(is: 4)).
to validate_length_of(:attr).as_array.is_equal_to(4).with_message(nil)
end
end
end

def define_model_validating_length(options = {})
options = options.dup
attribute_name = options.delete(:attribute_name) { :attr }
type = options.delete(:type) || :varchar
array = options.delete(:array)

define_model(:example, attribute_name => :string) do |model|
define_model(:example, attribute_name => { type: type, options: { array: array } }) do |model|
model.validates_length_of(attribute_name, options)
end
end

def define_active_model_validating_length(options)
attribute_name = options.delete(:attribute_name) { :attr }

define_active_model_class('Example', accessors: []) do
attribute attribute_name, array: true
validates_length_of(attribute_name, options)
end.new
end

def validating_array_length(options = {})
define_model_validating_length(options.merge(array: true)).new
end

def validating_length(options = {})
define_model_validating_length(options).new
end