remarkable_activemodel 4.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +147 -0
- data/LICENSE +20 -0
- data/README +74 -0
- data/lib/remarkable_activemodel.rb +28 -0
- data/lib/remarkable_activemodel/base.rb +252 -0
- data/lib/remarkable_activemodel/matchers/allow_values_for_matcher.rb +88 -0
- data/lib/remarkable_activemodel/matchers/validate_acceptance_of_matcher.rb +50 -0
- data/lib/remarkable_activemodel/matchers/validate_confirmation_of_matcher.rb +44 -0
- data/lib/remarkable_activemodel/matchers/validate_exclusion_of_matcher.rb +57 -0
- data/lib/remarkable_activemodel/matchers/validate_inclusion_of_matcher.rb +57 -0
- data/lib/remarkable_activemodel/matchers/validate_length_of_matcher.rb +150 -0
- data/lib/remarkable_activemodel/matchers/validate_numericality_of_matcher.rb +188 -0
- data/lib/remarkable_activemodel/matchers/validate_presence_of_matcher.rb +91 -0
- data/locale/en.yml +116 -0
- data/remarkable_activemodel.gemspec +61 -0
- data/remarkable_activerecord.gemspec +60 -0
- metadata +118 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module ActiveModel
|
3
|
+
module Matchers
|
4
|
+
class AllowValuesForMatcher < Remarkable::ActiveModel::Base #:nodoc:
|
5
|
+
include Remarkable::Negative
|
6
|
+
arguments :collection => :attributes, :as => :attribute
|
7
|
+
|
8
|
+
optional :message
|
9
|
+
optional :in, :splat => true
|
10
|
+
optional :allow_nil, :allow_blank, :default => true
|
11
|
+
|
12
|
+
collection_assertions :is_valid?, :is_invalid?, :allow_nil?, :allow_blank?
|
13
|
+
|
14
|
+
default_options :message => /.*/
|
15
|
+
|
16
|
+
before_assert do
|
17
|
+
first_value = @options[:in].is_a?(Array) ? @options[:in].first : @options[:in]
|
18
|
+
@in_range = first_value.is_a?(Range)
|
19
|
+
|
20
|
+
@options[:in] = if @in_range
|
21
|
+
first_value.to_a[0,2] + first_value.to_a[-2,2]
|
22
|
+
else
|
23
|
+
[*@options[:in]].compact
|
24
|
+
end
|
25
|
+
|
26
|
+
@options[:in].uniq!
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def is_valid?
|
32
|
+
assert_collection :value, valid_values do |value|
|
33
|
+
good?(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_invalid?
|
38
|
+
assert_collection :value, invalid_values do |value|
|
39
|
+
bad?(value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid_values
|
44
|
+
@options[:in]
|
45
|
+
end
|
46
|
+
|
47
|
+
def invalid_values
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
|
51
|
+
def interpolation_options
|
52
|
+
options = if @in_range
|
53
|
+
{ :in => (@options[:in].first..@options[:in].last).inspect }
|
54
|
+
elsif @options[:in].is_a?(Array)
|
55
|
+
{ :in => array_to_sentence(@options[:in], true, '[]') }
|
56
|
+
else
|
57
|
+
{ :in => @options[:in].inspect }
|
58
|
+
end
|
59
|
+
|
60
|
+
options.merge!(:behavior => @behavior.to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# Ensures that the attribute can be set to the given values. It checks
|
66
|
+
# for any message in the given attribute unless a :message is explicitely
|
67
|
+
# given.
|
68
|
+
#
|
69
|
+
# == Options
|
70
|
+
#
|
71
|
+
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
72
|
+
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
|
73
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
|
74
|
+
# Regexp, string or symbol. Default = <tt>/.*/</tt>
|
75
|
+
#
|
76
|
+
# == Examples
|
77
|
+
#
|
78
|
+
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
79
|
+
# it { should allow_values_for(:isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0") }
|
80
|
+
#
|
81
|
+
def allow_values_for(attribute, *args, &block)
|
82
|
+
options = args.extract_options!
|
83
|
+
AllowValuesForMatcher.new(attribute, options.merge!(:in => args), &block).spec(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module ActiveModel
|
3
|
+
module Matchers
|
4
|
+
class ValidateAcceptanceOfMatcher < Remarkable::ActiveModel::Base #:nodoc:
|
5
|
+
arguments :collection => :attributes, :as => :attribute
|
6
|
+
|
7
|
+
optional :accept, :message
|
8
|
+
optional :allow_nil, :default => true
|
9
|
+
|
10
|
+
collection_assertions :requires_acceptance?, :accept_is_valid?, :allow_nil?
|
11
|
+
|
12
|
+
default_options :message => :accepted
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def requires_acceptance?
|
17
|
+
bad?(false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def accept_is_valid?
|
21
|
+
return true unless @options.key?(:accept)
|
22
|
+
good?(@options[:accept])
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not accepted.
|
28
|
+
#
|
29
|
+
# == Options
|
30
|
+
#
|
31
|
+
# * <tt>:accept</tt> - the expected value to be accepted.
|
32
|
+
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
33
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
|
34
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
|
35
|
+
#
|
36
|
+
# == Examples
|
37
|
+
#
|
38
|
+
# should_validate_acceptance_of :eula, :terms
|
39
|
+
# should_validate_acceptance_of :eula, :terms, :accept => true
|
40
|
+
#
|
41
|
+
# it { should validate_acceptance_of(:eula, :terms) }
|
42
|
+
# it { should validate_acceptance_of(:eula, :terms, :accept => true) }
|
43
|
+
#
|
44
|
+
def validate_acceptance_of(*attributes, &block)
|
45
|
+
ValidateAcceptanceOfMatcher.new(*attributes, &block).spec(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module ActiveModel
|
3
|
+
module Matchers
|
4
|
+
class ValidateConfirmationOfMatcher < Remarkable::ActiveModel::Base #:nodoc:
|
5
|
+
arguments :collection => :attributes, :as => :attribute
|
6
|
+
|
7
|
+
optional :message
|
8
|
+
collection_assertions :responds_to_confirmation?, :confirms?
|
9
|
+
|
10
|
+
default_options :message => :confirmation
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def responds_to_confirmation?
|
15
|
+
@subject.respond_to?(:"#{@attribute}_confirmation=")
|
16
|
+
end
|
17
|
+
|
18
|
+
def confirms?
|
19
|
+
@subject.send(:"#{@attribute}_confirmation=", 'something')
|
20
|
+
bad?('different')
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# Ensures that the model cannot be saved if one of the attributes is not confirmed.
|
26
|
+
#
|
27
|
+
# == Options
|
28
|
+
#
|
29
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
|
30
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.confirmation')</tt>
|
31
|
+
#
|
32
|
+
# == Examples
|
33
|
+
#
|
34
|
+
# should_validate_confirmation_of :email, :password
|
35
|
+
#
|
36
|
+
# it { should validate_confirmation_of(:email, :password) }
|
37
|
+
#
|
38
|
+
def validate_confirmation_of(*attributes, &block)
|
39
|
+
ValidateConfirmationOfMatcher.new(*attributes, &block).spec(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'allow_values_for_matcher')
|
2
|
+
|
3
|
+
module Remarkable
|
4
|
+
module ActiveModel
|
5
|
+
module Matchers
|
6
|
+
class ValidateExclusionOfMatcher < AllowValuesForMatcher #:nodoc:
|
7
|
+
# Don't allow it to behave in the negative way.
|
8
|
+
undef_method :does_not_match?
|
9
|
+
|
10
|
+
default_options :message => :exclusion
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def valid_values
|
15
|
+
if @in_range
|
16
|
+
[ @options[:in].first - 1, @options[:in].last + 1 ]
|
17
|
+
else
|
18
|
+
value = @options[:in].select{ |i| i.is_a?(String) }.max
|
19
|
+
value ? [ value.next ] : []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def invalid_values
|
24
|
+
@options[:in]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# Ensures that given values are not valid for the attribute. If a range
|
30
|
+
# is given, ensures that the attribute is not valid in the given range.
|
31
|
+
#
|
32
|
+
# If you give that :username does not accept ["admin", "user"], it will
|
33
|
+
# test that "uses" (the next of the array max value) is allowed.
|
34
|
+
#
|
35
|
+
# == Options
|
36
|
+
#
|
37
|
+
# * <tt>:in</tt> - values to test exclusion.
|
38
|
+
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
39
|
+
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
|
40
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
|
41
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.exclusion')</tt>
|
42
|
+
#
|
43
|
+
# == Examples
|
44
|
+
#
|
45
|
+
# it { should validate_exclusion_of(:username, :in => ["admin", "user"]) }
|
46
|
+
# it { should validate_exclusion_of(:age, :in => 30..60) }
|
47
|
+
#
|
48
|
+
# should_validate_exclusion_of :username, :in => ["admin", "user"]
|
49
|
+
# should_validate_exclusion_of :age, :in => 30..60
|
50
|
+
#
|
51
|
+
def validate_exclusion_of(*args, &block)
|
52
|
+
ValidateExclusionOfMatcher.new(*args, &block).spec(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'allow_values_for_matcher')
|
2
|
+
|
3
|
+
module Remarkable
|
4
|
+
module ActiveModel
|
5
|
+
module Matchers
|
6
|
+
class ValidateInclusionOfMatcher < AllowValuesForMatcher #:nodoc:
|
7
|
+
# Don't allow it to behave in the negative way.
|
8
|
+
undef_method :does_not_match?
|
9
|
+
|
10
|
+
default_options :message => :inclusion
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def valid_values
|
15
|
+
@options[:in]
|
16
|
+
end
|
17
|
+
|
18
|
+
def invalid_values
|
19
|
+
if @in_range
|
20
|
+
[ @options[:in].first - 1, @options[:in].last + 1 ]
|
21
|
+
else
|
22
|
+
value = @options[:in].select{ |i| i.is_a?(String) }.max
|
23
|
+
value ? [ value.next ] : []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# Ensures that given values are valid for the attribute. If a range
|
30
|
+
# is given, ensures that the attribute is valid in the given range.
|
31
|
+
#
|
32
|
+
# If you give that :size accepts ["S", "M", "L"], it will test that "T"
|
33
|
+
# (the next of the array max value) is not allowed.
|
34
|
+
#
|
35
|
+
# == Options
|
36
|
+
#
|
37
|
+
# * <tt>:in</tt> - values to test inclusion.
|
38
|
+
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
39
|
+
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
|
40
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
|
41
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
|
42
|
+
#
|
43
|
+
# == Examples
|
44
|
+
#
|
45
|
+
# should_validate_inclusion_of :size, :in => ["S", "M", "L", "XL"]
|
46
|
+
# should_validate_inclusion_of :age, :in => 18..100
|
47
|
+
#
|
48
|
+
# it { should validate_inclusion_of(:size, :in => ["S", "M", "L", "XL"]) }
|
49
|
+
# it { should validate_inclusion_of(:age, :in => 18..100) }
|
50
|
+
#
|
51
|
+
def validate_inclusion_of(*args, &block)
|
52
|
+
ValidateInclusionOfMatcher.new(*args, &block).spec(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module ActiveModel
|
3
|
+
module Matchers
|
4
|
+
class ValidateLengthOfMatcher < Remarkable::ActiveModel::Base #:nodoc:
|
5
|
+
arguments :collection => :attributes, :as => :attribute
|
6
|
+
|
7
|
+
optional :within, :alias => :in
|
8
|
+
optional :minimum, :maximum, :is
|
9
|
+
optional :token, :separator, :with_kind_of
|
10
|
+
optional :allow_nil, :allow_blank, :default => true
|
11
|
+
optional :message, :too_short, :too_long, :wrong_length
|
12
|
+
|
13
|
+
collection_assertions :less_than_min_length?, :exactly_min_length?,
|
14
|
+
:more_than_max_length?, :exactly_max_length?,
|
15
|
+
:allow_nil?, :allow_blank?
|
16
|
+
|
17
|
+
before_assert do
|
18
|
+
# Reassign :in to :within
|
19
|
+
@options[:within] ||= @options.delete(:in) if @options.key?(:in)
|
20
|
+
|
21
|
+
if @options[:is]
|
22
|
+
@min_value, @max_value = @options[:is], @options[:is]
|
23
|
+
elsif @options[:within]
|
24
|
+
@min_value, @max_value = @options[:within].first, @options[:within].last
|
25
|
+
elsif @options[:maximum]
|
26
|
+
@min_value, @max_value = nil, @options[:maximum]
|
27
|
+
elsif @options[:minimum]
|
28
|
+
@min_value, @max_value = @options[:minimum], nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
default_options :too_short => :too_short, :too_long => :too_long, :wrong_length => :wrong_length
|
33
|
+
|
34
|
+
protected
|
35
|
+
def allow_nil?
|
36
|
+
super(default_message_for(:too_short))
|
37
|
+
end
|
38
|
+
|
39
|
+
def allow_blank?
|
40
|
+
super(default_message_for(:too_short))
|
41
|
+
end
|
42
|
+
|
43
|
+
def less_than_min_length?
|
44
|
+
@min_value.nil? || @min_value <= 1 || bad?(value_for_length(@min_value - 1), default_message_for(:too_short))
|
45
|
+
end
|
46
|
+
|
47
|
+
def exactly_min_length?
|
48
|
+
@min_value.nil? || @min_value <= 0 || good?(value_for_length(@min_value), default_message_for(:too_short))
|
49
|
+
end
|
50
|
+
|
51
|
+
def more_than_max_length?
|
52
|
+
@max_value.nil? || bad?(value_for_length(@max_value + 1), default_message_for(:too_long))
|
53
|
+
end
|
54
|
+
|
55
|
+
def exactly_max_length?
|
56
|
+
@max_value.nil? || @min_value == @max_value || good?(value_for_length(@max_value), default_message_for(:too_long))
|
57
|
+
end
|
58
|
+
|
59
|
+
def value_for_length(value)
|
60
|
+
if @options[:with_kind_of]
|
61
|
+
[@options[:with_kind_of].new] * value
|
62
|
+
else
|
63
|
+
([@options.fetch(:token, 'x')] * value).join(@options.fetch(:separator, ''))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def interpolation_options
|
68
|
+
{ :minimum => @min_value, :maximum => @max_value }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the default message for the validation type.
|
72
|
+
# If user supplied :message, it will return it. Otherwise it will return
|
73
|
+
# wrong_length on :is validation and :too_short or :too_long in the other
|
74
|
+
# types.
|
75
|
+
#
|
76
|
+
def default_message_for(validation_type)
|
77
|
+
return :message if @options[:message]
|
78
|
+
@options.key?(:is) ? :wrong_length : validation_type
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Validates the length of the given attributes. You have also to supply
|
83
|
+
# one of the following options: minimum, maximum, is or within.
|
84
|
+
#
|
85
|
+
# Note: this method is also aliased as <tt>validate_size_of</tt>.
|
86
|
+
#
|
87
|
+
# == Options
|
88
|
+
#
|
89
|
+
# * <tt>:minimum</tt> - The minimum size of the attribute.
|
90
|
+
# * <tt>:maximum</tt> - The maximum size of the attribute.
|
91
|
+
# * <tt>:is</tt> - The exact size of the attribute.
|
92
|
+
# * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
|
93
|
+
# * <tt>:in</tt> - A synonym(or alias) for :within.
|
94
|
+
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
95
|
+
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
|
96
|
+
# * <tt>:too_short</tt> - value the test expects to find in <tt>errors[:attribute]</tt> when attribute is too short.
|
97
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
|
98
|
+
# * <tt>:too_long</tt> - value the test expects to find in <tt>errors[:attribute]</tt> when attribute is too long.
|
99
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
|
100
|
+
# * <tt>:wrong_length</tt> - value the test expects to find in <tt>errors[:attribute]</tt> when attribute is the wrong length.
|
101
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % range.last</tt>
|
102
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
|
103
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % value</tt>
|
104
|
+
#
|
105
|
+
# It also accepts an extra option called :with_kind_of. If you are validating
|
106
|
+
# the size of an association array, you have to specify the kind of the array
|
107
|
+
# being validated. For example, if your post accepts maximum 10 comments, you
|
108
|
+
# can do:
|
109
|
+
#
|
110
|
+
# should_validate_length_of :comments, :maximum => 10, :with_kind_of => Comment
|
111
|
+
#
|
112
|
+
# Finally, it also accepts :token and :separator, to specify how the
|
113
|
+
# tokenizer should work. For example, if you are splitting the attribute
|
114
|
+
# per word:
|
115
|
+
#
|
116
|
+
# validates_length_of :essay, :minimum => 100, :tokenizer => lambda {|str| str.scan(/\w+/) }
|
117
|
+
#
|
118
|
+
# You could do this:
|
119
|
+
#
|
120
|
+
# should_validate_length_of :essay, :minimum => 100, :token => "word", :separator => " "
|
121
|
+
#
|
122
|
+
# == Gotcha
|
123
|
+
#
|
124
|
+
# In Rails 2.3.x, when :message is supplied, it overwrites the messages
|
125
|
+
# supplied in :wrong_length, :too_short and :too_long. However, in earlier
|
126
|
+
# versions, Rails ignores the :message option.
|
127
|
+
#
|
128
|
+
# == Examples
|
129
|
+
#
|
130
|
+
# it { should validate_length_of(:password).within(6..20) }
|
131
|
+
# it { should validate_length_of(:password).maximum(20) }
|
132
|
+
# it { should validate_length_of(:password).minimum(6) }
|
133
|
+
# it { should validate_length_of(:age).is(18) }
|
134
|
+
#
|
135
|
+
# should_validate_length_of :password, :within => 6..20
|
136
|
+
# should_validate_length_of :password, :maximum => 20
|
137
|
+
# should_validate_length_of :password, :minimum => 6
|
138
|
+
# should_validate_length_of :age, :is => 18
|
139
|
+
#
|
140
|
+
# should_validate_length_of :password do |m|
|
141
|
+
# m.minimum 6
|
142
|
+
# m.maximum 20
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
def validate_length_of(*attributes, &block)
|
146
|
+
ValidateLengthOfMatcher.new(*attributes, &block).spec(self)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|