openlogic-couchrest_model 1.0.0

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.
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