dm-accepts_nested_attributes 0.12.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.
@@ -0,0 +1,259 @@
1
+ module DataMapper
2
+ module NestedAttributes
3
+
4
+ ##
5
+ # Extensions and customizations for @see DataMapper::Resource
6
+ # that are needed if the @see DataMapper::Resource wants to
7
+ # accept nested attributes for any given relationship.
8
+ # Basically, this module provides functionality that allows
9
+ # either assignment or marking for destruction of related parent
10
+ # and child associations, based on the given attributes and what
11
+ # kind of relationship should be altered.
12
+ module Resource
13
+
14
+ ##
15
+ # Can be used to remove ambiguities from the passed attributes.
16
+ # Consider a situation with a belongs_to association where both a valid value
17
+ # for the foreign_key attribute *and* nested_attributes for a new record are
18
+ # present (i.e. item_type_id and item_type_attributes are present).
19
+ # Also see http://is.gd/sz2d on the rails-core ml for a discussion on this.
20
+ # The basic idea is, that there should be a well defined behavior for what
21
+ # exactly happens when such a situation occurs. I'm currently in favor for
22
+ # using the foreign_key if it is present, but this probably needs more thinking.
23
+ # For now, this method basically is a no-op, but at least it provides a hook where
24
+ # everyone can perform it's own sanitization by overwriting this method.
25
+ #
26
+ # @param attributes [Hash]
27
+ # The attributes to sanitize
28
+ #
29
+ # @return [Hash]
30
+ # The sanitized attributes
31
+ #
32
+ def sanitize_nested_attributes(attributes)
33
+ attributes # noop
34
+ end
35
+
36
+ ##
37
+ # Destroy intermediate resources that possibly need to be cleaned up
38
+ def save(*)
39
+ saved = super
40
+ remove_destroyables
41
+ saved
42
+ end
43
+
44
+ private
45
+
46
+ ##
47
+ # Attribute hash keys that should not be assigned as normal attributes.
48
+ #
49
+ # @return [#each]
50
+ # The model key and :_delete, the latter being a special value
51
+ # used to mark a resource for destruction
52
+ def unassignable_keys
53
+ model.key.to_a << :_delete
54
+ end
55
+
56
+
57
+ ##
58
+ # Assigns the given attributes to the resource association.
59
+ #
60
+ # If the given attributes include an <tt>:id</tt> that matches the existing
61
+ # record’s id, then the existing record will be modified. Otherwise a new
62
+ # record will be built.
63
+ #
64
+ # If the given attributes include a matching <tt>:id</tt> attribute _and_ a
65
+ # <tt>:_delete</tt> key set to a truthy value, then the existing record
66
+ # will be marked for destruction.
67
+ #
68
+ # @param relationship [DataMapper::Associations::Relationship]
69
+ # The relationship backing the association.
70
+ # Assignment will happen on the target end of the relationship
71
+ #
72
+ # @param attributes [Hash]
73
+ # The attributes to assign to the relationship's target end
74
+ # All attributes except @see UNASSIGNABLE_KEYS will be assigned
75
+ #
76
+ # @return nil
77
+ def assign_nested_attributes_for_related_resource(relationship, attributes)
78
+ if attributes[:id].blank?
79
+ return if reject_new_record?(relationship, attributes)
80
+ new_record = relationship.target_model.new(attributes.except(*unassignable_keys))
81
+ relationship.set(self, new_record)
82
+ else
83
+ existing_record = relationship.get(self)
84
+ if existing_record && existing_record.id.to_s == attributes[:id].to_s
85
+ update_or_mark_as_destroyable(relationship, existing_record, attributes)
86
+ end
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Assigns the given attributes to the collection association.
92
+ #
93
+ # Hashes with an <tt>:id</tt> value matching an existing associated record
94
+ # will update that record. Hashes without an <tt>:id</tt> value will build
95
+ # a new record for the association. Hashes with a matching <tt>:id</tt>
96
+ # value and a <tt>:_delete</tt> key set to a truthy value will mark the
97
+ # matched record for destruction.
98
+ #
99
+ # For example:
100
+ #
101
+ # assign_nested_attributes_for_collection_association(:people, {
102
+ # '1' => { :id => '1', :name => 'Peter' },
103
+ # '2' => { :name => 'John' },
104
+ # '3' => { :id => '2', :_delete => true }
105
+ # })
106
+ #
107
+ # Will update the name of the Person with ID 1, build a new associated
108
+ # person with the name `John', and mark the associatied Person with ID 2
109
+ # for destruction.
110
+ #
111
+ # Also accepts an Array of attribute hashes:
112
+ #
113
+ # assign_nested_attributes_for_collection_association(:people, [
114
+ # { :id => '1', :name => 'Peter' },
115
+ # { :name => 'John' },
116
+ # { :id => '2', :_delete => true }
117
+ # ])
118
+ #
119
+ # @param relationship [DataMapper::Associations::Relationship]
120
+ # The relationship backing the association.
121
+ # Assignment will happen on the target end of the relationship
122
+ #
123
+ # @param attributes [Hash]
124
+ # The attributes to assign to the relationship's target end
125
+ # All attributes except @see UNASSIGNABLE_KEYS will be assigned
126
+ #
127
+ # @return nil
128
+ def assign_nested_attributes_for_related_collection(relationship, attributes_collection)
129
+
130
+ normalize_attributes_collection(attributes_collection).each do |attributes|
131
+
132
+ if attributes[:id].blank?
133
+ next if reject_new_record?(relationship, attributes)
134
+ relationship.get(self).new(attributes.except(*unassignable_keys))
135
+ else
136
+ collection = relationship.get(self)
137
+ if existing_record = collection.get(attributes[:id])
138
+ update_or_mark_as_destroyable(relationship, existing_record, attributes)
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+ ##
147
+ # Updates a record with the +attributes+ or marks it for destruction if
148
+ # +allow_destroy+ is +true+ and has_delete_flag? returns +true+.
149
+ #
150
+ # @param relationship [DataMapper::Associations::Relationship]
151
+ # The relationship backing the association.
152
+ # Assignment will happen on the target end of the relationship
153
+ #
154
+ # @param attributes [Hash]
155
+ # The attributes to assign to the relationship's target end
156
+ # All attributes except @see UNASSIGNABLE_KEYS will be assigned
157
+ #
158
+ # @return nil
159
+ def update_or_mark_as_destroyable(relationship, resource, attributes)
160
+ allow_destroy = self.class.options_for_nested_attributes[relationship][:allow_destroy]
161
+ if has_delete_flag?(attributes) && allow_destroy
162
+ if relationship.is_a?(DataMapper::Associations::ManyToMany::Relationship)
163
+ intermediaries = relationship.through.get(self).all(relationship.via => resource)
164
+ intermediaries.each { |intermediate| destroyables << intermediate }
165
+ end
166
+ destroyables << resource
167
+ else
168
+ resource.update(attributes.except(*unassignable_keys))
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Determines if the given attributes hash contains a truthy :_delete key.
174
+ #
175
+ # @param attributes [Hash] The attributes to test
176
+ #
177
+ # @return [TrueClass, FalseClass]
178
+ # true, if attributes contains a truthy :_delete key
179
+ def has_delete_flag?(attributes)
180
+ !!attributes[:_delete]
181
+ end
182
+
183
+ ##
184
+ # Determines if a new record should be built with the given attributes.
185
+ # Rejects a new record if @see has_delete_flag? returns true for the given attributes,
186
+ # or if a :reject_if guard exists for the passed relationship that evaluates to +true+.
187
+ #
188
+ # @param relationship [DataMapper::Associations::Relationship]
189
+ # The relationship backing the association.
190
+ #
191
+ # @param attributes [Hash]
192
+ # The attributes to test with @see has_delete_flag?
193
+ #
194
+ # @return [TrueClass, FalseClass]
195
+ # true, if the given attributes will be rejected
196
+ def reject_new_record?(relationship, attributes)
197
+ guard = self.class.options_for_nested_attributes[relationship][:reject_if]
198
+ return false if guard.nil? # if relationship guard is nil, nothing will be rejected
199
+ has_delete_flag?(attributes) || evaluate_reject_new_record_guard(guard, attributes)
200
+ end
201
+
202
+ ##
203
+ # Evaluates the given guard by calling it with the given attributes
204
+ #
205
+ # @param [Symbol, String, #call] guard
206
+ # An instance method name or an object that respond_to?(:call), which
207
+ # would stop a new record from being created, if it evaluates to true.
208
+ #
209
+ # @param [Hash] attributes
210
+ # The attributes to pass to the guard for evaluating if it should reject
211
+ # the creation of a new resource
212
+ #
213
+ # @raise [ArgumentError]
214
+ # If the given guard doesn't match [Symbol, String, #call]
215
+ #
216
+ # @return [true, false]
217
+ # The value returned by evaluating the guard
218
+ def evaluate_reject_new_record_guard(guard, attributes)
219
+ if guard.is_a?(Symbol) || guard.is_a?(String)
220
+ send(guard, attributes)
221
+ elsif guard.respond_to?(:call)
222
+ guard.call(attributes)
223
+ else
224
+ # never reached when called from inside the plugin
225
+ raise ArgumentError, "guard must be a Symbol, a String, or respond_to?(:call)"
226
+ end
227
+ end
228
+
229
+ ##
230
+ # Make sure to return a collection of attribute hashes.
231
+ # If passed an attributes hash, map it to its attributes
232
+ #
233
+ # @param attributes [Hash, #each]
234
+ # An attributes hash or a collection of attribute hashes
235
+ #
236
+ # @return [#each]
237
+ # A collection of attribute hashes
238
+ def normalize_attributes_collection(attributes)
239
+ if attributes.is_a?(Hash)
240
+ attributes.map { |_, attributes| attributes }
241
+ else
242
+ attributes
243
+ end
244
+ end
245
+
246
+
247
+ def destroyables
248
+ @destroyables ||= []
249
+ end
250
+
251
+ def remove_destroyables
252
+ destroyables.each { |r| r.destroy if r.saved? }
253
+ @destroyables.clear
254
+ end
255
+
256
+ end
257
+
258
+ end
259
+ end
@@ -0,0 +1,24 @@
1
+ module DataMapper
2
+ module NestedAttributes
3
+
4
+ module TransactionalSave
5
+
6
+ ##
7
+ # Overrides @see DataMapper::Resource#save to perform inside a transaction.
8
+ # The current implementation simply wraps the saving of the complete object tree
9
+ # inside a transaction and rolls back in case any exceptions are raised,
10
+ # or any of the calls to
11
+ #
12
+ # @see DataMapper::Resource#save
13
+ #
14
+ # @return [Boolean]
15
+ # true if all related resources were saved properly
16
+ #
17
+ def save
18
+ transaction { |t| super || t.rollback && false }
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module NestedAttributes
3
+
4
+ VERSION = '0.12.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].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].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].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].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].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].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].should_not be_nil
270
+ @model.options_for_nested_attributes[relationship][:allow_destroy].should be_false
271
+ @model.options_for_nested_attributes[relationship][: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].should_not be_nil
279
+ @model.options_for_nested_attributes[relationship][:allow_destroy].should be_true
280
+ @model.options_for_nested_attributes[relationship][: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