remarkable_activemodel 4.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|