maksar-remarkable_activerecord 4.0.0.alpha6
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 +103 -0
- data/lib/remarkable/active_record.rb +14 -0
- data/lib/remarkable/active_record/base.rb +6 -0
- data/lib/remarkable/active_record/matchers/accept_nested_attributes_for_matcher.rb +138 -0
- data/lib/remarkable/active_record/matchers/allow_mass_assignment_of_matcher.rb +74 -0
- data/lib/remarkable/active_record/matchers/association_matcher.rb +283 -0
- data/lib/remarkable/active_record/matchers/have_column_matcher.rb +68 -0
- data/lib/remarkable/active_record/matchers/have_index_matcher.rb +73 -0
- data/lib/remarkable/active_record/matchers/have_readonly_attributes_matcher.rb +30 -0
- data/lib/remarkable/active_record/matchers/have_scope_matcher.rb +101 -0
- data/lib/remarkable/active_record/matchers/validate_associated_matcher.rb +100 -0
- data/lib/remarkable/active_record/matchers/validate_uniqueness_of_matcher.rb +233 -0
- data/locale/en.yml +264 -0
- data/remarkable_activerecord.gemspec +63 -0
- metadata +106 -0
|
@@ -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
|
+
|