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.
Files changed (121) hide show
  1. data/History.txt +5 -5
  2. data/Manifest.txt +53 -35
  3. data/PostInstall.txt +1 -6
  4. data/README.rdoc +109 -15
  5. data/Rakefile +29 -29
  6. data/lib/remarkable/active_record/README.markdown +378 -0
  7. data/lib/remarkable/active_record/active_record.rb +12 -11
  8. data/lib/remarkable/active_record/helpers.rb +215 -5
  9. data/lib/remarkable/active_record/macros/associations/association_matcher.rb +242 -0
  10. data/lib/remarkable/active_record/macros/callbacks/callback_matcher.rb +46 -0
  11. data/lib/remarkable/active_record/macros/database/column_matcher.rb +122 -0
  12. data/lib/remarkable/active_record/macros/database/index_matcher.rb +103 -0
  13. data/lib/remarkable/active_record/macros/validations/allow_mass_assignment_of_matcher.rb +52 -0
  14. data/lib/remarkable/active_record/macros/validations/ensure_value_in_list_matcher.rb +83 -0
  15. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range_matcher.rb +172 -0
  16. data/lib/remarkable/active_record/macros/validations/have_class_methods_matcher.rb +54 -0
  17. data/lib/remarkable/active_record/macros/validations/have_instance_methods_matcher.rb +54 -0
  18. data/lib/remarkable/active_record/macros/validations/have_named_scope_matcher.rb +94 -0
  19. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes_matcher.rb +48 -0
  20. data/lib/remarkable/active_record/macros/validations/protect_attributes_matcher.rb +51 -0
  21. data/lib/remarkable/active_record/macros/validations/validate_acceptance_of_matcher.rb +79 -0
  22. data/lib/remarkable/active_record/macros/validations/validate_associated_matcher.rb +177 -0
  23. data/lib/remarkable/active_record/macros/validations/validate_confirmation_of_matcher.rb +74 -0
  24. data/lib/remarkable/active_record/macros/validations/validate_exclusion_of_matcher.rb +38 -0
  25. data/lib/remarkable/active_record/macros/validations/validate_format_of_matcher.rb +33 -0
  26. data/lib/remarkable/active_record/macros/validations/validate_inclusion_of_matcher.rb +45 -0
  27. data/lib/remarkable/active_record/macros/validations/validate_length_of_matcher.rb +248 -0
  28. data/lib/remarkable/active_record/macros/validations/validate_numericality_of_matcher.rb +206 -0
  29. data/lib/remarkable/active_record/macros/validations/validate_presence_of_matcher.rb +72 -0
  30. data/lib/remarkable/active_record/macros/validations/validate_uniqueness_of_matcher.rb +222 -0
  31. data/lib/remarkable/active_record/macros.rb +52 -0
  32. data/lib/remarkable/assertions.rb +29 -0
  33. data/lib/remarkable/controller/README.markdown +147 -0
  34. data/lib/remarkable/controller/controller.rb +11 -6
  35. data/lib/remarkable/controller/helpers.rb +4 -38
  36. data/lib/remarkable/controller/macros/assign_matcher.rb +85 -0
  37. data/lib/remarkable/controller/macros/filter_params_matcher.rb +63 -0
  38. data/lib/remarkable/controller/macros/metadata_matcher.rb +63 -0
  39. data/lib/remarkable/controller/macros/render_with_layout_matcher.rb +75 -0
  40. data/lib/remarkable/controller/macros/respond_with_content_type_matcher.rb +60 -0
  41. data/lib/remarkable/controller/macros/respond_with_matcher.rb +62 -0
  42. data/lib/remarkable/controller/macros/return_from_session_matcher.rb +58 -0
  43. data/lib/remarkable/controller/macros/route_matcher.rb +75 -0
  44. data/lib/remarkable/controller/macros/set_the_flash_to_matcher.rb +60 -0
  45. data/lib/remarkable/controller/macros.rb +78 -0
  46. data/lib/remarkable/dsl.rb +239 -0
  47. data/lib/remarkable/example/example_methods.rb +27 -7
  48. data/lib/remarkable/helpers.rb +28 -0
  49. data/lib/remarkable/matcher_base.rb +64 -0
  50. data/lib/remarkable/private_helpers.rb +10 -115
  51. data/lib/remarkable/rails.rb +27 -0
  52. data/lib/remarkable.rb +13 -5
  53. data/remarkable.gemspec +43 -0
  54. data/spec/controllers/posts_controller_spec.rb +58 -4
  55. data/spec/controllers/users_controller_spec.rb +1 -0
  56. data/spec/fixtures/fleas.yml +10 -0
  57. data/spec/fixtures/users.yml +7 -0
  58. data/spec/models/address_spec.rb +44 -0
  59. data/spec/models/dog_spec.rb +64 -3
  60. data/spec/models/flea_spec.rb +30 -0
  61. data/spec/models/post_spec.rb +36 -2
  62. data/spec/models/product_spec.rb +73 -8
  63. data/spec/models/tag_spec.rb +2 -2
  64. data/spec/models/tagging_spec.rb +24 -0
  65. data/spec/models/user_spec.rb +206 -21
  66. data/spec/other/custom_macros_spec.rb +27 -0
  67. data/spec/other/my_own_matcher_spec.rb +11 -0
  68. data/spec/other/private_helpers_spec.rb +31 -0
  69. data/spec/rails_root/app/controllers/posts_controller.rb +2 -0
  70. data/spec/rails_root/app/models/address.rb +2 -2
  71. data/spec/rails_root/app/models/flea.rb +4 -0
  72. data/spec/rails_root/app/models/pets/dog.rb +12 -0
  73. data/spec/rails_root/app/models/product.rb +7 -5
  74. data/spec/rails_root/app/models/tagging.rb +2 -0
  75. data/spec/rails_root/app/models/user.rb +20 -5
  76. data/spec/rails_root/app/views/layouts/posts.rhtml +8 -6
  77. data/spec/rails_root/config/database.yml +1 -2
  78. data/spec/rails_root/config/environment.rb +3 -1
  79. data/spec/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  80. data/spec/rails_root/config/locales/en.yml +8 -0
  81. data/spec/rails_root/db/migrate/001_create_users.rb +2 -2
  82. data/spec/rails_root/db/migrate/005_create_dogs.rb +1 -0
  83. data/spec/rails_root/db/migrate/011_add_fleas_color.rb +10 -0
  84. data/spec/rails_root/db/migrate/012_add_fleas_address.rb +10 -0
  85. data/spec/rails_root/spec/remarkable_macros/.keep +0 -0
  86. data/spec/rails_root/vendor/plugins/my_plugin/remarkable_macros/.keep +0 -0
  87. data/spec/spec_helper.rb +0 -2
  88. metadata +63 -43
  89. data/lib/remarkable/active_record/macros/associations/belong_to.rb +0 -81
  90. data/lib/remarkable/active_record/macros/associations/have_and_belong_to_many.rb +0 -77
  91. data/lib/remarkable/active_record/macros/associations/have_many.rb +0 -160
  92. data/lib/remarkable/active_record/macros/associations/have_one.rb +0 -133
  93. data/lib/remarkable/active_record/macros/database/have_db_column.rb +0 -81
  94. data/lib/remarkable/active_record/macros/database/have_db_columns.rb +0 -73
  95. data/lib/remarkable/active_record/macros/database/have_indices.rb +0 -75
  96. data/lib/remarkable/active_record/macros/validations/allow_values_for.rb +0 -103
  97. data/lib/remarkable/active_record/macros/validations/ensure_length_at_least.rb +0 -97
  98. data/lib/remarkable/active_record/macros/validations/ensure_length_in_range.rb +0 -134
  99. data/lib/remarkable/active_record/macros/validations/ensure_length_is.rb +0 -106
  100. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range.rb +0 -117
  101. data/lib/remarkable/active_record/macros/validations/have_class_methods.rb +0 -74
  102. data/lib/remarkable/active_record/macros/validations/have_instance_methods.rb +0 -74
  103. data/lib/remarkable/active_record/macros/validations/have_named_scope.rb +0 -148
  104. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes.rb +0 -81
  105. data/lib/remarkable/active_record/macros/validations/only_allow_numeric_values_for.rb +0 -89
  106. data/lib/remarkable/active_record/macros/validations/protect_attributes.rb +0 -89
  107. data/lib/remarkable/active_record/macros/validations/require_acceptance_of.rb +0 -94
  108. data/lib/remarkable/active_record/macros/validations/require_attributes.rb +0 -94
  109. data/lib/remarkable/active_record/macros/validations/require_unique_attributes.rb +0 -146
  110. data/lib/remarkable/controller/macros/assign_to.rb +0 -110
  111. data/lib/remarkable/controller/macros/filter_params.rb +0 -52
  112. data/lib/remarkable/controller/macros/redirect_to.rb +0 -24
  113. data/lib/remarkable/controller/macros/render_a_form.rb +0 -23
  114. data/lib/remarkable/controller/macros/render_template.rb +0 -18
  115. data/lib/remarkable/controller/macros/render_with_layout.rb +0 -61
  116. data/lib/remarkable/controller/macros/respond_with.rb +0 -86
  117. data/lib/remarkable/controller/macros/respond_with_content_type.rb +0 -45
  118. data/lib/remarkable/controller/macros/return_from_session.rb +0 -45
  119. data/lib/remarkable/controller/macros/route.rb +0 -91
  120. data/lib/remarkable/controller/macros/set_the_flash_to.rb +0 -58
  121. 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, '*')].each do |file|
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 Matchers
11
- include Remarkable::Private
12
- include Remarkable::ActiveRecord::Helpers
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
- private # :enddoc:
4
+ include Remarkable::Default::Helpers
5
5
 
6
- def model_class
7
- self.described_type
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
- def fail_with(message)
11
- Spec::Expectations.fail_with(message)
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