carlosbrando-remarkable 0.0.99 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -5
- data/Manifest.txt +53 -35
- data/PostInstall.txt +1 -6
- data/README.rdoc +109 -15
- data/Rakefile +29 -29
- data/lib/remarkable/active_record/README.markdown +378 -0
- data/lib/remarkable/active_record/active_record.rb +12 -11
- data/lib/remarkable/active_record/helpers.rb +215 -5
- data/lib/remarkable/active_record/macros/associations/association_matcher.rb +242 -0
- data/lib/remarkable/active_record/macros/callbacks/callback_matcher.rb +46 -0
- data/lib/remarkable/active_record/macros/database/column_matcher.rb +122 -0
- data/lib/remarkable/active_record/macros/database/index_matcher.rb +103 -0
- data/lib/remarkable/active_record/macros/validations/allow_mass_assignment_of_matcher.rb +52 -0
- data/lib/remarkable/active_record/macros/validations/ensure_value_in_list_matcher.rb +83 -0
- data/lib/remarkable/active_record/macros/validations/ensure_value_in_range_matcher.rb +172 -0
- data/lib/remarkable/active_record/macros/validations/have_class_methods_matcher.rb +54 -0
- data/lib/remarkable/active_record/macros/validations/have_instance_methods_matcher.rb +54 -0
- data/lib/remarkable/active_record/macros/validations/have_named_scope_matcher.rb +94 -0
- data/lib/remarkable/active_record/macros/validations/have_readonly_attributes_matcher.rb +48 -0
- data/lib/remarkable/active_record/macros/validations/protect_attributes_matcher.rb +51 -0
- data/lib/remarkable/active_record/macros/validations/validate_acceptance_of_matcher.rb +79 -0
- data/lib/remarkable/active_record/macros/validations/validate_associated_matcher.rb +177 -0
- data/lib/remarkable/active_record/macros/validations/validate_confirmation_of_matcher.rb +74 -0
- data/lib/remarkable/active_record/macros/validations/validate_exclusion_of_matcher.rb +38 -0
- data/lib/remarkable/active_record/macros/validations/validate_format_of_matcher.rb +33 -0
- data/lib/remarkable/active_record/macros/validations/validate_inclusion_of_matcher.rb +45 -0
- data/lib/remarkable/active_record/macros/validations/validate_length_of_matcher.rb +248 -0
- data/lib/remarkable/active_record/macros/validations/validate_numericality_of_matcher.rb +206 -0
- data/lib/remarkable/active_record/macros/validations/validate_presence_of_matcher.rb +72 -0
- data/lib/remarkable/active_record/macros/validations/validate_uniqueness_of_matcher.rb +222 -0
- data/lib/remarkable/active_record/macros.rb +52 -0
- data/lib/remarkable/assertions.rb +29 -0
- data/lib/remarkable/controller/README.markdown +147 -0
- data/lib/remarkable/controller/controller.rb +11 -6
- data/lib/remarkable/controller/helpers.rb +4 -38
- data/lib/remarkable/controller/macros/assign_matcher.rb +85 -0
- data/lib/remarkable/controller/macros/filter_params_matcher.rb +63 -0
- data/lib/remarkable/controller/macros/metadata_matcher.rb +63 -0
- data/lib/remarkable/controller/macros/render_with_layout_matcher.rb +75 -0
- data/lib/remarkable/controller/macros/respond_with_content_type_matcher.rb +60 -0
- data/lib/remarkable/controller/macros/respond_with_matcher.rb +62 -0
- data/lib/remarkable/controller/macros/return_from_session_matcher.rb +58 -0
- data/lib/remarkable/controller/macros/route_matcher.rb +75 -0
- data/lib/remarkable/controller/macros/set_the_flash_to_matcher.rb +60 -0
- data/lib/remarkable/controller/macros.rb +78 -0
- data/lib/remarkable/dsl.rb +239 -0
- data/lib/remarkable/example/example_methods.rb +27 -7
- data/lib/remarkable/helpers.rb +28 -0
- data/lib/remarkable/matcher_base.rb +64 -0
- data/lib/remarkable/private_helpers.rb +10 -115
- data/lib/remarkable/rails.rb +27 -0
- data/lib/remarkable.rb +13 -5
- data/remarkable.gemspec +43 -0
- data/spec/controllers/posts_controller_spec.rb +58 -4
- data/spec/controllers/users_controller_spec.rb +1 -0
- data/spec/fixtures/fleas.yml +10 -0
- data/spec/fixtures/users.yml +7 -0
- data/spec/models/address_spec.rb +44 -0
- data/spec/models/dog_spec.rb +64 -3
- data/spec/models/flea_spec.rb +30 -0
- data/spec/models/post_spec.rb +36 -2
- data/spec/models/product_spec.rb +73 -8
- data/spec/models/tag_spec.rb +2 -2
- data/spec/models/tagging_spec.rb +24 -0
- data/spec/models/user_spec.rb +206 -21
- data/spec/other/custom_macros_spec.rb +27 -0
- data/spec/other/my_own_matcher_spec.rb +11 -0
- data/spec/other/private_helpers_spec.rb +31 -0
- data/spec/rails_root/app/controllers/posts_controller.rb +2 -0
- data/spec/rails_root/app/models/address.rb +2 -2
- data/spec/rails_root/app/models/flea.rb +4 -0
- data/spec/rails_root/app/models/pets/dog.rb +12 -0
- data/spec/rails_root/app/models/product.rb +7 -5
- data/spec/rails_root/app/models/tagging.rb +2 -0
- data/spec/rails_root/app/models/user.rb +20 -5
- data/spec/rails_root/app/views/layouts/posts.rhtml +8 -6
- data/spec/rails_root/config/database.yml +1 -2
- data/spec/rails_root/config/environment.rb +3 -1
- data/spec/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
- data/spec/rails_root/config/locales/en.yml +8 -0
- data/spec/rails_root/db/migrate/001_create_users.rb +2 -2
- data/spec/rails_root/db/migrate/005_create_dogs.rb +1 -0
- data/spec/rails_root/db/migrate/011_add_fleas_color.rb +10 -0
- data/spec/rails_root/db/migrate/012_add_fleas_address.rb +10 -0
- data/spec/rails_root/spec/remarkable_macros/.keep +0 -0
- data/spec/rails_root/vendor/plugins/my_plugin/remarkable_macros/.keep +0 -0
- data/spec/spec_helper.rb +0 -2
- metadata +63 -43
- data/lib/remarkable/active_record/macros/associations/belong_to.rb +0 -81
- data/lib/remarkable/active_record/macros/associations/have_and_belong_to_many.rb +0 -77
- data/lib/remarkable/active_record/macros/associations/have_many.rb +0 -160
- data/lib/remarkable/active_record/macros/associations/have_one.rb +0 -133
- data/lib/remarkable/active_record/macros/database/have_db_column.rb +0 -81
- data/lib/remarkable/active_record/macros/database/have_db_columns.rb +0 -73
- data/lib/remarkable/active_record/macros/database/have_indices.rb +0 -75
- data/lib/remarkable/active_record/macros/validations/allow_values_for.rb +0 -103
- data/lib/remarkable/active_record/macros/validations/ensure_length_at_least.rb +0 -97
- data/lib/remarkable/active_record/macros/validations/ensure_length_in_range.rb +0 -134
- data/lib/remarkable/active_record/macros/validations/ensure_length_is.rb +0 -106
- data/lib/remarkable/active_record/macros/validations/ensure_value_in_range.rb +0 -117
- data/lib/remarkable/active_record/macros/validations/have_class_methods.rb +0 -74
- data/lib/remarkable/active_record/macros/validations/have_instance_methods.rb +0 -74
- data/lib/remarkable/active_record/macros/validations/have_named_scope.rb +0 -148
- data/lib/remarkable/active_record/macros/validations/have_readonly_attributes.rb +0 -81
- data/lib/remarkable/active_record/macros/validations/only_allow_numeric_values_for.rb +0 -89
- data/lib/remarkable/active_record/macros/validations/protect_attributes.rb +0 -89
- data/lib/remarkable/active_record/macros/validations/require_acceptance_of.rb +0 -94
- data/lib/remarkable/active_record/macros/validations/require_attributes.rb +0 -94
- data/lib/remarkable/active_record/macros/validations/require_unique_attributes.rb +0 -146
- data/lib/remarkable/controller/macros/assign_to.rb +0 -110
- data/lib/remarkable/controller/macros/filter_params.rb +0 -52
- data/lib/remarkable/controller/macros/redirect_to.rb +0 -24
- data/lib/remarkable/controller/macros/render_a_form.rb +0 -23
- data/lib/remarkable/controller/macros/render_template.rb +0 -18
- data/lib/remarkable/controller/macros/render_with_layout.rb +0 -61
- data/lib/remarkable/controller/macros/respond_with.rb +0 -86
- data/lib/remarkable/controller/macros/respond_with_content_type.rb +0 -45
- data/lib/remarkable/controller/macros/return_from_session.rb +0 -45
- data/lib/remarkable/controller/macros/route.rb +0 -91
- data/lib/remarkable/controller/macros/set_the_flash_to.rb +0 -58
- data/spec/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,378 @@
|
|
1
|
+
h1. ActiveRecord Macros
|
2
|
+
|
3
|
+
For each example below, we will show you the Rspec way and in the Macro (Shoulda) way. Choose the one that pleases you the most. :)
|
4
|
+
|
5
|
+
h2. Associations
|
6
|
+
|
7
|
+
h3. belong_to
|
8
|
+
|
9
|
+
Ensure that the belongs_to relationship exists.
|
10
|
+
|
11
|
+
<pre><code> should_belong_to :parent
|
12
|
+
it { should belong_to(:parent) }</code></pre>
|
13
|
+
|
14
|
+
h3. have_and_belong_to_many
|
15
|
+
|
16
|
+
Ensures that the has_and_belongs_to_many relationship exists, and that the join table is in place.
|
17
|
+
|
18
|
+
<pre><code> should_have_and_belong_to_many :posts, :cars
|
19
|
+
it{ should have_and_belong_to_many :posts, :cars }</code></pre>
|
20
|
+
|
21
|
+
h3. have_many
|
22
|
+
|
23
|
+
Ensures that the has_many relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
|
24
|
+
|
25
|
+
Options:
|
26
|
+
* :through - association name for has_many :through
|
27
|
+
* :dependent - tests that the association makes use of the dependent option.
|
28
|
+
|
29
|
+
<pre><code> should_have_many :friends
|
30
|
+
should_have_many :enemies, :through => :friends
|
31
|
+
should_have_many :enemies, :dependent => :destroy
|
32
|
+
|
33
|
+
it{ should have_many(:friends) }
|
34
|
+
it{ should have_many(:enemies, :through => :friends) }
|
35
|
+
it{ should have_many(:enemies, :dependent => :destroy) }</code></pre>
|
36
|
+
|
37
|
+
h3. have_one
|
38
|
+
|
39
|
+
Ensure that the has_one relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
|
40
|
+
|
41
|
+
Options:
|
42
|
+
* :dependent - tests that the association makes use of the dependent option.
|
43
|
+
|
44
|
+
<pre><code> should_have_one :god
|
45
|
+
it { should have_one(:god) }</code></pre>
|
46
|
+
|
47
|
+
h2. Database
|
48
|
+
|
49
|
+
h3. have_db_column
|
50
|
+
|
51
|
+
Ensure that the given column is defined on the models backing SQL table. The options are the same as the instance variables defined on the column definition: :precision, :limit, :default, :null, :primary, :type, :scale, and :sql_type.
|
52
|
+
|
53
|
+
<pre><code> should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
|
54
|
+
:null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
|
55
|
+
|
56
|
+
it { should have_db_column(:email, :type => "string", :default => nil, :precision => nil, :limit => 255,
|
57
|
+
:null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)') }</code></pre>
|
58
|
+
|
59
|
+
h3. have_db_columns
|
60
|
+
|
61
|
+
Ensure that the given columns are defined on the models backing SQL table.
|
62
|
+
|
63
|
+
<pre><code> should_have_db_columns :id, :email, :name, :created_at
|
64
|
+
it { should have_db_columns :id, :email, :name, :created_at }</code></pre>
|
65
|
+
|
66
|
+
h3. have_indices
|
67
|
+
|
68
|
+
Ensures that there are DB indices on the given columns or tuples of columns. Also aliased to @should_have_index@ for readability.
|
69
|
+
|
70
|
+
<pre><code> should_have_indices :email, :name, [:commentable_type, :commentable_id]
|
71
|
+
should_have_index :age
|
72
|
+
|
73
|
+
it { should have_indices(:email, :name, [:commentable_type, :commentable_id]) }
|
74
|
+
it { should have_index(:age) }</code></pre>
|
75
|
+
|
76
|
+
h3. have_index
|
77
|
+
|
78
|
+
Alias for @should_have_indices@.
|
79
|
+
|
80
|
+
h2. Validations
|
81
|
+
|
82
|
+
A word about validations: we currently cover *ALL* Active Record validations with *ALL* options. This is great, but you have to know a few things about it.
|
83
|
+
|
84
|
+
The first thing is that we only validate what you explicitly give. In other words, if you do:
|
85
|
+
|
86
|
+
should_validate_numericality_of :age
|
87
|
+
|
88
|
+
It will only test if it allows only numbers for age. On Active Record, such validation implicitly states that :only_integer is false and :allow_nil is false, but those options are not tested by default. On Remarkable you have to explicitly give them in order to be tested:
|
89
|
+
|
90
|
+
should_validate_numericality_of :age, :only_integer => false, :allow_nil => false
|
91
|
+
|
92
|
+
Why? Because tests are part of your software specification, it should not have defaults. You must declare it in order to understand what it does or not. The only default value that is adopted is the :message and we will explain why next.
|
93
|
+
|
94
|
+
The second thing is understand how @validate@ macros work:
|
95
|
+
|
96
|
+
should_validate_inclusion_of :gender, %w(m f)
|
97
|
+
|
98
|
+
In this case, it will set a non valid value in age and search for the :inclusion message ("not included in list") in the error messages. If the message exists, your validation is working, otherwise, it's not. The thing is, if you change your error message, it will not be able to found the message and the test will always pass.
|
99
|
+
|
100
|
+
This is valid for all rspec matchers and not only Remarkable. Actually, we are the only one that fully supports I18n error messages. In other words, if you change the error message in through the I18n YAML files, everything will work properly. But when you set the message in your model like this:
|
101
|
+
|
102
|
+
validates_inclusion_of :gender, %w(m f), :message => "woah?! what are you then?"
|
103
|
+
|
104
|
+
You have also to change the message in your tests.
|
105
|
+
|
106
|
+
should_validate_inclusion_of :gender, %w(m f), :message => "woah?! what are you then?"
|
107
|
+
|
108
|
+
The third is, in any macro, if an instance variable has been created named after the model being tested, then the macro will use it to test. Otherwise, it will create a new instance to test against.
|
109
|
+
|
110
|
+
Deal? Now we are good to go!
|
111
|
+
|
112
|
+
h3. allow_mass_assignment_of
|
113
|
+
|
114
|
+
Ensures that the attribute can be set on mass update.
|
115
|
+
|
116
|
+
should_allow_mass_assignment_of :email, :name
|
117
|
+
it { should allow_mass_assignment_of(:email, :name) }
|
118
|
+
|
119
|
+
h3. have_named_scope
|
120
|
+
|
121
|
+
Ensures that the model has a method named scope_name that returns a NamedScope object with the proxy options set to the options you supply. scope_name can be either a symbol, or a method call which will be evaled against the model. The eval‘d method call has access to all the same instance variables that a should statement would.
|
122
|
+
|
123
|
+
Options: Any of the options that the named scope would pass on to find.
|
124
|
+
|
125
|
+
Example:
|
126
|
+
|
127
|
+
should_have_named_scope :visible, :conditions => {:visible => true}
|
128
|
+
|
129
|
+
Passes for:
|
130
|
+
|
131
|
+
named_scope :visible, :conditions => {:visible => true}
|
132
|
+
|
133
|
+
Or for:
|
134
|
+
|
135
|
+
def self.visible
|
136
|
+
scoped(:conditions => {:visible => true})
|
137
|
+
end
|
138
|
+
|
139
|
+
You can test lambdas or methods that return ActiveRecord#scoped calls:
|
140
|
+
|
141
|
+
should_have_named_scope 'recent(5)', :limit => 5
|
142
|
+
should_have_named_scope 'recent(1)', :limit => 1
|
143
|
+
|
144
|
+
Passes for:
|
145
|
+
|
146
|
+
named_scope :recent, lambda {|c| {:limit => c}}
|
147
|
+
|
148
|
+
Or for:
|
149
|
+
|
150
|
+
def self.recent(c)
|
151
|
+
scoped(:limit => c)
|
152
|
+
end
|
153
|
+
|
154
|
+
h3. have_readonly_attributes
|
155
|
+
|
156
|
+
Ensures that the attribute cannot be changed once the record has been created.
|
157
|
+
|
158
|
+
should_have_readonly_attributes :password, :admin_flag
|
159
|
+
|
160
|
+
h3. validate_acceptance_of
|
161
|
+
|
162
|
+
Ensures that the model cannot be saved if one of the attributes listed is not accepted.
|
163
|
+
|
164
|
+
Options:
|
165
|
+
* :accept - the expected value to be accepted.
|
166
|
+
* :allow_nil - when supplied, validates if it allows nil or not.
|
167
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
168
|
+
Regexp or symbol or string. Default = I18n.translate('activerecord.errors.messages.accepted')
|
169
|
+
|
170
|
+
<pre><code> should_validate_acceptance_of :eula, :terms
|
171
|
+
should_validate_acceptance_of :eula, :terms, :accept => true
|
172
|
+
|
173
|
+
it { should validate_acceptance_of(:eula, :terms) }
|
174
|
+
it { should validate_acceptance_of(:eula, :terms, :accept => true) }</code></pre>
|
175
|
+
|
176
|
+
h3. validate_associated
|
177
|
+
|
178
|
+
Ensures that the model is invalid if one of the associations given is invalid.
|
179
|
+
|
180
|
+
It tries to build an instance of the association by two ways. Let's suppose a user that has many projects and you want to validate it:
|
181
|
+
|
182
|
+
it { should validate_associated(:projects) }
|
183
|
+
|
184
|
+
The first attempt to build the association would be:
|
185
|
+
|
186
|
+
@user.projects.build
|
187
|
+
|
188
|
+
If not possible, then we try:
|
189
|
+
|
190
|
+
@user.build_project
|
191
|
+
|
192
|
+
Then it tries to save the associated object. If the object can be saved if success (in this case, it allows all attributes as blank), we won't be able to verify the validation and then an error will be raised. In such cases, you should instantiate the association before calling the matcher:
|
193
|
+
|
194
|
+
it do
|
195
|
+
@user = User.new
|
196
|
+
@project = @user.projects.build
|
197
|
+
should validate_associated(:projects)
|
198
|
+
end
|
199
|
+
|
200
|
+
Or give :builder as option:
|
201
|
+
|
202
|
+
should_validate_associated(:projects, :builder => proc { |user| user.projects.build }) }
|
203
|
+
|
204
|
+
Options:
|
205
|
+
* :builder - a proc to build the association.
|
206
|
+
* :message - value the test expects to find in errors.on(:attribute). Regexp or string. Default = I18n.translate('activerecord.errors.messages.invalid')
|
207
|
+
|
208
|
+
<pre><code> should_validate_associated :projects, :account
|
209
|
+
should_validate_associated :projects, :builder => proc { |user| user.projects.build }
|
210
|
+
|
211
|
+
it { should validate_associated(:projects, :account) }
|
212
|
+
it { should validate_associated(:projects, :builder => proc { |user| user.projects.build }) }</code></pre>
|
213
|
+
|
214
|
+
h3. validate_confirmation_of
|
215
|
+
|
216
|
+
Ensures that the model cannot be saved if one of the attributes is not confirmed.
|
217
|
+
|
218
|
+
Options:
|
219
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
220
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.confirmation')
|
221
|
+
|
222
|
+
should_validate_confirmation_of :email, :password
|
223
|
+
it { should validate_confirmation_of(:email, :password) }
|
224
|
+
|
225
|
+
h3. validate_exclusion_of
|
226
|
+
|
227
|
+
Ensures that given values are not valid for the attribute. If a range is given, ensures that the attribute is not valid in the given range.
|
228
|
+
|
229
|
+
Note: this matcher accepts at once just one attribute to test.
|
230
|
+
|
231
|
+
Options:
|
232
|
+
* :allow_nil - when supplied, validates if it allows nil or not.
|
233
|
+
* :allow_blank - when supplied, validates if it allows blank or not.
|
234
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
235
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.exclusion')
|
236
|
+
|
237
|
+
<pre><code> should_validate_exclusion_of :age, 30..60
|
238
|
+
should_validate_exclusion_of :username, "admin", "user"
|
239
|
+
should_not validate_exclusion_of :username, "clark_kent", "peter_park"
|
240
|
+
|
241
|
+
it { should validate_exclusion_of(:age, 30..60) }
|
242
|
+
it { should validate_exclusion_of(:username, "admin", "user") }
|
243
|
+
it { should_not validate_exclusion_of(:username, "clark_kent", "peter_park") }</code></pre>
|
244
|
+
|
245
|
+
h3. validate_format_of
|
246
|
+
|
247
|
+
Ensures that the attribute can be set to the given values.
|
248
|
+
|
249
|
+
Note: this matcher accepts at once just one attribute to test.
|
250
|
+
Note: this matcher is also aliased as "allow_values_for"
|
251
|
+
|
252
|
+
Options:
|
253
|
+
* :allow_nil - when supplied, validates if it allows nil or not.
|
254
|
+
* :allow_blank - when supplied, validates if it allows blank or not.
|
255
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
256
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.invalid')
|
257
|
+
|
258
|
+
<pre><code> should validate_format_of :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
259
|
+
should_not validate_format_of :isbn, "bad 1", "bad 2"
|
260
|
+
|
261
|
+
it { should validate_format_of(:isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0") }
|
262
|
+
it { should_not validate_format_of(:isbn, "bad 1", "bad 2") }
|
263
|
+
</code></pre>
|
264
|
+
|
265
|
+
h3. validate_inclusion_of
|
266
|
+
|
267
|
+
Ensures that given values are valid for the attribute. If a range is given, ensures that the attribute is valid in the given range.
|
268
|
+
|
269
|
+
Note: this matcher accepts at once just one attribute to test.
|
270
|
+
|
271
|
+
Options:
|
272
|
+
* :allow_nil - when supplied, validates if it allows nil or not.
|
273
|
+
* :allow_blank - when supplied, validates if it allows blank or not.
|
274
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
275
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.inclusion')
|
276
|
+
|
277
|
+
<pre><code>
|
278
|
+
it { should validate_inclusion_of(:age, 18..100) }
|
279
|
+
it { should validate_inclusion_of(:isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0") }
|
280
|
+
|
281
|
+
it { should_not validate_inclusion_of(:isbn, "bad 1", "bad 2") }
|
282
|
+
</code></pre>
|
283
|
+
|
284
|
+
h3. validate_length_of
|
285
|
+
|
286
|
+
Validates the length of the given attributes. You have also to supply one of the following options: minimum, maximum, is or within.
|
287
|
+
|
288
|
+
Note: this method is also aliased as @validate_size_of@.
|
289
|
+
|
290
|
+
Options:
|
291
|
+
* :minimum - The minimum size of the attribute.
|
292
|
+
* :maximum - The maximum size of the attribute.
|
293
|
+
* :is - The exact size of the attribute.
|
294
|
+
* :within - A range specifying the minimum and maximum size of the attribute.
|
295
|
+
* :in - A synonym(or alias) for :within.
|
296
|
+
* :allow_nil - when supplied, validates if it allows nil or not.
|
297
|
+
* :allow_blank - when supplied, validates if it allows blank or not.
|
298
|
+
* :short_message - value the test expects to find in errors.on(:attribute).
|
299
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.too_short') % range.first
|
300
|
+
* :long_message - value the test expects to find in errors.on(:attribute).
|
301
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.too_long') % range.last
|
302
|
+
* :message - value the test expects to find in errors.on(:attribute) only when :minimum, :maximum or :is is given.
|
303
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.wrong_length') % value
|
304
|
+
|
305
|
+
<pre><code> should_validate_length_of :password, :within => 6..20
|
306
|
+
should_validate_length_of(:password, :maximum => 20
|
307
|
+
should_validate_length_of(:password, :minimum => 6
|
308
|
+
should_validate_length_of(:age, :is => 18
|
309
|
+
|
310
|
+
it { should validate_length_of(:password, :within => 6..20) }
|
311
|
+
it { should validate_length_of(:password, :maximum => 20) }
|
312
|
+
it { should validate_length_of(:password).minimum(6) }
|
313
|
+
it { should validate_length_of(:age).is(18) }</code></pre>
|
314
|
+
|
315
|
+
h3. validate_numericality_of
|
316
|
+
|
317
|
+
Ensures that the given attributes accepts only numbers.
|
318
|
+
|
319
|
+
Options:
|
320
|
+
|
321
|
+
* :only_integer - when supplied, checks if it accepts only integers or not
|
322
|
+
* :odd - when supplied, checks if it accepts only odd values or not
|
323
|
+
* :even - when supplied, checks if it accepts only even values or not
|
324
|
+
* :equal_to - when supplied, checks if attributes are only valid when equal to given value
|
325
|
+
* :less_than - when supplied, checks if attributes are only valid when less than given value
|
326
|
+
* :greater_than - when supplied, checks if attributes are only valid when greater than given value
|
327
|
+
* :less_than_or_equal_to - when supplied, checks if attributes are only valid when less than or equal to given value
|
328
|
+
* :greater_than_or_equal_to - when supplied, checks if attributes are only valid when greater than or equal to given value
|
329
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
330
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.not_a_number')
|
331
|
+
|
332
|
+
<pre><code> should_validate_numericality_of :age, :price
|
333
|
+
should_validate_numericality_of :age, :odd => true
|
334
|
+
should_validate_numericality_of :age, :even => true
|
335
|
+
should_validate_numericality_of :age, :only_integer => true
|
336
|
+
|
337
|
+
it { should validate_numericality_of(:age, :price) }
|
338
|
+
it { should validate_numericality_of(:age).odd }
|
339
|
+
it { should validate_numericality_of(:age, :even => true) }
|
340
|
+
it { should validate_numericality_of(:age).only_integer }</code></pre>
|
341
|
+
|
342
|
+
h3. validate_presence_of
|
343
|
+
|
344
|
+
Ensures that the model cannot be saved if one of the attributes listed is not present.
|
345
|
+
|
346
|
+
Options:
|
347
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
348
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.blank')
|
349
|
+
|
350
|
+
should_validate_presence_of(:name, :phone_number)
|
351
|
+
it { should validate_presence_of(:name, :phone_number) }
|
352
|
+
|
353
|
+
h3. validate_uniqueness_of
|
354
|
+
|
355
|
+
Ensures that the model cannot be saved if one of the attributes listed is not unique.
|
356
|
+
|
357
|
+
Requires an existing record in the database. If you supply :allow_nil as option, you need to have in the database a record with the given attribute nil and another with the given attribute not nil. The same is required for allow_blank option.
|
358
|
+
|
359
|
+
Options:
|
360
|
+
* :scope - field(s) to scope the uniqueness to.
|
361
|
+
* :case_sensitive - the matcher look for an exact match.
|
362
|
+
* :allow_nil - when supplied, validates if it allows nil or not.
|
363
|
+
* :allow_blank - when supplied, validates if it allows blank or not.
|
364
|
+
* :message - value the test expects to find in errors.on(:attribute).
|
365
|
+
Regexp, string or symbol. Default = I18n.translate('activerecord.errors.messages.taken')
|
366
|
+
|
367
|
+
<pre><code>
|
368
|
+
should_validate_uniqueness_of :keyword, :username
|
369
|
+
should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
|
370
|
+
should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
|
371
|
+
should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
|
372
|
+
|
373
|
+
it { should validate_uniqueness_of(:keyword, :username) }
|
374
|
+
it { should validate_uniqueness_of(:name, :message => "O NOES! SOMEONE STOELED YER NAME!") }
|
375
|
+
it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
|
376
|
+
it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
|
377
|
+
</pre></code>
|
378
|
+
|
@@ -1,21 +1,22 @@
|
|
1
1
|
require 'remarkable/active_record/helpers'
|
2
|
-
%w( database associations validations ).each do |folder|
|
3
|
-
Dir[File.join(File.dirname(__FILE__), "macros", folder, '
|
2
|
+
%w( database associations validations callbacks ).each do |folder|
|
3
|
+
Dir[File.join(File.dirname(__FILE__), "macros", folder, '*.rb')].each do |file|
|
4
4
|
require file
|
5
5
|
end
|
6
6
|
end
|
7
|
+
require 'remarkable/active_record/macros'
|
7
8
|
|
8
9
|
module Spec
|
9
10
|
module Rails
|
10
|
-
module
|
11
|
-
|
12
|
-
|
11
|
+
module Example
|
12
|
+
class ModelExampleGroup
|
13
|
+
include Remarkable::Assertions
|
14
|
+
include Remarkable::ActiveRecord::Matchers
|
15
|
+
extend Remarkable::ActiveRecord::Macros
|
16
|
+
|
17
|
+
private
|
18
|
+
include Remarkable::Private
|
19
|
+
end
|
13
20
|
end
|
14
21
|
end
|
15
22
|
end
|
16
|
-
|
17
|
-
Spec::Rails::Matchers.send(:include, Remarkable::Syntax::RSpec)
|
18
|
-
|
19
|
-
Spec::Example::ExampleGroupMethods.send(:include, Remarkable::Private)
|
20
|
-
Spec::Example::ExampleGroupMethods.send(:include, Remarkable::ActiveRecord::Helpers)
|
21
|
-
Spec::Example::ExampleGroupMethods.send(:include, Remarkable::Syntax::Shoulda)
|
@@ -1,14 +1,224 @@
|
|
1
1
|
module Remarkable # :nodoc:
|
2
2
|
module ActiveRecord # :nodoc:
|
3
3
|
module Helpers # :nodoc:
|
4
|
-
|
4
|
+
include Remarkable::Default::Helpers
|
5
5
|
|
6
|
-
def
|
7
|
-
|
6
|
+
def message(value)
|
7
|
+
@options[:message] = value
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def allow_nil(value = true)
|
12
|
+
@options[:allow_nil] = value
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def allow_blank(value = true)
|
17
|
+
@options[:allow_blank] = value
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def pretty_error_messages(obj) # :nodoc:
|
24
|
+
obj.errors.map do |a, m|
|
25
|
+
msg = "#{a} #{m}"
|
26
|
+
msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get a instance of the given objecy or class.
|
31
|
+
#
|
32
|
+
# If a class is given, it will check if a instance variable of this class
|
33
|
+
# is already set.
|
34
|
+
#
|
35
|
+
def get_instance_of(object_or_klass) # :nodoc:
|
36
|
+
if object_or_klass.is_a?(Class)
|
37
|
+
klass = object_or_klass
|
38
|
+
object = @spec ? @spec.instance_variable_get("@#{instance_variable_name_for(klass)}") : nil
|
39
|
+
object ||= klass.new
|
40
|
+
else
|
41
|
+
object_or_klass
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Guess instance variable name
|
46
|
+
#
|
47
|
+
def instance_variable_name_for(klass)
|
48
|
+
klass.to_s.split('::').last.underscore
|
49
|
+
end
|
50
|
+
|
51
|
+
# Common structure of tests that has been refactored.
|
52
|
+
# It checks for the key, if it exists and it's true, tests that the value
|
53
|
+
# given is bad, otherwise tests that the value is good.
|
54
|
+
#
|
55
|
+
def assert_bad_or_good_if_key(key, value, missing, message_key = :message, count_for_interpolation = 0)
|
56
|
+
return true unless @options.key? key
|
57
|
+
|
58
|
+
if @options[key]
|
59
|
+
return true if bad?(value, message_key)
|
60
|
+
else
|
61
|
+
return true if good?(value, message_key)
|
62
|
+
missing = 'not ' + missing
|
63
|
+
end
|
64
|
+
|
65
|
+
@missing = missing
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Common structure of tests that has been refactored.
|
70
|
+
# It checks for the key, if it exists and it's true, tests that the value
|
71
|
+
# given is good, otherwise tests that the value is bad.
|
72
|
+
#
|
73
|
+
def assert_good_or_bad_if_key(key, value, missing, message_key = :message, count_for_interpolation = 0)
|
74
|
+
return true unless @options.key? key
|
75
|
+
|
76
|
+
if @options[key]
|
77
|
+
return true if good?(value, message_key, count_for_interpolation)
|
78
|
+
missing = 'not ' + missing
|
79
|
+
else
|
80
|
+
return true if bad?(value, message_key, count_for_interpolation)
|
81
|
+
end
|
82
|
+
|
83
|
+
@missing = missing
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
# Default allow_nil? validation.
|
88
|
+
#
|
89
|
+
# Notice that it checks for @options[:message], so be sure that this option
|
90
|
+
# is properly set.
|
91
|
+
#
|
92
|
+
def allow_nil?(message_key = :message, count_for_interpolation = 0)
|
93
|
+
message = "allow #{@attribute} be set to nil"
|
94
|
+
assert_good_or_bad_if_key(:allow_nil, nil, message, message_key, count_for_interpolation)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Default allow_blank? validation.
|
98
|
+
#
|
99
|
+
# Notice that it checks for @options[:message], so be sure that this option
|
100
|
+
# is properly set.
|
101
|
+
#
|
102
|
+
def allow_blank?(message_key = :message, count_for_interpolation = 0)
|
103
|
+
message = "allow #{@attribute} be set to blank"
|
104
|
+
assert_good_or_bad_if_key(:allow_blank, '', message, message_key, count_for_interpolation)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Shortcut for assert_good_value.
|
108
|
+
# Please notice that it has instance variables hard coded. So do not use
|
109
|
+
# it if you are trying to assert another instance besides @subject.
|
110
|
+
#
|
111
|
+
def good?(value, message_sym = :message, count_for_interpolation = 0)
|
112
|
+
assert_good_value(@subject, @attribute, value, @options[message_sym], count_for_interpolation)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Shortcut for assert_bad_value.
|
116
|
+
# Please notice that it has instance variables hard coded. So do not use
|
117
|
+
# it if you are trying to assert another instance besides @subject.
|
118
|
+
#
|
119
|
+
def bad?(value, message_sym = :message, count_for_interpolation = 0)
|
120
|
+
assert_bad_value(@subject, @attribute, value, @options[message_sym], count_for_interpolation)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Asserts that an Active Record model validates with the passed
|
124
|
+
# <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
|
125
|
+
# contained within the list of errors for that attribute.
|
126
|
+
#
|
127
|
+
# assert_good_value(User.new, :email, "user@example.com")
|
128
|
+
# assert_good_value(User.new, :ssn, "123456789", /length/)
|
129
|
+
#
|
130
|
+
# If a class is passed as the first argument, a new object will be
|
131
|
+
# instantiated before the assertion. If an instance variable exists with
|
132
|
+
# the same name as the class (underscored), that object will be used
|
133
|
+
# instead.
|
134
|
+
#
|
135
|
+
# assert_good_value(User, :email, "user@example.com")
|
136
|
+
#
|
137
|
+
# @product = Product.new(:tangible => false)
|
138
|
+
# assert_good_value(Product, :price, "0")
|
139
|
+
#
|
140
|
+
def assert_good_value(object_or_klass, attribute, value, error_message_to_avoid = //, count_for_interpolation = 0) # :nodoc:
|
141
|
+
object = get_instance_of(object_or_klass)
|
142
|
+
object.send("#{attribute}=", value)
|
143
|
+
|
144
|
+
return true if object.valid?
|
145
|
+
|
146
|
+
error_message_to_avoid = error_message_from_model(object, attribute, error_message_to_avoid, count_for_interpolation)
|
147
|
+
|
148
|
+
assert_does_not_contain(object.errors.on(attribute), error_message_to_avoid)
|
8
149
|
end
|
9
150
|
|
10
|
-
|
11
|
-
|
151
|
+
# Asserts that an Active Record model invalidates the passed
|
152
|
+
# <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
|
153
|
+
# contained within the list of errors for that attribute.
|
154
|
+
#
|
155
|
+
# assert_bad_value(User.new, :email, "invalid")
|
156
|
+
# assert_bad_value(User.new, :ssn, "123", /length/)
|
157
|
+
#
|
158
|
+
# If a class is passed as the first argument, a new object will be
|
159
|
+
# instantiated before the assertion. If an instance variable exists with
|
160
|
+
# the same name as the class (underscored), that object will be used
|
161
|
+
# instead.
|
162
|
+
#
|
163
|
+
# assert_bad_value(User, :email, "invalid")
|
164
|
+
#
|
165
|
+
# @product = Product.new(:tangible => true)
|
166
|
+
# assert_bad_value(Product, :price, "0")
|
167
|
+
#
|
168
|
+
def assert_bad_value(object_or_klass, attribute, value, error_message_to_expect = :invalid, count_for_interpolation = 0) # :nodoc:
|
169
|
+
object = get_instance_of(object_or_klass)
|
170
|
+
object.send("#{attribute}=", value)
|
171
|
+
|
172
|
+
return false if object.valid?
|
173
|
+
return false unless object.errors.on(attribute)
|
174
|
+
|
175
|
+
error_message_to_expect = error_message_from_model(object, attribute, error_message_to_expect, count_for_interpolation)
|
176
|
+
|
177
|
+
assert_contains(object.errors.on(attribute), error_message_to_expect)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return the error message to be checked. If the message is not a Symbol
|
181
|
+
# neither a Hash, it returns the own message.
|
182
|
+
#
|
183
|
+
# But the nice thing is that when the message is a Symbol we get the error
|
184
|
+
# messsage from within the model, using already existent structure inside
|
185
|
+
# ActiveRecord.
|
186
|
+
#
|
187
|
+
# This allows a couple things from the user side:
|
188
|
+
#
|
189
|
+
# 1. Specify symbols in their tests:
|
190
|
+
#
|
191
|
+
# should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => :inclusion)
|
192
|
+
#
|
193
|
+
# As we know, allow_values_for searches for a :invalid message. So if we
|
194
|
+
# were testing a validates_inclusion_of with allow_values_for, previously
|
195
|
+
# we had to do something like this:
|
196
|
+
#
|
197
|
+
# should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => 'not included in list')
|
198
|
+
#
|
199
|
+
# Now everything gets resumed to a Symbol.
|
200
|
+
#
|
201
|
+
# 2. Do not worry with specs if their are using I18n API properly.
|
202
|
+
#
|
203
|
+
# As we know, I18n API provides several interpolation options besides
|
204
|
+
# fallback when creating error messages. If the user changed the message,
|
205
|
+
# macros would start to pass when they shouldn't.
|
206
|
+
#
|
207
|
+
# Using the underlying mechanism inside ActiveRecord makes us free from
|
208
|
+
# all thos errors.
|
209
|
+
#
|
210
|
+
# The count value is used to do interpolation.
|
211
|
+
#
|
212
|
+
def error_message_from_model(model, attribute, message, count_value = 0)
|
213
|
+
if message.is_a? Symbol
|
214
|
+
if Object.const_defined?(:I18n) # Rails >= 2.2
|
215
|
+
model.errors.generate_message(attribute, message, :count => count_value)
|
216
|
+
else # Rails <= 2.1
|
217
|
+
::ActiveRecord::Errors.default_error_messages[message] % count_value
|
218
|
+
end
|
219
|
+
else
|
220
|
+
message
|
221
|
+
end
|
12
222
|
end
|
13
223
|
|
14
224
|
end
|