maksar-remarkable_activerecord 4.0.0.alpha6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class HaveScopeMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :scope_name
6
+ assertions :is_scope?, :options_match?
7
+
8
+ optionals :with, :splat => true
9
+
10
+ # Chained scopes taken from: http://m.onkey.org/2010/1/22/active-record-query-interface
11
+ optionals :where, :having, :select, :group, :order, :limit, :offset, :joins, :includes, :lock, :readonly, :from
12
+
13
+ protected
14
+
15
+ def is_scope?
16
+ @scope_object = if @options.key?(:with)
17
+ @options[:with] = [ @options[:with] ] unless Array === @options[:with]
18
+ subject_class.send(@scope_name, *@options[:with])
19
+ else
20
+ subject_class.send(@scope_name)
21
+ end
22
+
23
+ @scope_object.class == ::ActiveRecord::Relation && @scope_object.arel
24
+ end
25
+
26
+ def options_match?
27
+ @options.empty? || @scope_object.arel.to_sql == arel(subject_class, @options.except(:with)).to_sql
28
+ end
29
+
30
+ def interpolation_options
31
+ {
32
+ :options => (subject_class.respond_to?(:scoped) ? arel(subject_class, @options.except(:with)).to_sql : '{}'),
33
+ :actual => (@scope_object ? @scope_object.arel.to_sql : '{}')
34
+ }
35
+ end
36
+
37
+ private
38
+ def arel(model, scopes = nil)
39
+ return model.scoped unless scopes
40
+ scopes.inject(model.scoped) do |chain, (cond, option)|
41
+ chain.send(cond, option)
42
+ end.arel
43
+ end
44
+
45
+ end
46
+
47
+ # Ensures that the model has a named scope that returns an Relation object capable
48
+ # of building into relational algebra.
49
+ #
50
+ # == Options
51
+ #
52
+ # * <tt>with</tt> - Options to be sent to the named scope
53
+ #
54
+ # All options that the named scope would scope with Arel:
55
+ # :where, :having, :select, :group, :order, :limit, :offset, :joins, :includes, :lock, :readonly, :from
56
+ #
57
+ # Matching is done by constructing the Arel objects and testing for equality.
58
+ #
59
+ # == Examples
60
+ #
61
+ # it { should have_scope(:visible, :where => {:visible => true}) }
62
+ # it { should have_scope(:visible).where(:visible => true) }
63
+ #
64
+ # Passes for
65
+ #
66
+ # scope :visible, where(:visible => true)
67
+ #
68
+ # Or for
69
+ #
70
+ # scope :visible, lambda { where(:visible => true) }
71
+ #
72
+ # Or for
73
+ #
74
+ # def self.visible
75
+ # where(:visible => true)
76
+ # end
77
+ #
78
+ #
79
+ # You can test lambdas or methods that return ActiveRecord#scoped calls by fixing
80
+ # a defined parameter.
81
+ #
82
+ # it { should have_scope(:recent, :with => 5) }
83
+ # it { should have_scope(:recent, :with => 1) }
84
+ #
85
+ # Passes for
86
+ #
87
+ # scope :recent, lambda {|c| limit(c)}
88
+ #
89
+ # Or for
90
+ #
91
+ # def self.recent(c)
92
+ # limit(c)
93
+ # end
94
+ #
95
+ def have_scope(*args, &block)
96
+ HaveScopeMatcher.new(*args, &block).spec(self)
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,100 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidateAssociatedMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :collection => :associations, :as => :association, :block => true
6
+
7
+ optional :message
8
+ optional :builder, :block => true
9
+
10
+ collection_assertions :find_association?, :is_valid?
11
+ default_options :message => :invalid
12
+
13
+ protected
14
+
15
+ def find_association?
16
+ reflection = @subject.class.reflect_on_association(@association)
17
+
18
+ raise ScriptError, "Could not find association #{@association} on #{subject_class}." unless reflection
19
+
20
+ associated_object = if builder = @options[:builder] || @block
21
+ builder.call(@subject)
22
+ elsif [:belongs_to, :has_one].include?(reflection.macro)
23
+ @subject.send(:"build_#{@association}") rescue nil
24
+ else
25
+ @subject.send(@association).build rescue nil
26
+ end
27
+
28
+ raise ScriptError, "The association object #{@association} could not be built. You can give me " <<
29
+ ":builder as option or a block which returns an association." unless associated_object
30
+
31
+ raise ScriptError, "The associated object #{@association} is not invalid. You can give me " <<
32
+ ":builder as option or a block which returns an invalid association." if associated_object.valid?
33
+
34
+ return true
35
+ end
36
+
37
+ def is_valid?
38
+ return false if @subject.valid?
39
+
40
+ error_message_to_expect = error_message_from_model(@subject, @association, @options[:message])
41
+
42
+ assert_contains(@subject.errors[@association], error_message_to_expect)
43
+ end
44
+ end
45
+
46
+ # Ensures that the model is invalid if one of the associations given is
47
+ # invalid. It tries to build the association automatically. In has_one
48
+ # and belongs_to cases, it will build it like this:
49
+ #
50
+ # @model.build_association
51
+ # @project.build_manager
52
+ #
53
+ # In has_many and has_and_belongs_to_many to cases it will build it like
54
+ # this:
55
+ #
56
+ # @model.association.build
57
+ # @project.tasks.build
58
+ #
59
+ # The object returned MUST be invalid and it's likely the case, since the
60
+ # associated object is empty when calling build. However, if the associated
61
+ # object has to be manipulated to be invalid, you will have to give :builder
62
+ # as option or a block to manipulate it:
63
+ #
64
+ # should_validate_associated(:tasks) do |project|
65
+ # project.tasks.build(:captcha => 'i_am_a_bot')
66
+ # end
67
+ #
68
+ # In the case above, the associated object task is only invalid when the
69
+ # captcha attribute is set. So we give a block to the matcher that tell
70
+ # exactly how to build an invalid object.
71
+ #
72
+ # The example above can also be written as:
73
+ #
74
+ # should_validate_associated :tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_bot') }
75
+ #
76
+ # == Options
77
+ #
78
+ # * <tt>:builder</tt> - a proc to build the association
79
+ #
80
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
81
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
82
+ #
83
+ # == Examples
84
+ #
85
+ # should_validate_associated :tasks
86
+ # should_validate_associated :tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_bot') }
87
+ #
88
+ # should_validate_associated :tasks do |m|
89
+ # m.builder { |p| p.tasks.build(:captcha => 'i_am_a_bot') }
90
+ # end
91
+ #
92
+ # it { should validate_associated(:tasks) }
93
+ # it { should validate_associated(:tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_bot') }) }
94
+ #
95
+ def validate_associated(*args, &block)
96
+ ValidateAssociatedMatcher.new(*args, &block).spec(self)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,233 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidateUniquenessOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :collection => :attributes, :as => :attribute
6
+
7
+ optional :message
8
+ optional :scope, :splat => true
9
+ optional :case_sensitive, :allow_nil, :allow_blank, :default => true
10
+
11
+ collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
12
+ :valid_with_new_scope?, :allow_nil?, :allow_blank?
13
+
14
+ default_options :message => :taken
15
+
16
+ before_assert do
17
+ @options[:scope] = [*@options[:scope]].compact if @options[:scope]
18
+ end
19
+
20
+ private
21
+
22
+ # Tries to find an object in the database. If allow_nil and/or allow_blank
23
+ # is given, we must find a record which is not nil or not blank.
24
+ #
25
+ # We should also ensure that the object retrieved from the database
26
+ # is not the @subject.
27
+ #
28
+ # If any of these attempts fail, an error is raised.
29
+ #
30
+ def find_first_object?
31
+ conditions, message = if @options[:allow_nil]
32
+ [ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
33
+ elsif @options[:allow_blank]
34
+ [ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
35
+ else
36
+ [ [], "" ]
37
+ end
38
+
39
+ unless @subject.new_record?
40
+ primary_key = subject_class.primary_key
41
+
42
+ message << " which is different from the subject record (the object being validated is the same as the one in the database)"
43
+ conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
44
+ end
45
+
46
+ options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
47
+
48
+ return true if @existing = subject_class.find(:first, options)
49
+ raise ScriptError, "could not find a #{subject_class} record in the database" + message
50
+ end
51
+
52
+ # Set subject scope to be equal to the object found.
53
+ #
54
+ def responds_to_scope?
55
+ (@options[:scope] || []).each do |scope|
56
+ setter = :"#{scope}="
57
+
58
+ return false, :method => setter unless @subject.respond_to?(setter)
59
+ return false, :method => scope unless @existing.respond_to?(scope)
60
+
61
+ @subject.send(setter, @existing.send(scope))
62
+ end
63
+ true
64
+ end
65
+
66
+ # Check if the attribute given is valid and if the validation fails for equal values.
67
+ #
68
+ def is_unique?
69
+ @value = @existing.send(@attribute)
70
+ return bad?(@value)
71
+ end
72
+
73
+ # If :case_sensitive is given and it's false, we swap the case of the
74
+ # value used in :is_unique? and see if the test object remains valid.
75
+ #
76
+ # If :case_sensitive is given and it's true, we swap the case of the
77
+ # value used in is_unique? and see if the test object is not valid.
78
+ #
79
+ # This validation will only occur if the test object is a String.
80
+ #
81
+ def case_sensitive?
82
+ return true unless @value.is_a?(String)
83
+ assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
84
+ end
85
+
86
+ # Now test that the object is valid when changing the scoped attribute.
87
+ #
88
+ def valid_with_new_scope?
89
+ (@options[:scope] || []).each do |scope|
90
+ setter = :"#{scope}="
91
+
92
+ previous_scope_value = @subject.send(scope)
93
+ @subject.send(setter, new_value_for_scope(scope))
94
+ return false, :method => scope unless good?(@value)
95
+
96
+ @subject.send(setter, previous_scope_value)
97
+ end
98
+ true
99
+ end
100
+
101
+ # Change the existing object attribute to nil to run allow nil
102
+ # validations. If we find any problem while updating the @existing
103
+ # record, it's because we can't save nil values in the database. So it
104
+ # passes when :allow_nil is false, but should raise an error when
105
+ # :allow_nil is true
106
+ #
107
+ def allow_nil?
108
+ return true unless @options.key?(:allow_nil)
109
+
110
+ begin
111
+ @existing.update_attribute(@attribute, nil)
112
+ rescue ::ActiveRecord::StatementInvalid => e
113
+ raise ScriptError, "You declared that #{@attribute} accepts nil values in validate_uniqueness_of, " <<
114
+ "but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
115
+ return true
116
+ end
117
+
118
+ super
119
+ end
120
+
121
+ # Change the existing object attribute to blank to run allow blank
122
+ # validation. It uses the same logic as :allow_nil.
123
+ #
124
+ def allow_blank?
125
+ return true unless @options.key?(:allow_blank)
126
+
127
+ begin
128
+ @existing.update_attribute(@attribute, '')
129
+ rescue ::ActiveRecord::StatementInvalid => e
130
+ raise ScriptError, "You declared that #{@attribute} accepts blank values in validate_uniqueness_of, " <<
131
+ "but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
132
+ return true
133
+ end
134
+
135
+ super
136
+ end
137
+
138
+ # Returns a value to be used as new scope. It deals with four different
139
+ # cases: date, time, boolean and stringfiable (everything that can be
140
+ # converted to a string and the next value makes sense)
141
+ #
142
+ def new_value_for_scope(scope)
143
+ column_type = if @existing.respond_to?(:column_for_attribute)
144
+ @existing.column_for_attribute(scope)
145
+ else
146
+ nil
147
+ end
148
+
149
+ case column_type.type
150
+ when :int, :integer, :float, :decimal
151
+ new_value_for_stringfiable_scope(scope)
152
+ when :datetime, :timestamp, :time
153
+ Time.now + 10000
154
+ when :date
155
+ Date.today + 100
156
+ when :boolean
157
+ !@existing.send(scope)
158
+ else
159
+ new_value_for_stringfiable_scope(scope)
160
+ end
161
+ end
162
+
163
+ # Returns a value to be used as scope by generating a range of values
164
+ # and searching for them in the database.
165
+ #
166
+ def new_value_for_stringfiable_scope(scope)
167
+ values = [(@existing.send(scope) || 999).next.to_s]
168
+
169
+ # Generate a range of values to search in the database
170
+ 100.times do
171
+ values << values.last.next
172
+ end
173
+ conditions = { scope => values, @attribute => @value }
174
+
175
+ # Get values from the database, get the scope attribute and map them to string.
176
+ db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
177
+ db_values.map!{ |r| r.send(scope).to_s }
178
+
179
+ if value_to_return = (values - db_values).first
180
+ value_to_return
181
+ else
182
+ raise ScriptError, "Tried to find an unique scope value for #{scope} but I could not. " <<
183
+ "The conditions hash was #{conditions.inspect} and it returned all records."
184
+ end
185
+ end
186
+ end
187
+
188
+ # Ensures that the model cannot be saved if one of the attributes listed
189
+ # is not unique.
190
+ #
191
+ # Requires an existing record in the database. If you supply :allow_nil as
192
+ # option, you need to have in the database a record which is not nil in the
193
+ # given attributes. The same is required for allow_blank option.
194
+ #
195
+ # Notice that the record being validate should not be the same as in the
196
+ # database. In other words, you can't do this:
197
+ #
198
+ # subject { Post.create!(@valid_attributes) }
199
+ # should_validate_uniqueness_of :title
200
+ #
201
+ # But don't worry, if you eventually do that, a helpful error message
202
+ # will be raised.
203
+ #
204
+ # == Options
205
+ #
206
+ # * <tt>:scope</tt> - field(s) to scope the uniqueness to.
207
+ # * <tt>:case_sensitive</tt> - the matcher look for an exact match.
208
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
209
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
210
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors[:attribute]</tt>.
211
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
212
+ #
213
+ # == Examples
214
+ #
215
+ # it { should validate_uniqueness_of(:keyword, :username) }
216
+ # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
217
+ # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
218
+ #
219
+ # should_validate_uniqueness_of :keyword, :username
220
+ # should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
221
+ # should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
222
+ #
223
+ # should_validate_uniqueness_of :email do |m|
224
+ # m.scope = name
225
+ # m.case_sensitive = false
226
+ # end
227
+ #
228
+ def validate_uniqueness_of(*attributes, &block)
229
+ ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
230
+ end
231
+ end
232
+ end
233
+ end
data/locale/en.yml ADDED
@@ -0,0 +1,264 @@
1
+ en:
2
+ remarkable:
3
+ active_record:
4
+ describe:
5
+ each: "%{key} is %{value}"
6
+ prepend: "when "
7
+ connector: " and "
8
+ expectations:
9
+ allow_nil: "%{subject_name} to %{not}allow nil values for %{attribute}"
10
+ allow_blank: "%{subject_name} to %{not}allow blank values for %{attribute}"
11
+ optionals:
12
+ allow_nil:
13
+ positive: "allowing nil values"
14
+ negative: "not allowing nil values"
15
+ allow_blank:
16
+ positive: "allowing blank values"
17
+ negative: "not allowing blank values"
18
+
19
+ accept_nested_attributes_for:
20
+ description: "accept nested attributes for %{associations}"
21
+ expectations:
22
+ association_exists: "%{subject_name} to have association %{association}, but does not"
23
+ is_autosave: "%{subject_name} to have association %{association} with autosave true, got false"
24
+ responds_to_attributes: "%{subject_name} to respond to :%{association}_attributes=, but does not"
25
+ allows_destroy: "%{subject_name} with allow destroy equals to %{allow_destroy}, got %{actual}"
26
+ accepts: "%{subject_name} to accept attributes %{attributes} for %{association}, but does not"
27
+ rejects: "%{subject_name} to reject attributes %{attributes} for %{association}, but does not"
28
+ optionals:
29
+ allow_destroy:
30
+ positive: "allowing destroy"
31
+ negative: "not allowing destroy"
32
+ accept:
33
+ positive: "accepting %{sentence}"
34
+ reject:
35
+ positive: "rejecting %{sentence}"
36
+
37
+ allow_values_for:
38
+ description: "allow %{in} as values for %{attributes}"
39
+ expectations:
40
+ is_valid: "%{subject_name} to be valid when %{attribute} is set to %{value}"
41
+
42
+ allow_mass_assignment_of:
43
+ description: "allow mass assignment of %{attributes}"
44
+ expectations:
45
+ allows: "%{subject_name} to allow mass assignment (%{subject_name} is protecting %{protected_attributes})"
46
+ is_protected: "%{subject_name} to allow mass assignment of %{attribute} (%{subject_name} is protecting %{attribute})"
47
+ is_accessible: "%{subject_name} to allow mass assignment of %{attribute} (%{subject_name} has not made %{attribute} accessible)"
48
+ negative_expectations:
49
+ allows: "%{subject_name} to allow mass assignment (%{subject_name} made %{accessible_attributes} accessible)"
50
+ is_protected: "%{subject_name} to allow mass assignment of %{attribute} (%{subject_name} is not protecting %{attribute})"
51
+ is_accessible: "%{subject_name} to allow mass assignment of %{attribute} (%{subject_name} has made %{attribute} accessible)"
52
+
53
+ association:
54
+ belongs_to: belong to
55
+ has_many: have many
56
+ has_and_belongs_to_many: have and belong to many
57
+ has_one: have one
58
+ description: "%{macro} %{associations}"
59
+ expectations:
60
+ association_exists: "%{subject_name} records %{macro} %{association}, but the association does not exist"
61
+ macro_matches: "%{subject_name} records %{macro} %{association}, got %{subject_name} records %{actual_macro} %{association}"
62
+ through_exists: "%{subject_name} records %{macro} %{association} through %{through}, through association does not exist"
63
+ source_exists: "%{subject_name} records %{macro} %{association} through %{through}, source association does not exist"
64
+ klass_exists: "%{subject_name} records %{macro} %{association}, but the association class does not exist"
65
+ join_table_exists: "join table %{join_table} to exist, but does not"
66
+ foreign_key_exists: "foreign key %{foreign_key} to exist on %{foreign_key_table}, but does not"
67
+ polymorphic_exists: "%{subject_table} to have %{polymorphic_column} as column, but does not"
68
+ counter_cache_exists: "%{reflection_table} to have %{counter_cache_column} as column, but does not"
69
+ options_match: "%{subject_name} records %{macro} %{association} with options %{options}, got %{actual}"
70
+ optionals:
71
+ through:
72
+ positive: "through %{value}"
73
+ source:
74
+ positive: "with source %{inspect}"
75
+ source_type:
76
+ positive: "with source type %{inspect}"
77
+ class_name:
78
+ positive: "with class name %{inspect}"
79
+ foreign_key:
80
+ positive: "with foreign key %{inspect}"
81
+ dependent:
82
+ positive: "with dependent %{inspect}"
83
+ join_table:
84
+ positive: "with join table %{inspect}"
85
+ uniq:
86
+ positive: "with unique records"
87
+ negative: "without unique records"
88
+ readonly:
89
+ positive: "with readonly records"
90
+ negative: "without readonly records"
91
+ validate:
92
+ positive: "validating associated records"
93
+ negative: "not validating associated records"
94
+ autosave:
95
+ positive: "autosaving associated records"
96
+ negative: "not autosaving associated records"
97
+ as:
98
+ positive: "through the polymorphic interface %{inspect}"
99
+ counter_cache:
100
+ positive: "with counter cache %{inspect}"
101
+ negative: "without counter cache"
102
+ select:
103
+ positive: "selecting %{inspect}"
104
+ conditions:
105
+ positive: "with conditions %{inspect}"
106
+ include:
107
+ positive: "including %{inspect}"
108
+ group:
109
+ positive: "grouping by %{inspect}"
110
+ having:
111
+ positive: "having %{inspect}"
112
+ order:
113
+ positive: "with order %{inspect}"
114
+ limit:
115
+ positive: "with limit %{inspect}"
116
+ offset:
117
+ positive: "with offset %{inspect}"
118
+
119
+ have_column:
120
+ description: "have column(s) named %{columns}"
121
+ expectations:
122
+ column_exists: "%{subject_name} to have column named %{column}"
123
+ options_match: "%{subject_name} to have column %{column} with options %{options}, got %{actual}"
124
+ optionals:
125
+ type:
126
+ positive: "with type %{inspect}"
127
+ null:
128
+ positive: "allowing null values"
129
+ negative: "not allowing null values"
130
+ default:
131
+ positive: "with default value %{inspect}"
132
+ negative: "with default value %{inspect}"
133
+ limit:
134
+ positive: "with limit %{inspect}"
135
+
136
+ have_default_scope:
137
+ description: "have a default scope with %{options}"
138
+ expectations:
139
+ options_match: "default scope with %{options}, got %{actual}"
140
+
141
+ have_index:
142
+ description: "have index for column(s) %{columns}"
143
+ expectations:
144
+ index_exists: "index %{column} to exist on table %{table_name}"
145
+ is_unique: "index on %{column} with unique equals to %{unique}, got %{actual}"
146
+ optionals:
147
+ unique:
148
+ positive: "with unique values"
149
+ negative: "with non unique values"
150
+ table_name:
151
+ positive: "on table %{value}"
152
+
153
+ have_readonly_attributes:
154
+ description: "make %{attributes} read-only"
155
+ expectations:
156
+ is_readonly: "%{subject_name} to make %{attribute} read-only, got %{actual}"
157
+
158
+ have_scope:
159
+ description: "have to scope itself to %{options} when %{scope_name} is called"
160
+ expectations:
161
+ is_scope: "%{scope_name} when called on %{subject_name} return an instance of ActiveRecord::NamedScope::Scope"
162
+ options_match: "%{scope_name} when called on %{subject_name} scope to %{options}, got %{actual}"
163
+ optionals:
164
+ with:
165
+ positive: "with %{inspect} as argument"
166
+
167
+ validate_acceptance_of:
168
+ description: "require %{attributes} to be accepted"
169
+ expectations:
170
+ requires_acceptance: "%{subject_name} to be invalid if %{attribute} is not accepted"
171
+ accept_is_valid: "%{subject_name} to be valid when %{attribute} is accepted with value %{accept}"
172
+ optionals:
173
+ accept:
174
+ positive: "with value %{inspect}"
175
+
176
+ validate_associated:
177
+ description: "require associated %{associations} to be valid"
178
+ expectations:
179
+ is_valid: "%{subject_name} to be invalid when %{association} is invalid"
180
+
181
+ validate_confirmation_of:
182
+ description: "require %{attributes} to be confirmed"
183
+ expectations:
184
+ responds_to_confirmation: "%{subject_name} instance responds to %{attribute}_confirmation"
185
+ confirms: "%{subject_name} to be valid only when %{attribute} is confirmed"
186
+
187
+ validate_exclusion_of:
188
+ description: "ensure exclusion of %{attributes} in %{in}"
189
+ expectations:
190
+ is_valid: "%{subject_name} to be valid when %{attribute} is set to %{value}"
191
+ is_invalid: "%{subject_name} to be invalid when %{attribute} is set to %{value}"
192
+
193
+ validate_inclusion_of:
194
+ description: "ensure inclusion of %{attributes} in %{in}"
195
+ expectations:
196
+ is_valid: "%{subject_name} to be valid when %{attribute} is set to %{value}"
197
+ is_invalid: "%{subject_name} to be invalid when %{attribute} is set to %{value}"
198
+
199
+ validate_length_of:
200
+ description: "ensure length of %{attributes}"
201
+ expectations:
202
+ less_than_min_length: "%{subject_name} to be invalid when %{attribute} length is less than %{minimum} characters"
203
+ exactly_min_length: "%{subject_name} to be valid when %{attribute} length is %{minimum} characters"
204
+ more_than_max_length: "%{subject_name} to be invalid when %{attribute} length is more than %{maximum} characters"
205
+ exactly_max_length: "%{subject_name} to be valid when %{attribute} length is %{maximum} characters"
206
+ optionals:
207
+ within:
208
+ positive: "is within %{inspect} characters"
209
+ maximum:
210
+ positive: "is maximum %{inspect} characters"
211
+ minimum:
212
+ positive: "is minimum %{inspect} characters"
213
+ is:
214
+ positive: "is equal to %{inspect} characters"
215
+ with_kind_of:
216
+ positive: "with kind of %{value}"
217
+
218
+ validate_numericality_of:
219
+ description: "ensure numericality of %{attributes}"
220
+ expectations:
221
+ only_numeric_values: "%{subject_name} to allow only numeric values for %{attribute}"
222
+ only_integer: "%{subject_name} to %{not}allow only integer values for %{attribute}"
223
+ only_even: "%{subject_name} to allow only even values for %{attribute}"
224
+ only_odd: "%{subject_name} to allow only odd values for %{attribute}"
225
+ equals_to: "%{subject_name} to be valid only when %{attribute} is equal to %{count}"
226
+ more_than_maximum: "%{subject_name} to be invalid when %{attribute} is greater than %{count}"
227
+ less_than_minimum: "%{subject_name} to be invalid when %{attribute} is less than %{count}"
228
+ optionals:
229
+ only_integer:
230
+ positive: "allowing only integer values"
231
+ odd:
232
+ positive: "allowing only odd values"
233
+ even:
234
+ positive: "allowing only even values"
235
+ equal_to:
236
+ positive: "is equal to %{inspect}"
237
+ less_than:
238
+ positive: "is less than %{inspect}"
239
+ greater_than:
240
+ positive: "is greater than %{inspect}"
241
+ less_than_or_equal_to:
242
+ positive: "is less than or equal to %{inspect}"
243
+ greater_than_or_equal_to:
244
+ positive: "is greater than or equal to %{inspect}"
245
+
246
+ validate_presence_of:
247
+ description: "require %{attributes} to be set"
248
+ expectations:
249
+ allow_nil: "%{subject_name} to require %{attribute} to be set"
250
+
251
+ validate_uniqueness_of:
252
+ description: "require unique values for %{attributes}"
253
+ expectations:
254
+ responds_to_scope: "%{subject_name} instance responds to %{method}"
255
+ is_unique: "%{subject_name} to require unique values for %{attribute}"
256
+ case_sensitive: "%{subject_name} to %{not}be case sensitive on %{attribute} validation"
257
+ valid_with_new_scope: "%{subject_name} to be valid when %{attribute} scope (%{method}) change"
258
+ optionals:
259
+ scope:
260
+ positive: "scoped to %{sentence}"
261
+ case_sensitive:
262
+ positive: "case sensitive"
263
+ negative: "case insensitive"
264
+