openlogic-couchrest_model 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,134 @@
1
+ require "spec_helper"
2
+
3
+ class DesignModel < CouchRest::Model::Base
4
+ end
5
+
6
+ describe CouchRest::Model::Designs do
7
+
8
+ it "should accessable from model" do
9
+ DesignModel.respond_to?(:design).should be_true
10
+ end
11
+
12
+ describe "class methods" do
13
+
14
+ describe ".design" do
15
+ before :each do
16
+ @mapper = mock('DesignMapper')
17
+ @mapper.stub!(:create_view_method)
18
+ end
19
+
20
+ it "should instantiate a new DesignMapper" do
21
+ CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper)
22
+ @mapper.should_receive(:create_view_method).with(:all)
23
+ @mapper.should_receive(:instance_eval)
24
+ DesignModel.design() { }
25
+ end
26
+
27
+ it "should allow methods to be called in mapper" do
28
+ @mapper.should_receive(:foo)
29
+ CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper)
30
+ DesignModel.design { foo }
31
+ end
32
+
33
+ it "should work even if a block is not provided" do
34
+ lambda { DesignModel.design }.should_not raise_error
35
+ end
36
+
37
+ end
38
+
39
+ describe "default_per_page" do
40
+ it "should return 25 default" do
41
+ DesignModel.default_per_page.should eql(25)
42
+ end
43
+ end
44
+
45
+ describe ".paginates_per" do
46
+ it "should set the default per page value" do
47
+ DesignModel.paginates_per(21)
48
+ DesignModel.default_per_page.should eql(21)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "DesignMapper" do
54
+
55
+ before :all do
56
+ @klass = CouchRest::Model::Designs::DesignMapper
57
+ end
58
+
59
+ it "should initialize and set model" do
60
+ object = @klass.new(DesignModel)
61
+ object.send(:model).should eql(DesignModel)
62
+ end
63
+
64
+ describe "#view" do
65
+
66
+ before :each do
67
+ @object = @klass.new(DesignModel)
68
+ end
69
+
70
+ it "should call create method on view" do
71
+ CouchRest::Model::Designs::View.should_receive(:create).with(DesignModel, 'test', {})
72
+ @object.view('test')
73
+ end
74
+
75
+ it "should create a method on parent model" do
76
+ CouchRest::Model::Designs::View.stub!(:create)
77
+ @object.view('test_view')
78
+ DesignModel.should respond_to(:test_view)
79
+ end
80
+
81
+ it "should create a method for view instance" do
82
+ CouchRest::Model::Designs::View.stub!(:create)
83
+ @object.should_receive(:create_view_method).with('test')
84
+ @object.view('test')
85
+ end
86
+ end
87
+
88
+ context "for model with auto_update_design_doc disabled " do
89
+ class ::DesignModelAutoUpdateDesignDocDisabled < ::CouchRest::Model::Base
90
+ self.auto_update_design_doc = false
91
+ end
92
+
93
+ describe "#view" do
94
+ before :each do
95
+ @object = @klass.new(DesignModelAutoUpdateDesignDocDisabled)
96
+ end
97
+
98
+ it "does not attempt to create view" do
99
+ CouchRest::Model::Designs::View.should_not_receive(:create)
100
+ @object.view('test')
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "#filter" do
106
+
107
+ before :each do
108
+ @object = @klass.new(DesignModel)
109
+ end
110
+
111
+ it "should add the provided function to the design doc" do
112
+ @object.filter(:important, "function(doc, req) { return doc.priority == 'high'; }")
113
+ DesignModel.design_doc['filters'].should_not be_empty
114
+ DesignModel.design_doc['filters']['important'].should_not be_blank
115
+ end
116
+
117
+ end
118
+
119
+ describe "#create_view_method" do
120
+ before :each do
121
+ @object = @klass.new(DesignModel)
122
+ end
123
+
124
+ it "should create a method that returns view instance" do
125
+ CouchRest::Model::Designs::View.should_receive(:new).with(DesignModel, {}, 'test_view').and_return(nil)
126
+ @object.create_view_method('test_view')
127
+ DesignModel.test_view
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,436 @@
1
+ require "spec_helper"
2
+
3
+ class WithCastedModelMixin
4
+ include CouchRest::Model::CastedModel
5
+ property :name
6
+ property :details, Object, :default => {}
7
+ property :casted_attribute, WithCastedModelMixin
8
+ end
9
+
10
+ class DirtyModel < CouchRest::Model::Base
11
+ use_database DB
12
+
13
+ property :casted_attribute, WithCastedModelMixin
14
+ property :title, :default => 'Sample Title'
15
+ property :details, Object, :default => { 'color' => 'blue' }
16
+ property :keywords, [String], :default => ['default-keyword']
17
+ property :sub_models do
18
+ property :title
19
+ end
20
+ end
21
+
22
+ class DirtyUniqueIdModel < CouchRest::Model::Base
23
+ use_database DB
24
+ attr_accessor :code
25
+ unique_id :code
26
+ property :title, String, :default => "Sample Title"
27
+ timestamps!
28
+
29
+ def code; self['_id'] || @code; end
30
+ end
31
+
32
+ describe "Dirty" do
33
+
34
+ describe "changes" do
35
+
36
+ it "should return changes on an attribute" do
37
+ @card = Card.new(:first_name => "matt")
38
+ @card.first_name = "andrew"
39
+ @card.first_name_changed?.should be_true
40
+ @card.changes.should == { "first_name" => ["matt", "andrew"] }
41
+ end
42
+
43
+ end
44
+
45
+ describe "save" do
46
+
47
+ it "should not save unchanged records" do
48
+ card_id = Card.create!(:first_name => "matt").id
49
+ @card = Card.find(card_id)
50
+ @card.database.should_not_receive(:save_doc)
51
+ @card.save
52
+ end
53
+
54
+ it "should save changed records" do
55
+ card_id = Card.create!(:first_name => "matt").id
56
+ @card = Card.find(card_id)
57
+ @card.first_name = "andrew"
58
+ @card.database.should_receive(:save_doc).and_return({"ok" => true})
59
+ @card.save
60
+ end
61
+
62
+ end
63
+
64
+ describe "changed?" do
65
+
66
+ # match activerecord behaviour
67
+ it "should report no changes on a new object with no attributes set" do
68
+ @card = Card.new
69
+ @card.changed?.should be_false
70
+ end
71
+
72
+ it "should report no changes on a hash property with a default value" do
73
+ @obj = DirtyModel.new
74
+ @obj.details.changed?.should be_false
75
+ end
76
+
77
+ # match activerecord behaviour
78
+ it "should report changes on a new object with attributes set" do
79
+ @card = Card.new(:first_name => "matt")
80
+ @card.changed?.should be_true
81
+ end
82
+
83
+ it "should report no changes on new object with 'unique_id' set" do
84
+ @obj = DirtyUniqueIdModel.new
85
+ @obj.changed?.should be_false
86
+ @obj.changes.should be_empty
87
+ end
88
+
89
+ it "should report no changes on objects fetched from the database" do
90
+ card_id = Card.create!(:first_name => "matt").id
91
+ @card = Card.find(card_id)
92
+ @card.changed?.should be_false
93
+ end
94
+
95
+ it "should report changes if the record is modified" do
96
+ @card = Card.new
97
+ @card.first_name = "andrew"
98
+ @card.changed?.should be_true
99
+ @card.first_name_changed?.should be_true
100
+ end
101
+
102
+ it "should report no changes for unmodified records" do
103
+ card_id = Card.create!(:first_name => "matt").id
104
+ @card = Card.find(card_id)
105
+ @card.first_name = "matt"
106
+ @card.changed?.should be_false
107
+ @card.first_name_changed?.should be_false
108
+ end
109
+
110
+ it "should report no changes after a new record has been saved" do
111
+ @card = Card.new(:first_name => "matt")
112
+ @card.save!
113
+ @card.changed?.should be_false
114
+ end
115
+
116
+ it "should report no changes after a record has been saved" do
117
+ card_id = Card.create!(:first_name => "matt").id
118
+ @card = Card.find(card_id)
119
+ @card.first_name = "andrew"
120
+ @card.save!
121
+ @card.changed?.should be_false
122
+ end
123
+
124
+ # test changing list properties
125
+
126
+ it "should report changes if a list property is modified" do
127
+ cat_id = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}]).id
128
+ @cat = Cat.find(cat_id)
129
+ @cat.toys = [{:name => "Feather"}]
130
+ @cat.changed?.should be_true
131
+ end
132
+
133
+ it "should report no changes if a list property is unmodified" do
134
+ cat_id = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}]).id
135
+ @cat = Cat.find(cat_id)
136
+ @cat.toys = [{:name => "Mouse"}] # same as original list
137
+ @cat.changed?.should be_false
138
+ end
139
+
140
+ # attachments
141
+
142
+ it "should report changes if an attachment is added" do
143
+ cat_id = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}]).id
144
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
145
+ @cat = Cat.find(cat_id)
146
+ @cat.create_attachment(:file => @file, :name => "my_attachment")
147
+ @cat.changed?.should be_true
148
+ end
149
+
150
+ it "should report changes if an attachment is deleted" do
151
+ @cat = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}])
152
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
153
+ @attachment_name = "my_attachment"
154
+ @cat.create_attachment(:file => @file, :name => @attachment_name)
155
+ @cat.save
156
+ @cat = Cat.find(@cat.id)
157
+ @cat.delete_attachment(@attachment_name)
158
+ @cat.changed?.should be_true
159
+ end
160
+
161
+ # casted models
162
+
163
+ it "should report changes to casted models" do
164
+ @cat = Cat.create!(:name => "Felix", :favorite_toy => { :name => "Mouse" })
165
+ @cat = Cat.find(@cat.id)
166
+ @cat.favorite_toy.name = 'Feather'
167
+ @cat.changed?.should be_true
168
+ end
169
+
170
+ it "should report changes to casted model in array" do
171
+ @obj = Cat.create!(:name => 'felix', :toys => [{:name => "Catnip"}])
172
+ @obj = Cat.get(@obj.id)
173
+ @obj.toys.first.name.should eql('Catnip')
174
+ @obj.toys.first.changed?.should be_false
175
+ @obj.changed?.should be_false
176
+ @obj.toys.first.name = "Super Catnip"
177
+ @obj.toys.first.changed?.should be_true
178
+ @obj.changed?.should be_true
179
+ end
180
+
181
+ it "should report changes to anonymous casted models in array" do
182
+ @obj = DirtyModel.create!(:sub_models => [{:title => "Sample"}])
183
+ @obj = DirtyModel.get(@obj.id)
184
+ @obj.sub_models.first.title.should eql("Sample")
185
+ @obj.sub_models.first.changed?.should be_false
186
+ @obj.changed?.should be_false
187
+ @obj.sub_models.first.title = "Another Sample"
188
+ @obj.sub_models.first.changed?.should be_true
189
+ @obj.changed?.should be_true
190
+ end
191
+
192
+ # casted arrays
193
+
194
+ def test_casted_array(change_expected)
195
+ obj = DirtyModel.create!
196
+ obj = DirtyModel.get(obj.id)
197
+ array = obj.keywords
198
+ yield array, obj
199
+ if change_expected
200
+ obj.changed?.should be_true
201
+ else
202
+ obj.changed?.should be_false
203
+ end
204
+ end
205
+
206
+ def should_change_array
207
+ test_casted_array(true) { |a,b| yield a,b }
208
+ end
209
+
210
+ def should_not_change_array
211
+ test_casted_array(false) { |a,b| yield a,b }
212
+ end
213
+
214
+ it "should report changes if an array index is modified" do
215
+ should_change_array do |array, obj|
216
+ array[0] = "keyword"
217
+ end
218
+ end
219
+
220
+ it "should report no changes if an array index is unmodified" do
221
+ should_not_change_array do |array, obj|
222
+ array[0] = array[0]
223
+ end
224
+ end
225
+
226
+ it "should report changes if an array is appended with <<" do
227
+ should_change_array do |array, obj|
228
+ array << 'keyword'
229
+ end
230
+ end
231
+
232
+ it "should report changes if an array is popped" do
233
+ should_change_array do |array, obj|
234
+ array.pop
235
+ end
236
+ end
237
+
238
+ it "should report changes if an array is popped after reload" do
239
+ should_change_array do |array, obj|
240
+ obj.reload
241
+ obj.keywords.pop
242
+ end
243
+ end
244
+
245
+
246
+ it "should report no changes if an empty array is popped" do
247
+ should_not_change_array do |array, obj|
248
+ array.clear
249
+ obj.save! # clears changes
250
+ array.pop
251
+ end
252
+ end
253
+
254
+ it "should report changes on deletion from an array" do
255
+ should_change_array do |array, obj|
256
+ array << "keyword"
257
+ obj.save!
258
+ array.delete_at(0)
259
+ end
260
+
261
+ should_change_array do |array, obj|
262
+ array << "keyword"
263
+ obj.save!
264
+ array.delete("keyword")
265
+ end
266
+ end
267
+
268
+ it "should report changes on deletion from an array after reload" do
269
+ should_change_array do |array, obj|
270
+ array << "keyword"
271
+ obj.save!
272
+ obj.reload
273
+ array.delete_at(0)
274
+ end
275
+
276
+ should_change_array do |array, obj|
277
+ array << "keyword"
278
+ obj.save!
279
+ obj.reload
280
+ array.delete("keyword")
281
+ end
282
+ end
283
+
284
+ it "should report no changes on deletion from an empty array" do
285
+ should_not_change_array do |array, obj|
286
+ array.clear
287
+ obj.save!
288
+ array.delete_at(0)
289
+ end
290
+
291
+ should_not_change_array do |array, obj|
292
+ array.clear
293
+ obj.save!
294
+ array.delete("keyword")
295
+ end
296
+ end
297
+
298
+ it "should report changes if an array is pushed" do
299
+ should_change_array do |array, obj|
300
+ array.push("keyword")
301
+ end
302
+ end
303
+
304
+ it "should report changes if an array is shifted" do
305
+ should_change_array do |array, obj|
306
+ array.shift
307
+ end
308
+ end
309
+
310
+ it "should report no changes if an empty array is shifted" do
311
+ should_not_change_array do |array, obj|
312
+ array.clear
313
+ obj.save! # clears changes
314
+ array.shift
315
+ end
316
+ end
317
+
318
+ it "should report changes if an array is unshifted" do
319
+ should_change_array do |array, obj|
320
+ array.unshift("keyword")
321
+ end
322
+ end
323
+
324
+ it "should report changes if an array is cleared" do
325
+ should_change_array do |array, obj|
326
+ array.clear
327
+ end
328
+ end
329
+
330
+ # Object, {} (casted hash)
331
+
332
+ def test_casted_hash(change_expected)
333
+ obj = DirtyModel.create!
334
+ obj = DirtyModel.get(obj.id)
335
+ hash = obj.details
336
+ yield hash, obj
337
+ if change_expected
338
+ obj.changed?.should be_true
339
+ else
340
+ obj.changed?.should be_false
341
+ end
342
+ end
343
+
344
+ def should_change_hash
345
+ test_casted_hash(true) { |a,b| yield a,b }
346
+ end
347
+
348
+ def should_not_change_hash
349
+ test_casted_hash(false) { |a,b| yield a,b }
350
+ end
351
+
352
+ it "should report changes if a hash is modified" do
353
+ should_change_hash do |hash, obj|
354
+ hash['color'] = 'orange'
355
+ end
356
+ end
357
+
358
+ it "should report no changes if a hash is unmodified" do
359
+ should_not_change_hash do |hash, obj|
360
+ hash['color'] = hash['color']
361
+ end
362
+ end
363
+
364
+ it "should report changes when deleting from a hash" do
365
+ should_change_hash do |hash, obj|
366
+ hash.delete('color')
367
+ end
368
+ end
369
+
370
+ it "should report no changes when deleting a non existent key from a hash" do
371
+ should_not_change_hash do |hash, obj|
372
+ hash.delete('non-existent-key')
373
+ end
374
+ end
375
+
376
+ it "should report changes when clearing a hash" do
377
+ should_change_hash do |hash, obj|
378
+ hash.clear
379
+ end
380
+ end
381
+
382
+ it "should report changes when merging changes to a hash" do
383
+ should_change_hash do |hash, obj|
384
+ hash.merge!('foo' => 'bar')
385
+ end
386
+ end
387
+
388
+ it "should report no changes when merging no changes to a hash" do
389
+ should_not_change_hash do |hash, obj|
390
+ hash.merge!('color' => hash['color'])
391
+ end
392
+ end
393
+
394
+ it "should report changes when replacing hash content" do
395
+ should_change_hash do |hash, obj|
396
+ hash.replace('foo' => 'bar')
397
+ end
398
+ end
399
+
400
+ it "should report no changes when replacing hash content with same content" do
401
+ should_not_change_hash do |hash, obj|
402
+ hash.replace(hash)
403
+ end
404
+ end
405
+
406
+ it "should report changes when removing records with delete_if" do
407
+ should_change_hash do |hash, obj|
408
+ hash.delete_if { true }
409
+ end
410
+ end
411
+
412
+ it "should report no changes when removing no records with delete_if" do
413
+ should_not_change_hash do |hash, obj|
414
+ hash.delete_if { false }
415
+ end
416
+ end
417
+
418
+ if {}.respond_to?(:keep_if)
419
+
420
+ it "should report changes when removing records with keep_if" do
421
+ should_change_hash do |hash, obj|
422
+ hash.keep_if { false }
423
+ end
424
+ end
425
+
426
+ it "should report no changes when removing no records with keep_if" do
427
+ should_not_change_hash do |hash, obj|
428
+ hash.keep_if { true }
429
+ end
430
+ end
431
+
432
+ end
433
+
434
+ end
435
+
436
+ end