Skip to content

Commit

Permalink
Add validate_length_of matcher.
Browse files Browse the repository at this point in the history
  • Loading branch information
nmerouze committed Jul 31, 2009
1 parent 8dfabc6 commit 8122f1a
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 5 deletions.
10 changes: 6 additions & 4 deletions README.md
Expand Up @@ -4,13 +4,15 @@ Remarkable matchers for [MongoMapper](http://github.com/jnunemaker/mongomapper).

## Matchers

<pre><code>
it { should have_key(:name, String) }
<pre><code>it { should have_key(:name, String) }
it { should have_keys(:name, :phone_number, String) }
it { should validate_presence_of(:name, :phone_number, :message => "not there!") }
it { should belong_to(:user, :class_name => 'Person') }
it { should have_many(:users, :class_name => 'Person', :polymorphic => true) }
</code></pre>
it { should have_many(:users, :class_name => 'Person', :polymorphic => true) }</code></pre>

## TODO

* Finish validate_length_of

## Contributions

Expand Down
106 changes: 106 additions & 0 deletions lib/remarkable_mongomapper/matchers/validate_length_of_matcher.rb
@@ -0,0 +1,106 @@
module Remarkable
module MongoMapper
module Matchers
class ValidateLengthOfMatcher < Remarkable::MongoMapper::Base #:nodoc:
arguments :collection => :attributes, :as => :attribute

optional :within, :minimum, :maximum, :is
optional :allow_nil, :allow_blank, :default => true
optional :message

default_options :message => "is invalid"

collection_assertions :less_than_min_length?, :exactly_min_length?,
:more_than_max_length?, :exactly_max_length?,
:allow_nil?, :allow_blank?

before_assert do
if @options[:is]
@min_value, @max_value = @options[:is], @options[:is]
elsif @options[:within]
@min_value, @max_value = @options[:within].first, @options[:within].last
elsif @options[:maximum]
@min_value, @max_value = nil, @options[:maximum]
elsif @options[:minimum]
@min_value, @max_value = @options[:minimum], nil
end
end

protected
def allow_nil?
super(default_message_for(:too_short))
end

def allow_blank?
super(default_message_for(:too_short))
end

def less_than_min_length?
@min_value.nil? || @min_value <= 1 || bad?(@min_value - 1, default_message_for(:too_short))
end

def exactly_min_length?
@min_value.nil? || @min_value <= 0 || good?(@min_value, default_message_for(:too_short))
end

def more_than_max_length?
@max_value.nil? || bad?(@max_value + 1, default_message_for(:too_long))
end

def exactly_max_length?
@max_value.nil? || @min_value == @max_value || good?(@max_value, default_message_for(:too_long))
end

def interpolation_options
{ :minimum => @min_value, :maximum => @max_value }
end

# Returns the default message for the validation type.
# If user supplied :message, it will return it. Otherwise it will return
# wrong_length on :is validation and :too_short or :too_long in the other
# types.
#
def default_message_for(validation_type)
return :message if @options[:message]
end
end

# Validates the length of the given attributes. You have also to supply
# one of the following options: minimum, maximum, is or within.
#
# Note: this method is also aliased as <tt>validate_size_of</tt>.
#
# == Options
#
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp, string or symbol. Default = "is invalid"</tt>
#
# == Examples
#
# it { should validate_length_of(:password).within(6..20) }
# it { should validate_length_of(:password).maximum(20) }
# it { should validate_length_of(:password).minimum(6) }
# it { should validate_length_of(:age).is(18) }
#
# should_validate_length_of :password, :within => 6..20
# should_validate_length_of :password, :maximum => 20
# should_validate_length_of :password, :minimum => 6
# should_validate_length_of :age, :is => 18
#
# should_validate_length_of :password do |m|
# m.minimum 6
# m.maximum 20
# end
#
def validate_length_of(*attributes, &block)
ValidateLengthOfMatcher.new(*attributes, &block).spec(self)
end
end
end
end
89 changes: 88 additions & 1 deletion locales/en.yml
Expand Up @@ -36,13 +36,100 @@ en:
expectations:
has_key: "{{subject_name}} to have key named {{attribute}} with type {{type}}"

validate_acceptance_of:
description: "require {{attributes}} to be accepted"
expectations:
requires_acceptance: "{{subject_name}} to be invalid if {{attribute}} is not accepted"
accept_is_valid: "{{subject_name}} to be valid when {{attribute}} is accepted with value {{accept}}"
optionals:
accept:
positive: "with value {{inspect}}"

validate_associated:
description: "require associated {{associations}} to be valid"
expectations:
is_valid: "{{subject_name}} to be invalid when {{association}} is invalid"

validate_confirmation_of:
description: "require {{attributes}} to be confirmed"
expectations:
responds_to_confirmation: "{{subject_name}} instance responds to {{attribute}}_confirmation"
confirms: "{{subject_name}} to be valid only when {{attribute}} is confirmed"

validate_exclusion_of:
description: "ensure exclusion of {{attributes}} in {{in}}"
expectations:
is_valid: "{{subject_name}} to be valid when {{attribute}} is set to {{value}}"
is_invalid: "{{subject_name}} to be invalid when {{attribute}} is set to {{value}}"

validate_inclusion_of:
description: "ensure inclusion of {{attributes}} in {{in}}"
expectations:
is_valid: "{{subject_name}} to be valid when {{attribute}} is set to {{value}}"
is_invalid: "{{subject_name}} to be invalid when {{attribute}} is set to {{value}}"

validate_length_of:
description: "ensure length of {{attributes}}"
expectations:
less_than_min_length: "{{subject_name}} to be invalid when {{attribute}} length is less than {{minimum}} characters"
exactly_min_length: "{{subject_name}} to be valid when {{attribute}} length is {{minimum}} characters"
more_than_max_length: "{{subject_name}} to be invalid when {{attribute}} length is more than {{maximum}} characters"
exactly_max_length: "{{subject_name}} to be valid when {{attribute}} length is {{maximum}} characters"
optionals:
within:
positive: "is within {{inspect}} characters"
maximum:
positive: "is maximum {{inspect}} characters"
minimum:
positive: "is minimum {{inspect}} characters"
is:
positive: "is equal to {{inspect}} characters"
with_kind_of:
positive: "with kind of {{value}}"

validate_numericality_of:
description: "ensure numericality of {{attributes}}"
expectations:
only_numeric_values: "{{subject_name}} to allow only numeric values for {{attribute}}"
only_integer: "{{subject_name}} to {{not}}allow only integer values for {{attribute}}"
only_even: "{{subject_name}} to allow only even values for {{attribute}}"
only_odd: "{{subject_name}} to allow only odd values for {{attribute}}"
equals_to: "{{subject_name}} to be valid only when {{attribute}} is equal to {{count}}"
more_than_maximum: "{{subject_name}} to be invalid when {{attribute}} is greater than {{count}}"
less_than_minimum: "{{subject_name}} to be invalid when {{attribute}} is less than {{count}}"
optionals:
only_integer:
positive: "allowing only integer values"
odd:
positive: "allowing only odd values"
even:
positive: "allowing only even values"
equal_to:
positive: "is equal to {{inspect}}"
less_than:
positive: "is less than {{inspect}}"
greater_than:
positive: "is greater than {{inspect}}"
less_than_or_equal_to:
positive: "is less than or equal to {{inspect}}"
greater_than_or_equal_to:
positive: "is greater than or equal to {{inspect}}"

validate_presence_of:
description: "require {{attributes}} to be set"
expectations:
allow_nil: "{{subject_name}} to require {{attribute}} to be set"
allow_nil: "{{subject_name}} to require {{attribute}} to be set"

validate_uniqueness_of:
description: "require unique values for {{attributes}}"
expectations:
responds_to_scope: "{{subject_name}} instance responds to {{method}}"
is_unique: "{{subject_name}} to require unique values for {{attribute}}"
case_sensitive: "{{subject_name}} to {{not}}be case sensitive on {{attribute}} validation"
valid_with_new_scope: "{{subject_name}} to be valid when {{attribute}} scope ({{method}}) change"
optionals:
scope:
positive: "scoped to {{sentence}}"
case_sensitive:
positive: "case sensitive"
negative: "case insensitive"
147 changes: 147 additions & 0 deletions spec/matchers/validate_length_of_matcher_spec.rb
@@ -0,0 +1,147 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe 'validate_length_of' do
include ModelBuilder

# Defines a model, create a validation and returns a raw matcher
def define_and_validate(options={})
options = options.merge(:within => 3..5) if options.slice(:within, :maximum, :minimum, :is).empty?

@model = define_model :product do
include MongoMapper::Document

key :size, String
key :category, String

validates_length_of :size, options
end

validate_length_of(:size)
end

describe 'messages' do
before(:each){ @matcher = define_and_validate }

it 'should contain a description' do
@matcher.within(3..5)
@matcher.description.should == 'ensure length of size is within 3..5 characters'

@matcher.within(nil).is(3)
@matcher.description.should == 'ensure length of size is equal to 3 characters'

@matcher.is(nil).maximum(5)
@matcher.description.should == 'ensure length of size is maximum 5 characters'

@matcher.maximum(nil).minimum(3)
@matcher.description.should == 'ensure length of size is minimum 3 characters'

@matcher.allow_nil(false)
@matcher.description.should == 'ensure length of size is minimum 3 characters and not allowing nil values'

@matcher.allow_blank
@matcher.description.should == 'ensure length of size is minimum 3 characters, not allowing nil values, and allowing blank values'
end

it 'should set less_than_min_length? message' do
@matcher.within(4..5).matches?(@model)
@matcher.failure_message.should == 'Expected Product to be invalid when size length is less than 4 characters'
end

it 'should set exactly_min_length? message' do
@matcher.should_receive(:less_than_min_length?).and_return(true)
@matcher.within(2..5).matches?(@model)
@matcher.failure_message.should == 'Expected Product to be valid when size length is 2 characters'
end

it 'should set more_than_max_length? message' do
@matcher.within(3..4).matches?(@model)
@matcher.failure_message.should == 'Expected Product to be invalid when size length is more than 4 characters'
end

it 'should set exactly_max_length? message' do
@matcher.should_receive(:more_than_max_length?).and_return(true)
@matcher.within(3..6).matches?(@model)
@matcher.failure_message.should == 'Expected Product to be valid when size length is 6 characters'
end

it 'should set allow_blank? message' do
@matcher.within(3..5).allow_blank.matches?(@model)
@matcher.failure_message.should == 'Expected Product to allow blank values for size'
end

it 'should set allow_nil? message' do
@matcher.within(3..5).allow_nil.matches?(@model)
@matcher.failure_message.should == 'Expected Product to allow nil values for size'
end
end

describe 'matcher' do
# Wrap specs without options. Usually a couple specs.
describe 'without options' do
before(:each){ define_and_validate }

it { should validate_length_of(:size, :within => 3..5) }
it { should_not validate_length_of(:category, :within => 3..5) }
end

describe "with message option" do

# if RAILS_VERSION =~ /^2.3/
# it { should define_and_validate(:message => 'not valid').within(3..5).message('not valid') }
# it { should_not define_and_validate(:message => 'not valid').within(3..5).message('valid') }
# else
# it { should define_and_validate(:too_short => 'not valid', :too_long => 'not valid').within(3..5).message('not valid') }
# it { should_not define_and_validate(:too_short => 'not valid', :too_long => 'not valid').within(3..5).message('valid') }
# end

it { should define_and_validate(:is => 4, :message => 'not valid').is(4).message('not valid') }
it { should_not define_and_validate(:is => 4, :message => 'not valid').is(4).message('valid') }
end

describe "with within option" do
it { should define_and_validate(:within => 3..5).within(3..5) }
it { should_not define_and_validate(:within => 3..5).within(2..5) }
it { should_not define_and_validate(:within => 3..5).within(4..5) }
it { should_not define_and_validate(:within => 3..5).within(3..4) }
it { should_not define_and_validate(:within => 3..5).within(3..6) }
end

describe "with minimum option" do
it { should define_and_validate(:minimum => 3).minimum(3) }
it { should_not define_and_validate(:minimum => 3).minimum(2) }
it { should_not define_and_validate(:minimum => 3).minimum(4) }
end

describe "with maximum option" do
it { should define_and_validate(:maximum => 3).maximum(3) }
it { should_not define_and_validate(:maximum => 3).maximum(2) }
it { should_not define_and_validate(:maximum => 3).maximum(4) }
end

describe "with is option" do
it { should define_and_validate(:is => 3).is(3) }
it { should_not define_and_validate(:is => 3).is(2) }
it { should_not define_and_validate(:is => 3).is(4) }
end

# Those are macros to test optionals which accept only boolean values
create_optional_boolean_specs(:allow_nil, self)
create_optional_boolean_specs(:allow_blank, self)
end

# In macros we include just a few tests to assure that everything works properly
describe 'macros' do
before(:each) { define_and_validate }

should_validate_length_of :size, :within => 3..5

should_not_validate_length_of :size, :within => 2..5
should_not_validate_length_of :size, :within => 4..5
should_not_validate_length_of :size, :within => 3..4
should_not_validate_length_of :size, :within => 3..6

should_validate_length_of :size do |m|
m.within = 3..5
end
end
end

0 comments on commit 8122f1a

Please sign in to comment.