dm-accepts_nested_attributes_for 1.2.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 (58) hide show
  1. data/CHANGELOG +970 -0
  2. data/Gemfile +84 -0
  3. data/LICENSE +20 -0
  4. data/README.textile +94 -0
  5. data/Rakefile +25 -0
  6. data/TODO +6 -0
  7. data/VERSION +1 -0
  8. data/dm-accepts_nested_attributes.gemspec +114 -0
  9. data/lib/dm-accepts_nested_attributes.rb +13 -0
  10. data/lib/dm-accepts_nested_attributes/model.rb +144 -0
  11. data/lib/dm-accepts_nested_attributes/relationship.rb +82 -0
  12. data/lib/dm-accepts_nested_attributes/resource.rb +382 -0
  13. data/lib/dm-accepts_nested_attributes/version.rb +7 -0
  14. data/spec/accepts_nested_attributes_for_spec.rb +408 -0
  15. data/spec/assign_nested_attributes_for_spec.rb +101 -0
  16. data/spec/comb/1-1_disjoint_spec.rb +67 -0
  17. data/spec/comb/1-1_overlapping_spec.rb +66 -0
  18. data/spec/comb/1-1_subset_spec.rb +65 -0
  19. data/spec/comb/1-1_superset_spec.rb +67 -0
  20. data/spec/comb/1-m_disjoint_spec.rb +71 -0
  21. data/spec/comb/1-m_overlapping_spec.rb +70 -0
  22. data/spec/comb/1-m_subset_spec.rb +65 -0
  23. data/spec/comb/1-m_superset_spec.rb +71 -0
  24. data/spec/comb/m-1_disjoint_spec.rb +71 -0
  25. data/spec/comb/m-1_overlapping_spec.rb +70 -0
  26. data/spec/comb/m-1_subset_spec.rb +65 -0
  27. data/spec/comb/m-1_superset_spec.rb +71 -0
  28. data/spec/comb/n-m_composite_spec.rb +141 -0
  29. data/spec/comb/n-m_surrogate_spec.rb +154 -0
  30. data/spec/many_to_many_composite_spec.rb +120 -0
  31. data/spec/many_to_many_spec.rb +129 -0
  32. data/spec/many_to_one_composite_spec.rb +120 -0
  33. data/spec/many_to_one_spec.rb +101 -0
  34. data/spec/one_to_many_composite_spec.rb +120 -0
  35. data/spec/one_to_many_spec.rb +100 -0
  36. data/spec/one_to_one_composite_spec.rb +150 -0
  37. data/spec/one_to_one_spec.rb +115 -0
  38. data/spec/rcov.opts +6 -0
  39. data/spec/resource_spec.rb +65 -0
  40. data/spec/shared/many_to_many_composite_spec.rb +149 -0
  41. data/spec/shared/many_to_many_spec.rb +146 -0
  42. data/spec/shared/many_to_one_composite_spec.rb +160 -0
  43. data/spec/shared/many_to_one_spec.rb +130 -0
  44. data/spec/shared/one_to_many_composite_spec.rb +159 -0
  45. data/spec/shared/one_to_many_spec.rb +107 -0
  46. data/spec/shared/one_to_one_composite_spec.rb +114 -0
  47. data/spec/shared/one_to_one_spec.rb +111 -0
  48. data/spec/spec.opts +4 -0
  49. data/spec/spec_helper.rb +50 -0
  50. data/spec/update_dirty_spec.rb +113 -0
  51. data/spec/update_multiple_spec.rb +79 -0
  52. data/tasks/changelog.rake +20 -0
  53. data/tasks/ci.rake +1 -0
  54. data/tasks/local_gemfile.rake +18 -0
  55. data/tasks/spec.rake +22 -0
  56. data/tasks/yard.rake +9 -0
  57. data/tasks/yardstick.rake +19 -0
  58. metadata +216 -0
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module NestedAttributes
3
+
4
+ VERSION = '1.0.0'.freeze
5
+
6
+ end
7
+ end
@@ -0,0 +1,408 @@
1
+ require 'spec_helper'
2
+
3
+ describe "DataMapper::Model.accepts_nested_attributes_for" do
4
+
5
+ fixtures = <<-RUBY
6
+
7
+ class ::Branch
8
+ include DataMapper::Resource
9
+ property :id, Serial
10
+ has 1, :shop
11
+ has n, :items
12
+ has n, :bookings, :through => :items
13
+ end
14
+
15
+ class ::Shop
16
+ include DataMapper::Resource
17
+ property :id, Serial
18
+ belongs_to :branch
19
+ end
20
+
21
+ class ::Item
22
+ include DataMapper::Resource
23
+ property :id, Serial
24
+ belongs_to :branch
25
+ has n, :bookings
26
+ end
27
+
28
+ class ::Booking
29
+ include DataMapper::Resource
30
+ property :id, Serial
31
+ belongs_to :item
32
+ end
33
+
34
+ RUBY
35
+
36
+ before(:all) do
37
+ eval fixtures
38
+ DataMapper.auto_migrate!
39
+ end
40
+
41
+ before(:each) do
42
+
43
+ Object.send(:remove_const, 'Branch') if Object.const_defined?('Branch')
44
+ Object.send(:remove_const, 'Shop') if Object.const_defined?('Shop')
45
+ Object.send(:remove_const, 'Item') if Object.const_defined?('Item')
46
+ Object.send(:remove_const, 'Booking') if Object.const_defined?('Booking')
47
+
48
+ eval fixtures # neither class_eval nor instance_eval work here
49
+
50
+ end
51
+
52
+
53
+ describe "when called with" do
54
+
55
+ describe "no association_name" do
56
+
57
+ it "should raise" do
58
+ lambda { Branch.accepts_nested_attributes_for }.should raise_error(ArgumentError)
59
+ end
60
+
61
+ end
62
+
63
+ describe "nil as association_name" do
64
+
65
+ describe "and no options" do
66
+
67
+ it "should raise" do
68
+ lambda { Branch.accepts_nested_attributes_for(nil) }.should raise_error(ArgumentError)
69
+ end
70
+
71
+ end
72
+
73
+ describe "and empty options" do
74
+
75
+ it "should raise" do
76
+ lambda { Branch.accepts_nested_attributes_for(nil, {}) }.should raise_error(ArgumentError)
77
+ end
78
+
79
+ end
80
+
81
+ describe "and invalid options" do
82
+
83
+ it "should raise" do
84
+ lambda { Branch.accepts_nested_attributes_for(nil, { :foo => :bar }) }.should raise_error(ArgumentError)
85
+ end
86
+
87
+ end
88
+
89
+ describe "and valid options" do
90
+
91
+ it "should raise" do
92
+ lambda { Branch.accepts_nested_attributes_for(nil, { :allow_destroy => true }) }.should raise_error(ArgumentError)
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ describe "an invalid association_name" do
100
+
101
+ describe "and no options" do
102
+
103
+ it "should raise" do
104
+ lambda { Branch.accepts_nested_attributes_for(:foo) }.should raise_error(ArgumentError)
105
+ end
106
+
107
+ end
108
+
109
+ describe "and empty options" do
110
+
111
+ it "should raise" do
112
+ lambda { Branch.accepts_nested_attributes_for(:foo, {}) }.should raise_error(ArgumentError)
113
+ end
114
+
115
+ end
116
+
117
+ describe "and invalid options" do
118
+
119
+ it "should raise" do
120
+ lambda { Branch.accepts_nested_attributes_for(:foo, { :foo => :bar }) }.should raise_error(ArgumentError)
121
+ end
122
+
123
+ end
124
+
125
+ describe "and valid options" do
126
+
127
+ it "should raise" do
128
+ lambda { Branch.accepts_nested_attributes_for(:foo, { :allow_destroy => true }) }.should raise_error(ArgumentError)
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+
135
+
136
+ describe "a valid association_name", :shared => true do
137
+
138
+
139
+ describe "and no options" do
140
+
141
+ it "should not raise" do
142
+ lambda { @model.accepts_nested_attributes_for @association }.should_not raise_error
143
+ end
144
+
145
+ it "should store the accessible association in .options_for_nested_attributes" do
146
+ @model.options_for_nested_attributes[@association].should be_nil
147
+ @model.accepts_nested_attributes_for @association
148
+ relationship = @model.relationships[@association]
149
+ @model.options_for_nested_attributes[relationship.name].should_not be_nil
150
+ end
151
+
152
+ it "should store the default options under the association_name in .options_for_nested_attributes" do
153
+ @model.options_for_nested_attributes[@association].should be_nil
154
+ @model.accepts_nested_attributes_for @association
155
+ relationship = @model.relationships[@association]
156
+ @model.options_for_nested_attributes[relationship.name].should == { :allow_destroy => false }
157
+ end
158
+
159
+ it "should create a \#{association_name}_attributes instance reader" do
160
+ p = @model.new
161
+ p.respond_to?("#{@association}_attributes").should be_false
162
+ @model.accepts_nested_attributes_for @association
163
+ p = @model.new
164
+ p.respond_to?("#{@association}_attributes").should be_true
165
+ end
166
+
167
+ it "should create a \#{association_name}_attributes instance writer" do
168
+ p = @model.new
169
+ p.respond_to?("#{@association}_attributes=").should be_false
170
+ @model.accepts_nested_attributes_for @association
171
+ p = @model.new
172
+ p.respond_to?("#{@association}_attributes=").should be_true
173
+ end
174
+
175
+ end
176
+
177
+ describe "and empty options" do
178
+
179
+ it "should not raise" do
180
+ lambda { @model.accepts_nested_attributes_for @association, {} }.should_not raise_error
181
+ end
182
+
183
+ it "should store the accessible association in .autosave_associations" do
184
+ @model.options_for_nested_attributes[@association].should be_nil
185
+ @model.accepts_nested_attributes_for @association, {}
186
+ relationship = @model.relationships[@association]
187
+ @model.options_for_nested_attributes[relationship.name].should_not be_nil
188
+ end
189
+
190
+ it "should store the default options under the association_name in .autosave_associations" do
191
+ @model.options_for_nested_attributes[@association].should be_nil
192
+ @model.accepts_nested_attributes_for @association, {}
193
+ relationship = @model.relationships[@association]
194
+ @model.options_for_nested_attributes[relationship.name].should == { :allow_destroy => false }
195
+ end
196
+
197
+ it "should create a \#{association_name}_attributes instance reader" do
198
+ p = @model.new
199
+ p.respond_to?("#{@association}_attributes").should be_false
200
+ @model.accepts_nested_attributes_for @association, {}
201
+ p = @model.new
202
+ p.respond_to?("#{@association}_attributes").should be_true
203
+ end
204
+
205
+ it "should create a \#{association_name}_attributes instance writer" do
206
+ p = @model.new
207
+ p.respond_to?("#{@association}_attributes=").should be_false
208
+ @model.accepts_nested_attributes_for @association, {}
209
+ p = @model.new
210
+ p.respond_to?("#{@association}_attributes=").should be_true
211
+ end
212
+
213
+ end
214
+
215
+ describe "and invalid options" do
216
+
217
+ it "should raise" do
218
+ lambda { @model.accepts_nested_attributes_for @association, { :foo => :bar } }.should raise_error(DataMapper::NestedAttributes::InvalidOptions)
219
+ end
220
+
221
+ it "should not store the accessible association in .autosave_associations" do
222
+ @model.options_for_nested_attributes[@association].should be_nil
223
+ lambda { @model.accepts_nested_attributes_for @association, { :foo => :bar } }.should raise_error(DataMapper::NestedAttributes::InvalidOptions)
224
+ @model.options_for_nested_attributes[@association].should be_nil
225
+ end
226
+
227
+ it "should not create a \#{association_name}_attributes instance reader" do
228
+ p = @model.new
229
+ p.respond_to?("#{@association}_attributes").should be_false
230
+ lambda { @model.accepts_nested_attributes_for @association, { :foo => :bar } }.should raise_error(DataMapper::NestedAttributes::InvalidOptions)
231
+ p = @model.new
232
+ p.respond_to?("#{@association}_attributes").should be_false
233
+ end
234
+
235
+ it "should not create a \#{association_name}_attributes instance writer" do
236
+ p = @model.new
237
+ p.respond_to?("#{@association}_attributes=").should be_false
238
+ lambda { @model.accepts_nested_attributes_for @association, { :foo => :bar } }.should raise_error(DataMapper::NestedAttributes::InvalidOptions)
239
+ p = @model.new
240
+ p.respond_to?("#{@association}_attributes=").should be_false
241
+ end
242
+
243
+ end
244
+
245
+ describe "and valid options" do
246
+
247
+ it "should not raise" do
248
+ lambda { @model.accepts_nested_attributes_for @association, :allow_destroy => true }.should_not raise_error
249
+ end
250
+
251
+ it "should store the accessible association in .autosave_associations" do
252
+ @model.options_for_nested_attributes[@association].should be_nil
253
+ @model.accepts_nested_attributes_for @association, :allow_destroy => true
254
+ relationship = @model.relationships[@association]
255
+ @model.options_for_nested_attributes[relationship.name].should_not be_nil
256
+ end
257
+
258
+ it "should accept :allow_destroy as the only option (and thus overwrite the default option)" do
259
+ @model.options_for_nested_attributes[@association].should be_nil
260
+ @model.accepts_nested_attributes_for @association, :allow_destroy => true
261
+ relationship = @model.relationships[@association]
262
+ @model.options_for_nested_attributes[relationship.name].should == { :allow_destroy => true }
263
+ end
264
+
265
+ it "should accept :reject_if as the only option (and add :allow_destroy => false)" do
266
+ @model.options_for_nested_attributes[@association].should be_nil
267
+ @model.accepts_nested_attributes_for @association, :reject_if => lambda { |attributes| nil }
268
+ relationship = @model.relationships[@association]
269
+ @model.options_for_nested_attributes[relationship.name].should_not be_nil
270
+ @model.options_for_nested_attributes[relationship.name][:allow_destroy].should be_false
271
+ @model.options_for_nested_attributes[relationship.name][:reject_if].should be_kind_of(Proc)
272
+ end
273
+
274
+ it "should accept both :allow_destroy and :reject_if as options" do
275
+ @model.options_for_nested_attributes[@association].should be_nil
276
+ @model.accepts_nested_attributes_for @association, :allow_destroy => true, :reject_if => lambda { |attributes| nil }
277
+ relationship = @model.relationships[@association]
278
+ @model.options_for_nested_attributes[relationship.name].should_not be_nil
279
+ @model.options_for_nested_attributes[relationship.name][:allow_destroy].should be_true
280
+ @model.options_for_nested_attributes[relationship.name][:reject_if].should be_kind_of(Proc)
281
+ end
282
+
283
+ it "should create a \#{association_name}_attributes instance reader" do
284
+ p = @model.new
285
+ p.respond_to?("#{@association}_attributes").should be_false
286
+ @model.accepts_nested_attributes_for @association, :allow_destroy => true
287
+ p = @model.new
288
+ p.respond_to?("#{@association}_attributes").should be_true
289
+ end
290
+
291
+ it "should create a \#{association_name}_attributes instance writer" do
292
+ p = @model.new
293
+ p.respond_to?("#{@association}_attributes=").should be_false
294
+ @model.accepts_nested_attributes_for @association, :allow_destroy => true
295
+ p = @model.new
296
+ p.respond_to?("#{@association}_attributes=").should be_true
297
+ end
298
+
299
+ end
300
+
301
+ end
302
+
303
+ describe "a valid association_name pointing to multiple resources", :shared => true do
304
+
305
+ describe "and no options" do
306
+
307
+ it "should not create a get_\#{association_name} instance reader" do
308
+ p = @model.new
309
+ p.respond_to?("get_#{@association}").should be_false
310
+ lambda { @model.accepts_nested_attributes_for @association }.should_not raise_error
311
+ p = @model.new
312
+ p.respond_to?("get_#{@association}").should be_false
313
+ end
314
+
315
+ end
316
+
317
+ describe "and empty options" do
318
+
319
+ it "should not create a get_\#{association_name} instance reader" do
320
+ p = @model.new
321
+ p.respond_to?("get_#{@association}").should be_false
322
+ lambda { @model.accepts_nested_attributes_for @association, {} }.should_not raise_error
323
+ p = @model.new
324
+ p.respond_to?("get_#{@association}").should be_false
325
+ end
326
+
327
+ end
328
+
329
+ describe "and invalid options" do
330
+
331
+ it "should not create a get_\#{association_name} instance reader" do
332
+ p = @model.new
333
+ p.respond_to?("get_#{@association}").should be_false
334
+ lambda { @model.accepts_nested_attributes_for @association, { :foo => :bar } }.should raise_error(DataMapper::NestedAttributes::InvalidOptions)
335
+ p = @model.new
336
+ p.respond_to?("get_#{@association}").should be_false
337
+ end
338
+
339
+ end
340
+
341
+ describe "and valid options" do
342
+
343
+ it "should not create a get_\#{association_name} instance reader" do
344
+ p = @model.new
345
+ p.respond_to?("get_#{@association}").should be_false
346
+ lambda { @model.accepts_nested_attributes_for @association, { :allow_destroy => :true } }.should_not raise_error
347
+ p = @model.new
348
+ p.respond_to?("get_#{@association}").should be_false
349
+ end
350
+
351
+ end
352
+
353
+ end
354
+
355
+ # ----------------------------------------------------------------------------------------
356
+ # ----------------------------------------------------------------------------------------
357
+ # ----------------------------------------------------------------------------------------
358
+
359
+
360
+ describe "a valid belongs_to association_name" do
361
+
362
+ before(:each) do
363
+ @model = Item
364
+ @association = :branch
365
+ end
366
+
367
+ it_should_behave_like "a valid association_name"
368
+
369
+ end
370
+
371
+ describe "a valid has(1) association_name" do
372
+
373
+ before(:each) do
374
+ @model = Branch
375
+ @association = :shop
376
+ end
377
+
378
+ it_should_behave_like "a valid association_name"
379
+
380
+ end
381
+
382
+ describe "a valid has(n) association_name" do
383
+
384
+ before(:each) do
385
+ @model = Branch
386
+ @association = :items
387
+ end
388
+
389
+ it_should_behave_like "a valid association_name"
390
+ it_should_behave_like "a valid association_name pointing to multiple resources"
391
+
392
+ end
393
+
394
+ describe "a valid has(n, :through) association_name" do
395
+
396
+ before(:each) do
397
+ @model = Branch
398
+ @association = :bookings
399
+ end
400
+
401
+ it_should_behave_like "a valid association_name"
402
+ it_should_behave_like "a valid association_name pointing to multiple resources"
403
+
404
+ end
405
+
406
+ end
407
+
408
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe "DataMapper::Model#assign_nested_attributes_for" do
4
+
5
+ fixtures = <<-RUBY
6
+
7
+ class ::Branch
8
+ include DataMapper::Resource
9
+ property :id, Serial
10
+ has 1, :shop
11
+ has n, :items
12
+ has n, :bookings, :through => :items
13
+
14
+ accepts_nested_attributes_for :shop
15
+ accepts_nested_attributes_for :items
16
+ accepts_nested_attributes_for :bookings
17
+ end
18
+
19
+ class ::Shop
20
+ include DataMapper::Resource
21
+ property :id, Serial
22
+ belongs_to :branch
23
+ end
24
+
25
+ class ::Item
26
+ include DataMapper::Resource
27
+ property :id, Serial
28
+ belongs_to :branch
29
+ has n, :bookings
30
+ end
31
+
32
+ class ::Booking
33
+ include DataMapper::Resource
34
+ property :id, Serial
35
+ belongs_to :item
36
+ end
37
+
38
+ RUBY
39
+
40
+ before(:all) do
41
+ eval fixtures
42
+ DataMapper.auto_migrate!
43
+ end
44
+
45
+ before(:each) do
46
+ Object.send(:remove_const, 'Branch') if Object.const_defined?('Branch')
47
+ Object.send(:remove_const, 'Shop') if Object.const_defined?('Shop')
48
+ Object.send(:remove_const, 'Item') if Object.const_defined?('Item')
49
+ Object.send(:remove_const, 'Booking') if Object.const_defined?('Booking')
50
+
51
+ eval fixtures # neither class_eval nor instance_eval work here
52
+ end
53
+
54
+
55
+ describe "resource" do
56
+ it "should raise unless called with a Hash" do
57
+ lambda { Branch.new.shop_attributes = [] }.should raise_error(ArgumentError)
58
+ lambda { Branch.new.shop_attributes = "" }.should raise_error(ArgumentError)
59
+ end
60
+
61
+ it "should not raise when called with a Hash" do
62
+ lambda { Branch.new.shop_attributes = {} }.should_not raise_error(ArgumentError)
63
+ end
64
+ end
65
+
66
+ describe "collection" do
67
+ it "should not raise when called with a Hash of Hashes" do
68
+ lambda { Branch.new.items_attributes = {:a => {}} }.should_not raise_error(ArgumentError)
69
+ end
70
+
71
+ it "should not raise when called with an Array of Hashes" do
72
+ lambda { Branch.new.items_attributes = [{}] }.should_not raise_error(ArgumentError)
73
+ end
74
+
75
+ it "should raise when called with a param that is not a Hash or Array" do
76
+ lambda { Branch.new.items_attributes = "" }.should raise_error(ArgumentError)
77
+ end
78
+
79
+ it "should raise when called with a Hash that includes not only Hashes" do
80
+ lambda { Branch.new.items_attributes = {:a => "", :b => {}} }.should raise_error(ArgumentError)
81
+ end
82
+
83
+ it "should raise when called with an Array that includes not only Hashes" do
84
+ lambda { Branch.new.items_attributes = ["", {}] }.should raise_error(ArgumentError)
85
+ end
86
+ end
87
+
88
+ describe "collection with :through" do
89
+ it "should raise unless called with a Hash or Array" do
90
+ lambda { Branch.new.bookings_attributes = "" }.should raise_error(ArgumentError)
91
+ end
92
+
93
+ it "should not raise when called with a Hash" do
94
+ lambda { Branch.new.bookings_attributes = {} }.should_not raise_error(ArgumentError)
95
+ end
96
+
97
+ it "should not raise when called with an Array" do
98
+ lambda { Branch.new.bookings_attributes = [] }.should_not raise_error(ArgumentError)
99
+ end
100
+ end
101
+ end