super-smart-hub 0.0.1
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.
- checksums.yaml +7 -0
- data/paranoia-3.1.0/CHANGELOG.md +234 -0
- data/paranoia-3.1.0/CODE_OF_CONDUCT.md +74 -0
- data/paranoia-3.1.0/CONTRIBUTING.md +34 -0
- data/paranoia-3.1.0/Gemfile +33 -0
- data/paranoia-3.1.0/LICENSE +17 -0
- data/paranoia-3.1.0/README.md +413 -0
- data/paranoia-3.1.0/Rakefile +10 -0
- data/paranoia-3.1.0/lib/paranoia/active_record_5_2.rb +41 -0
- data/paranoia-3.1.0/lib/paranoia/rspec.rb +26 -0
- data/paranoia-3.1.0/lib/paranoia/version.rb +3 -0
- data/paranoia-3.1.0/lib/paranoia.rb +423 -0
- data/paranoia-3.1.0/paranoia.gemspec +40 -0
- data/super-smart-hub.gemspec +12 -0
- metadata +54 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
[](https://badge.fury.io/rb/paranoia)
|
|
2
|
+
[](https://github.com/rubysherpas/paranoia/actions/workflows/build.yml)
|
|
3
|
+
|
|
4
|
+
**Notice:**
|
|
5
|
+
|
|
6
|
+
`paranoia` has some surprising behaviour (like overriding ActiveRecord's `delete` and `destroy`) and is not recommended for new projects. See [`discard`'s README](https://github.com/jhawthorn/discard#why-not-paranoia-or-acts_as_paranoid) for more details.
|
|
7
|
+
|
|
8
|
+
Paranoia will continue to accept bug fixes and support new versions of Rails but isn't accepting new features.
|
|
9
|
+
|
|
10
|
+
# Paranoia
|
|
11
|
+
|
|
12
|
+
Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/ActsAsParanoid/acts_as_paranoid) for Rails 3/4/5, using much, much, much less code.
|
|
13
|
+
|
|
14
|
+
When your app is using Paranoia, calling `destroy` on an ActiveRecord object doesn't actually destroy the database record, but just *hides* it. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field.
|
|
15
|
+
|
|
16
|
+
If you wish to actually destroy an object you may call `really_destroy!`. **WARNING**: This will also *really destroy* all `dependent: :destroy` records, so please aim this method away from face when using.
|
|
17
|
+
|
|
18
|
+
If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted if `acts_as_paranoid` is set, otherwise the normal destroy will be called. ***See [Destroying through association callbacks](#destroying-through-association-callbacks) for clarifying examples.***
|
|
19
|
+
|
|
20
|
+
## Getting Started Video
|
|
21
|
+
Setup and basic usage of the paranoia gem
|
|
22
|
+
[GoRails #41](https://gorails.com/episodes/soft-delete-with-paranoia)
|
|
23
|
+
|
|
24
|
+
## Installation & Usage
|
|
25
|
+
|
|
26
|
+
For Rails 3, please use version 1 of Paranoia:
|
|
27
|
+
|
|
28
|
+
``` ruby
|
|
29
|
+
gem "paranoia", "~> 1.0"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For Rails 4 and 5, please use version 2 of Paranoia (2.2 or greater required for rails 5):
|
|
33
|
+
|
|
34
|
+
``` ruby
|
|
35
|
+
gem "paranoia", "~> 2.2"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Of course you can install this from GitHub as well from one of these examples:
|
|
39
|
+
|
|
40
|
+
``` ruby
|
|
41
|
+
gem "paranoia", github: "rubysherpas/paranoia", branch: "rails3"
|
|
42
|
+
gem "paranoia", github: "rubysherpas/paranoia", branch: "rails4"
|
|
43
|
+
gem "paranoia", github: "rubysherpas/paranoia", branch: "rails5"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then run:
|
|
47
|
+
|
|
48
|
+
``` shell
|
|
49
|
+
bundle install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Updating is as simple as `bundle update paranoia`.
|
|
53
|
+
|
|
54
|
+
#### Run your migrations for the desired models
|
|
55
|
+
|
|
56
|
+
Run:
|
|
57
|
+
|
|
58
|
+
``` shell
|
|
59
|
+
bin/rails generate migration AddDeletedAtToClients deleted_at:datetime:index
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
and now you have a migration
|
|
63
|
+
|
|
64
|
+
``` ruby
|
|
65
|
+
class AddDeletedAtToClients < ActiveRecord::Migration
|
|
66
|
+
def change
|
|
67
|
+
add_column :clients, :deleted_at, :datetime
|
|
68
|
+
add_index :clients, :deleted_at
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Usage
|
|
74
|
+
|
|
75
|
+
#### In your model:
|
|
76
|
+
|
|
77
|
+
``` ruby
|
|
78
|
+
class Client < ActiveRecord::Base
|
|
79
|
+
acts_as_paranoid
|
|
80
|
+
|
|
81
|
+
# ...
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Hey presto, it's there! Calling `destroy` will now set the `deleted_at` column:
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
``` ruby
|
|
89
|
+
>> client.deleted_at
|
|
90
|
+
# => nil
|
|
91
|
+
>> client.destroy
|
|
92
|
+
# => client
|
|
93
|
+
>> client.deleted_at
|
|
94
|
+
# => [current timestamp]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If you really want it gone *gone*, call `really_destroy!`:
|
|
98
|
+
|
|
99
|
+
``` ruby
|
|
100
|
+
>> client.deleted_at
|
|
101
|
+
# => nil
|
|
102
|
+
>> client.really_destroy!
|
|
103
|
+
# => client
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If you need skip updating timestamps for deleting records, call `really_destroy!(update_destroy_attributes: false)`.
|
|
107
|
+
When we call `really_destroy!(update_destroy_attributes: false)` on the parent `client`, then each child `email` will also have `really_destroy!(update_destroy_attributes: false)` called.
|
|
108
|
+
|
|
109
|
+
``` ruby
|
|
110
|
+
>> client.really_destroy!(update_destroy_attributes: false)
|
|
111
|
+
# => client
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you want to use a column other than `deleted_at`, you can pass it as an option:
|
|
115
|
+
|
|
116
|
+
``` ruby
|
|
117
|
+
class Client < ActiveRecord::Base
|
|
118
|
+
acts_as_paranoid column: :destroyed_at
|
|
119
|
+
|
|
120
|
+
...
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
If you want to skip adding the default scope:
|
|
126
|
+
|
|
127
|
+
``` ruby
|
|
128
|
+
class Client < ActiveRecord::Base
|
|
129
|
+
acts_as_paranoid without_default_scope: true
|
|
130
|
+
|
|
131
|
+
...
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If you want to access soft-deleted associations, override the getter method:
|
|
136
|
+
|
|
137
|
+
``` ruby
|
|
138
|
+
def product
|
|
139
|
+
Product.unscoped { super }
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
If you want to include associated soft-deleted objects, you can (un)scope the association:
|
|
144
|
+
|
|
145
|
+
``` ruby
|
|
146
|
+
class Person < ActiveRecord::Base
|
|
147
|
+
belongs_to :group, -> { with_deleted }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
Person.includes(:group).all
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If you want to find all records, even those which are deleted:
|
|
154
|
+
|
|
155
|
+
``` ruby
|
|
156
|
+
Client.with_deleted
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If you want to exclude deleted records, when not able to use the default_scope (e.g. when using without_default_scope):
|
|
160
|
+
|
|
161
|
+
``` ruby
|
|
162
|
+
Client.without_deleted
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
If you want to find only the deleted records:
|
|
166
|
+
|
|
167
|
+
``` ruby
|
|
168
|
+
Client.only_deleted
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
If you want to check if a record is soft-deleted:
|
|
172
|
+
|
|
173
|
+
``` ruby
|
|
174
|
+
client.paranoia_destroyed?
|
|
175
|
+
# or
|
|
176
|
+
client.deleted?
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
If you want to restore a record:
|
|
180
|
+
|
|
181
|
+
``` ruby
|
|
182
|
+
Client.restore(id)
|
|
183
|
+
# or
|
|
184
|
+
client.restore
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
If you want to restore a whole bunch of records:
|
|
188
|
+
|
|
189
|
+
``` ruby
|
|
190
|
+
Client.restore([id1, id2, ..., idN])
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
If you want to restore a record and their dependently destroyed associated records:
|
|
194
|
+
|
|
195
|
+
``` ruby
|
|
196
|
+
Client.restore(id, :recursive => true)
|
|
197
|
+
# or
|
|
198
|
+
client.restore(:recursive => true)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If you want to restore a record and only those dependently destroyed associated records that were deleted within 2 minutes of the object upon which they depend:
|
|
202
|
+
|
|
203
|
+
``` ruby
|
|
204
|
+
Client.restore(id, :recursive => true, :recovery_window => 2.minutes)
|
|
205
|
+
# or
|
|
206
|
+
client.restore(:recursive => true, :recovery_window => 2.minutes)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
If you want to trigger an after_commit callback when restoring a record:
|
|
210
|
+
|
|
211
|
+
``` ruby
|
|
212
|
+
class Client < ActiveRecord::Base
|
|
213
|
+
acts_as_paranoid after_restore_commit: true
|
|
214
|
+
|
|
215
|
+
after_commit :commit_called, on: :restore
|
|
216
|
+
# or
|
|
217
|
+
after_restore_commit :commit_called
|
|
218
|
+
...
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Note that by default paranoia will not prevent that a soft destroyed object can't be associated with another object of a different model.
|
|
223
|
+
A Rails validator is provided should you require this functionality:
|
|
224
|
+
``` ruby
|
|
225
|
+
validates :some_assocation, association_not_soft_destroyed: true
|
|
226
|
+
```
|
|
227
|
+
This validator makes sure that `some_assocation` is not soft destroyed. If the object is soft destroyed the main object is rendered invalid and an validation error is added.
|
|
228
|
+
|
|
229
|
+
For more information, please look at the tests.
|
|
230
|
+
|
|
231
|
+
#### About indexes:
|
|
232
|
+
|
|
233
|
+
Beware that you should adapt all your indexes for them to work as fast as previously.
|
|
234
|
+
For example,
|
|
235
|
+
|
|
236
|
+
``` ruby
|
|
237
|
+
add_index :clients, :group_id
|
|
238
|
+
add_index :clients, [:group_id, :other_id]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
should be replaced with
|
|
242
|
+
|
|
243
|
+
``` ruby
|
|
244
|
+
add_index :clients, :group_id, where: "deleted_at IS NULL"
|
|
245
|
+
add_index :clients, [:group_id, :other_id], where: "deleted_at IS NULL"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Of course, this is not necessary for the indexes you always use in association with `with_deleted` or `only_deleted`.
|
|
249
|
+
|
|
250
|
+
##### Unique Indexes
|
|
251
|
+
|
|
252
|
+
Because NULL != NULL in standard SQL, we can not simply create a unique index
|
|
253
|
+
on the deleted_at column and expect it to enforce that there only be one record
|
|
254
|
+
with a certain combination of values.
|
|
255
|
+
|
|
256
|
+
If your database supports them, good alternatives include partial indexes
|
|
257
|
+
(above) and indexes on computed columns. E.g.
|
|
258
|
+
|
|
259
|
+
``` ruby
|
|
260
|
+
add_index :clients, [:group_id, 'COALESCE(deleted_at, false)'], unique: true
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
If not, an alternative is to create a separate column which is maintained
|
|
264
|
+
alongside deleted_at for the sake of enforcing uniqueness. To that end,
|
|
265
|
+
paranoia makes use of two method to make its destroy and restore actions:
|
|
266
|
+
paranoia_restore_attributes and paranoia_destroy_attributes.
|
|
267
|
+
|
|
268
|
+
``` ruby
|
|
269
|
+
add_column :clients, :active, :boolean
|
|
270
|
+
add_index :clients, [:group_id, :active], unique: true
|
|
271
|
+
|
|
272
|
+
class Client < ActiveRecord::Base
|
|
273
|
+
# optionally have paranoia make use of your unique column, so that
|
|
274
|
+
# your lookups will benefit from the unique index
|
|
275
|
+
acts_as_paranoid column: :active, sentinel_value: true
|
|
276
|
+
|
|
277
|
+
def paranoia_restore_attributes
|
|
278
|
+
{
|
|
279
|
+
deleted_at: nil,
|
|
280
|
+
active: true
|
|
281
|
+
}
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def paranoia_destroy_attributes
|
|
285
|
+
{
|
|
286
|
+
deleted_at: current_time_from_proper_timezone,
|
|
287
|
+
active: nil
|
|
288
|
+
}
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
##### Destroying through association callbacks
|
|
294
|
+
|
|
295
|
+
When dealing with `dependent: :destroy` associations and `acts_as_paranoid`, it's important to remember that whatever method is called on the parent model will be called on the child model. For example, given both models of an association have `acts_as_paranoid` defined:
|
|
296
|
+
|
|
297
|
+
``` ruby
|
|
298
|
+
class Client < ActiveRecord::Base
|
|
299
|
+
acts_as_paranoid
|
|
300
|
+
|
|
301
|
+
has_many :emails, dependent: :destroy
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
class Email < ActiveRecord::Base
|
|
305
|
+
acts_as_paranoid
|
|
306
|
+
|
|
307
|
+
belongs_to :client
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
When we call `destroy` on the parent `client`, it will call `destroy` on all of its associated children `emails`:
|
|
312
|
+
|
|
313
|
+
``` ruby
|
|
314
|
+
>> client.emails.count
|
|
315
|
+
# => 5
|
|
316
|
+
>> client.destroy
|
|
317
|
+
# => client
|
|
318
|
+
>> client.deleted_at
|
|
319
|
+
# => [current timestamp]
|
|
320
|
+
>> Email.where(client_id: client.id).count
|
|
321
|
+
# => 0
|
|
322
|
+
>> Email.with_deleted.where(client_id: client.id).count
|
|
323
|
+
# => 5
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Similarly, when we call `really_destroy!` on the parent `client`, then each child `email` will also have `really_destroy!` called:
|
|
327
|
+
|
|
328
|
+
``` ruby
|
|
329
|
+
>> client.emails.count
|
|
330
|
+
# => 5
|
|
331
|
+
>> client.id
|
|
332
|
+
# => 12345
|
|
333
|
+
>> client.really_destroy!
|
|
334
|
+
# => client
|
|
335
|
+
>> Client.find 12345
|
|
336
|
+
# => ActiveRecord::RecordNotFound
|
|
337
|
+
>> Email.with_deleted.where(client_id: client.id).count
|
|
338
|
+
# => 0
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
However, if the child model `Email` does not have `acts_as_paranoid` set, then calling `destroy` on the parent `client` will also call `destroy` on each child `email`, thereby actually destroying them:
|
|
342
|
+
|
|
343
|
+
``` ruby
|
|
344
|
+
class Client < ActiveRecord::Base
|
|
345
|
+
acts_as_paranoid
|
|
346
|
+
|
|
347
|
+
has_many :emails, dependent: :destroy
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
class Email < ActiveRecord::Base
|
|
351
|
+
belongs_to :client
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
>> client.emails.count
|
|
355
|
+
# => 5
|
|
356
|
+
>> client.destroy
|
|
357
|
+
# => client
|
|
358
|
+
>> Email.where(client_id: client.id).count
|
|
359
|
+
# => 0
|
|
360
|
+
>> Email.with_deleted.where(client_id: client.id).count
|
|
361
|
+
# => NoMethodError: undefined method `with_deleted' for #<Class:0x0123456>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### delete_all:
|
|
365
|
+
|
|
366
|
+
The gem supports `delete_all` method, however it is disabled by default, to enable it add this in your `environment` file
|
|
367
|
+
|
|
368
|
+
``` ruby
|
|
369
|
+
Paranoia.delete_all_enabled = true
|
|
370
|
+
```
|
|
371
|
+
alternatively, you can enable/disable it for specific models as follow:
|
|
372
|
+
|
|
373
|
+
``` ruby
|
|
374
|
+
class User < ActiveRecord::Base
|
|
375
|
+
acts_as_paranoid(delete_all_enabled: true)
|
|
376
|
+
end
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Acts As Paranoid Migration
|
|
380
|
+
|
|
381
|
+
You can replace the older `acts_as_paranoid` methods as follows:
|
|
382
|
+
|
|
383
|
+
| Old Syntax | New Syntax |
|
|
384
|
+
|:-------------------------- |:------------------------------ |
|
|
385
|
+
|`find_with_deleted(:all)` | `Client.with_deleted` |
|
|
386
|
+
|`find_with_deleted(:first)` | `Client.with_deleted.first` |
|
|
387
|
+
|`find_with_deleted(id)` | `Client.with_deleted.find(id)` |
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
The `recover` method in `acts_as_paranoid` runs `update` callbacks. Paranoia's
|
|
391
|
+
`restore` method does not do this.
|
|
392
|
+
|
|
393
|
+
## Callbacks
|
|
394
|
+
|
|
395
|
+
Paranoia provides several callbacks. It triggers `destroy` callback when the record is marked as deleted and `real_destroy` when the record is completely removed from database. It also calls `restore` callback when the record is restored via paranoia
|
|
396
|
+
|
|
397
|
+
For example if you want to index your records in some search engine you can go like this:
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
class Product < ActiveRecord::Base
|
|
401
|
+
acts_as_paranoid
|
|
402
|
+
|
|
403
|
+
after_destroy :update_document_in_search_engine
|
|
404
|
+
after_restore :update_document_in_search_engine
|
|
405
|
+
after_real_destroy :remove_document_from_search_engine
|
|
406
|
+
end
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
You can use these events just like regular Rails callbacks with before, after and around hooks.
|
|
410
|
+
|
|
411
|
+
## License
|
|
412
|
+
|
|
413
|
+
This gem is released under the MIT license.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module HandleParanoiaDestroyedInBelongsToAssociation
|
|
2
|
+
def handle_dependency
|
|
3
|
+
return unless load_target
|
|
4
|
+
|
|
5
|
+
case options[:dependent]
|
|
6
|
+
when :destroy
|
|
7
|
+
target.destroy
|
|
8
|
+
if target.respond_to?(:paranoia_destroyed?)
|
|
9
|
+
raise ActiveRecord::Rollback unless target.paranoia_destroyed?
|
|
10
|
+
else
|
|
11
|
+
raise ActiveRecord::Rollback unless target.destroyed?
|
|
12
|
+
end
|
|
13
|
+
else
|
|
14
|
+
target.send(options[:dependent])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module HandleParanoiaDestroyedInHasOneAssociation
|
|
20
|
+
def delete(method = options[:dependent])
|
|
21
|
+
if load_target
|
|
22
|
+
case method
|
|
23
|
+
when :delete
|
|
24
|
+
target.delete
|
|
25
|
+
when :destroy
|
|
26
|
+
target.destroyed_by_association = reflection
|
|
27
|
+
target.destroy
|
|
28
|
+
if target.respond_to?(:paranoia_destroyed?)
|
|
29
|
+
throw(:abort) unless target.paranoia_destroyed?
|
|
30
|
+
else
|
|
31
|
+
throw(:abort) unless target.destroyed?
|
|
32
|
+
end
|
|
33
|
+
when :nullify
|
|
34
|
+
target.update_columns(reflection.foreign_key => nil) if target.persisted?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
ActiveRecord::Associations::BelongsToAssociation.prepend HandleParanoiaDestroyedInBelongsToAssociation
|
|
41
|
+
ActiveRecord::Associations::HasOneAssociation.prepend HandleParanoiaDestroyedInHasOneAssociation
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
if defined?(RSpec)
|
|
2
|
+
require 'rspec/expectations'
|
|
3
|
+
|
|
4
|
+
# Validate the subject's class did call "acts_as_paranoid"
|
|
5
|
+
RSpec::Matchers.define :act_as_paranoid do
|
|
6
|
+
match { |subject| subject.class.ancestors.include?(Paranoia) }
|
|
7
|
+
|
|
8
|
+
failure_message_proc = lambda do
|
|
9
|
+
"expected #{subject.class} to use `acts_as_paranoid`"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
failure_message_when_negated_proc = lambda do
|
|
13
|
+
"expected #{subject.class} not to use `acts_as_paranoid`"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if respond_to?(:failure_message_when_negated)
|
|
17
|
+
failure_message(&failure_message_proc)
|
|
18
|
+
failure_message_when_negated(&failure_message_when_negated_proc)
|
|
19
|
+
else
|
|
20
|
+
# RSpec 2 compatibility:
|
|
21
|
+
failure_message_for_should(&failure_message_proc)
|
|
22
|
+
failure_message_for_should_not(&failure_message_when_negated_proc)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|