Permalink
Browse files

Add validate_length_of matcher.

  • Loading branch information...
1 parent 8dfabc6 commit 8122f1a791296d7dffe4e4abdd94e2d1338f5779 @nmerouze nmerouze committed Jul 31, 2009
View
@@ -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
@@ -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
View
@@ -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"
@@ -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.