backbone-nested-attributes 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +21 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +187 -0
  6. data/Rakefile +15 -0
  7. data/app/assets/javascripts/backbone-nested-attributes/all.js +2 -0
  8. data/app/assets/javascripts/backbone-nested-attributes/model.js +175 -0
  9. data/app/assets/javascripts/backbone-nested-attributes/undoable.js +60 -0
  10. data/backbone-nested-attributes.gemspec +20 -0
  11. data/lib/backbone-nested-attributes.rb +6 -0
  12. data/lib/backbone-nested-attributes/engine.rb +7 -0
  13. data/lib/backbone-nested-attributes/version.rb +5 -0
  14. data/spec/dummy/README.rdoc +261 -0
  15. data/spec/dummy/Rakefile +7 -0
  16. data/spec/dummy/app/assets/javascripts/application.js +18 -0
  17. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  18. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  19. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  20. data/spec/dummy/app/mailers/.gitkeep +0 -0
  21. data/spec/dummy/app/models/.gitkeep +0 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  23. data/spec/dummy/config.ru +4 -0
  24. data/spec/dummy/config/application.rb +79 -0
  25. data/spec/dummy/config/boot.rb +10 -0
  26. data/spec/dummy/config/database.yml +25 -0
  27. data/spec/dummy/config/environment.rb +5 -0
  28. data/spec/dummy/config/environments/development.rb +37 -0
  29. data/spec/dummy/config/environments/production.rb +67 -0
  30. data/spec/dummy/config/environments/test.rb +37 -0
  31. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/spec/dummy/config/initializers/inflections.rb +15 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  35. data/spec/dummy/config/initializers/session_store.rb +8 -0
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  37. data/spec/dummy/config/locales/en.yml +5 -0
  38. data/spec/dummy/config/routes.rb +58 -0
  39. data/spec/dummy/db/.gitkeep +0 -0
  40. data/spec/dummy/lib/assets/.gitkeep +0 -0
  41. data/spec/dummy/log/.gitkeep +0 -0
  42. data/spec/dummy/public/404.html +26 -0
  43. data/spec/dummy/public/422.html +26 -0
  44. data/spec/dummy/public/500.html +25 -0
  45. data/spec/dummy/public/favicon.ico +0 -0
  46. data/spec/dummy/script/rails +6 -0
  47. data/spec/javascripts/backbone-nested-attributes/ModelSpec.js +626 -0
  48. data/spec/javascripts/backbone-nested-attributes/UndoableSpec.js +489 -0
  49. data/spec/javascripts/helpers/SpecHelper.js +7 -0
  50. data/spec/javascripts/helpers/mock-ajax.js +207 -0
  51. data/spec/javascripts/support/jasmine.yml +76 -0
  52. metadata +156 -0
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,626 @@
1
+ describe("Backbone.NestedAttributesModel", function() {
2
+ var model,
3
+ Post,
4
+ post,
5
+ Comment,
6
+ comment,
7
+ Person,
8
+ author
9
+
10
+ beforeEach(function() {
11
+ model = new Backbone.NestedAttributesModel({})
12
+ })
13
+
14
+ it("should be a Backbone.Model", function() {
15
+ expect(model).toBeAnInstanceOf(Backbone.Model)
16
+ })
17
+
18
+ describe("with a has one relationship", function() {
19
+ beforeEach(function() {
20
+ Post = Backbone.NestedAttributesModel.extend({
21
+ relations: [
22
+ {
23
+ type: 'one',
24
+ key: 'author',
25
+ relatedModel: function () { return Person }
26
+ }
27
+ ]
28
+ })
29
+
30
+ Person = Backbone.NestedAttributesModel.extend({})
31
+ })
32
+
33
+ describe("when creating", function() {
34
+ it("does not initializes the author attribute", function() {
35
+ model = new Post
36
+ expect(model.get('author')).not.toBeDefined()
37
+ });
38
+
39
+ describe("while setting attributes", function() {
40
+ beforeEach(function() {
41
+ model = new Post({ title: 'Some Title', author: { name: 'Jon Snow'} })
42
+ })
43
+
44
+ it("sets the normal attributes", function() {
45
+ expect(model.get('title')).toEqual('Some Title')
46
+ })
47
+
48
+ it("creates the author model", function() {
49
+ author = model.get('author')
50
+
51
+ expect(author).toBeAnInstanceOf(Person)
52
+ expect(author.get('name')).toEqual('Jon Snow')
53
+ })
54
+
55
+ describe("passing a model to the relation attribute", function() {
56
+ beforeEach(function() {
57
+ author = new Person({ name: 'Jon Snow' })
58
+ model = new Post({ title: 'Some Title', author: author })
59
+ })
60
+
61
+ it("store the given model in the relation attribute", function() {
62
+ expect(model.get('author')).toBe(author)
63
+ })
64
+ })
65
+ })
66
+ })
67
+
68
+ describe("when updating", function() {
69
+ beforeEach(function() {
70
+ model = new Post
71
+ })
72
+
73
+ it("store the given model in the relation attribute", function() {
74
+ model.set({ author: { name: 'Jon Snow' } })
75
+
76
+ author = model.get('author')
77
+
78
+ expect(author).toBeAnInstanceOf(Person)
79
+ expect(author.get('name')).toEqual('Jon Snow')
80
+ })
81
+
82
+ it("allows passing key, value when setting a relation attribute", function() {
83
+ model.set('author', { name: 'Jon Snow' })
84
+
85
+ author = model.get('author')
86
+
87
+ expect(author).toBeAnInstanceOf(Person)
88
+ expect(author.get('name')).toEqual('Jon Snow')
89
+ })
90
+
91
+ describe("passing a model to the relation attribute", function() {
92
+ beforeEach(function() {
93
+ author = new Person({ name: 'Jon Snow' })
94
+ model = new Post
95
+ })
96
+
97
+ it("store the given model in the relation attribute", function() {
98
+ model.set({ title: 'Some title', author: author })
99
+ expect(model.get('author')).toBe(author)
100
+ })
101
+ })
102
+ })
103
+
104
+ describe("cloning", function() {
105
+ beforeEach(function() {
106
+ post = new Post({ title: 'Some Title', author: { name: 'Jon Snow' } })
107
+ })
108
+
109
+ it("creates a new post object, with the same attributes, including nested has one relations, that are not shared", function() {
110
+ var newPost = post.clone()
111
+
112
+ expect(newPost).not.toBe(post)
113
+ expect(newPost.get('author')).not.toBe(post.get('author'))
114
+
115
+ expect(newPost.get('title')).toEqual(post.get('title'))
116
+ expect(newPost.get('author').get('name')).toEqual(post.get('author').get('name'))
117
+ })
118
+ })
119
+
120
+ describe("event bubbling", function() {
121
+ var changedModel, changedCount
122
+
123
+ beforeEach(function() {
124
+ changedCount = 0
125
+ changedModel = undefined
126
+
127
+ model = new Post({ title: 'Some Title', author: { name: 'Jon Snow' } })
128
+ author = model.get('author')
129
+ })
130
+
131
+ describe("when a nested model is changed", function() {
132
+ it("triggers a nested:change event on the parent, with the changed model as an argument", function() {
133
+ model.on('nested:change', function (model) {
134
+ changedModel = model
135
+ })
136
+
137
+ author.set({ name: 'Robb Stark' })
138
+ expect(changedModel).toBe(author)
139
+ })
140
+
141
+ it("triggers a nested:change only once, after setting nested attributes again", function() {
142
+ model.set({ title: 'Some Title', author: { name: 'Jon Snow' } })
143
+ author = model.get('author')
144
+
145
+ model.on('nested:change', function (model) {
146
+ changedModel = model
147
+ changedCount += 1
148
+ })
149
+
150
+ author.set({ name: 'Robb Stark' })
151
+ expect(changedModel).toBe(author)
152
+ expect(changedCount).toEqual(1)
153
+ })
154
+
155
+ it("triggers a change:<relationKey> event on the parent, with the changed model as an argument", function() {
156
+ model.on('change:author', function (model) {
157
+ changedModel = model
158
+ })
159
+
160
+ author.set({ name: 'Robb Stark' })
161
+ expect(changedModel).toBe(author)
162
+ })
163
+
164
+ it("triggers a change:<relationKey> only once, after setting nested attributes again", function() {
165
+ model.set({ title: 'Some Title', author: { body: 'Jon Snow' } })
166
+ author = model.get('author')
167
+
168
+ model.on('change:author', function (model) {
169
+ changedModel = model
170
+ changedCount += 1
171
+ })
172
+
173
+ author.set({ name: 'Robb Stark' })
174
+ expect(changedModel).toBe(author)
175
+ expect(changedCount).toEqual(1)
176
+ })
177
+
178
+ describe("and the nested model triggers a nested:change", function() {
179
+ it("triggers a nested:change on the parent", function() {
180
+ model.on('nested:change', function (model) {
181
+ changedModel = model
182
+ })
183
+
184
+ author.trigger('nested:change', author)
185
+ expect(changedModel).toBe(author)
186
+ })
187
+ })
188
+ })
189
+
190
+ describe("when clearing", function() {
191
+ it("stops listening to relation nested:change events", function() {
192
+ model.on('nested:change', function (model) {
193
+ changedModel = model
194
+ })
195
+
196
+ model.clear()
197
+ author.set({ name: 'Robb Stark' })
198
+ expect(changedModel).toBeUndefined()
199
+ })
200
+
201
+ it("stops listening to relation change:<relationKey> events", function() {
202
+ model.clear()
203
+
204
+ model.on('change:author', function (model) {
205
+ changedModel = model
206
+ })
207
+
208
+ author.set({ name: 'Robb Stark' })
209
+ expect(changedModel).toBeUndefined()
210
+ })
211
+ })
212
+ })
213
+
214
+ describe("toJSON", function() {
215
+ beforeEach(function() {
216
+ model = new Post({ title: 'Some Title', author: { name: 'Jon Snow' } })
217
+ })
218
+
219
+ it("serializes its own attributes, as well as the relation ones", function() {
220
+ expect(model.toJSON()).toEqual({ title: 'Some Title', author: { name: 'Jon Snow' } })
221
+ })
222
+
223
+ describe("with nested attributes support", function() {
224
+ it("serializes the relation attributes with a _attributes suffix", function() {
225
+ expect(model.toJSON({ nested: true })).toEqual({
226
+ title: 'Some Title',
227
+ author_attributes: { name: 'Jon Snow' }
228
+ })
229
+ })
230
+ })
231
+ })
232
+ })
233
+
234
+ describe("with a has many relationship", function() {
235
+ beforeEach(function() {
236
+ Post = Backbone.NestedAttributesModel.extend({
237
+ relations: [
238
+ {
239
+ key: 'comments',
240
+ relatedModel: function () { return Comment }
241
+ }
242
+ ]
243
+ })
244
+
245
+ Comment = Backbone.NestedAttributesModel.extend({})
246
+ })
247
+
248
+ describe("when creating", function() {
249
+ it("initializes the comments attribute with an empty collection", function() {
250
+ model = new Post
251
+ expect(model.get('comments')).toBeDefined()
252
+ expect(model.get('comments')).toBeAnInstanceOf(Backbone.Collection)
253
+ });
254
+
255
+ describe("while setting attributes", function() {
256
+ beforeEach(function() {
257
+ model = new Post({ title: 'Some Title', comments: [{ body: 'some comment'} ] })
258
+ })
259
+
260
+ it("sets the normal attributes", function() {
261
+ expect(model.get('title')).toEqual('Some Title')
262
+ })
263
+
264
+ it("creates the comment inside comments collection", function() {
265
+ comment = model.get('comments').at(0)
266
+
267
+ expect(comment).toBeAnInstanceOf(Comment)
268
+ expect(comment.get('body')).toEqual('some comment')
269
+ })
270
+
271
+ describe("passing a collection to a relation attribute", function() {
272
+ var comments, Comments
273
+
274
+ beforeEach(function() {
275
+ Comments = Backbone.Collection.extend({ model: Comment })
276
+ comments = new Comments({ body: 'some comment' }, { body: 'some other comment' })
277
+ model = new Post({ title: 'Some Title', comments: comments })
278
+ })
279
+
280
+ it("does not store the given collection, but instead creates a new one with models from the given collection", function() {
281
+ expect(model.get('comments')).not.toBe(comments)
282
+ expect(model.get('comments')).not.toBeAnInstanceOf(Comments)
283
+ expect(model.get('comments')).toBeAnInstanceOf(Backbone.Collection)
284
+ expect(model.get('comments').at(0)).toBe(comments.at(0))
285
+ expect(model.get('comments').at(1)).toBe(comments.at(1))
286
+ })
287
+ })
288
+ })
289
+ })
290
+
291
+ describe("when updating", function() {
292
+ beforeEach(function() {
293
+ model = new Post
294
+ })
295
+
296
+ it("creates the comment inside comments collection", function() {
297
+ model.set({ comments: [{ body: 'some comment' }] })
298
+
299
+ comment = model.get('comments').at(0)
300
+
301
+ expect(comment).toBeAnInstanceOf(Comment)
302
+ expect(comment.get('body')).toEqual('some comment')
303
+ })
304
+
305
+ it("does not creates a new collection, but updates it instead", function() {
306
+ var originalCollection = model.get('comments')
307
+
308
+ model.set({ comments: [{ body: 'some comment' }] })
309
+ expect(model.get('comments')).toBe(originalCollection)
310
+ })
311
+
312
+ it("updates existing models in the collection", function() {
313
+ var existingComment
314
+
315
+ model.set({ comments: [{ id: '123', body: 'some comment' }] })
316
+ existingComment = model.get('comments').at(0)
317
+
318
+ model.set({ comments: [{ id: existingComment.id, body: 'some other comment' }] })
319
+
320
+ expect(model.get('comments').at(0).get('body')).toEqual('some other comment')
321
+ })
322
+
323
+ it("allows passing key, value when setting a relation attribute", function() {
324
+ model.set('comments', [{ body: 'some comment' }])
325
+
326
+ comment = model.get('comments').at(0)
327
+
328
+ expect(comment).toBeAnInstanceOf(Comment)
329
+ expect(comment.get('body')).toEqual('some comment')
330
+ })
331
+
332
+ it("allows one to update non-nested attributes without modifying nested ones", function() {
333
+ model = new Post({ title: 'new title', comments: [{ body: 'some comment' }] })
334
+ model.set('title', 'other title')
335
+ expect(model.get('title')).toEqual('other title')
336
+ expect(model.get('comments').length).toEqual(1)
337
+ })
338
+
339
+ describe("passing a collection to a relation attribute", function() {
340
+ var comments, Comments
341
+
342
+ beforeEach(function() {
343
+ Comments = Backbone.Collection.extend({ model: Comment })
344
+ comments = new Comments({ body: 'some comment' }, { body: 'some other comment' })
345
+ model = new Post
346
+ })
347
+
348
+ it("does not store the given collection, but instead updates the existing one with models from the given collection", function() {
349
+ var originalCollection = model.get('comments')
350
+
351
+ model.set({ title: 'Some Title', comments: comments })
352
+
353
+ expect(model.get('comments')).toBe(originalCollection)
354
+ expect(model.get('comments').at(0)).toBe(comments.at(0))
355
+ expect(model.get('comments').at(1)).toBe(comments.at(1))
356
+ })
357
+ })
358
+ })
359
+
360
+ describe("cloning", function() {
361
+ beforeEach(function() {
362
+ post = new Post({ title: 'Some Title', comments: [{ body: 'some comment' }] })
363
+ })
364
+
365
+ it("creates a new post object, with the same attributes, including nested, but do not share the nested collections", function() {
366
+ var newPost = post.clone()
367
+
368
+ expect(newPost).not.toBe(post)
369
+ expect(newPost.get('comments')).not.toBe(post.get('comments'))
370
+ expect(newPost.get('comments').at(0)).not.toBe(post.get('comments').at(0))
371
+
372
+ expect(newPost.get('title')).toEqual(post.get('title'))
373
+ expect(newPost.get('comments').at(0).get('body')).toEqual(post.get('comments').at(0).get('body'))
374
+ })
375
+ })
376
+
377
+ describe("specifying the collection type", function() {
378
+ var Comments
379
+
380
+ beforeEach(function() {
381
+ Comments = Backbone.Collection.extend({ model: Comment })
382
+
383
+ Post = Backbone.NestedAttributesModel.extend({
384
+ relations: [
385
+ {
386
+ key: 'comments',
387
+ collectionType: function () { return Comments }
388
+ }
389
+ ]
390
+ })
391
+
392
+ model = new Post({ title: 'Some Title', comments: [{ body: 'some comment' }] })
393
+ })
394
+
395
+ it("uses the given collection as the relation attribute", function() {
396
+ expect(model.get('comments')).toBeAnInstanceOf(Comments)
397
+ expect(model.get('comments').at(0)).toBeAnInstanceOf(Comment)
398
+ })
399
+ })
400
+
401
+ describe("event bubbling", function() {
402
+ var changedModel, changedCount
403
+
404
+ beforeEach(function() {
405
+ changedCount = 0
406
+ changedModel = undefined
407
+
408
+ model = new Post({ title: 'Some Title', comments: [{ body: 'some comment' }] })
409
+ comment = model.get('comments').at(0)
410
+ })
411
+
412
+ describe("when a nested model is changed", function() {
413
+ it("triggers a nested:change event on the parent, with the changed model as an argument", function() {
414
+ model.on('nested:change', function (model) {
415
+ changedModel = model
416
+ })
417
+
418
+ comment.set({ body: 'some new body' })
419
+ expect(changedModel).toBe(comment)
420
+ })
421
+
422
+ it("triggers a nested:change only once, after setting nested attributes again", function() {
423
+ model.set({ title: 'Some Title', comments: [{ body: 'some other comment' }] })
424
+ comment = model.get('comments').at(0)
425
+
426
+ model.on('nested:change', function (model) {
427
+ changedModel = model
428
+ changedCount += 1
429
+ })
430
+
431
+ comment.set({ body: 'some new body' })
432
+ expect(changedModel).toBe(comment)
433
+ expect(changedCount).toEqual(1)
434
+ })
435
+
436
+ it("triggers a change:<relationKey> event on the parent, with the changed model as an argument", function() {
437
+ model.on('change:comments', function (model) {
438
+ changedModel = model
439
+ })
440
+
441
+ comment.set({ body: 'some new body' })
442
+ expect(changedModel).toBe(comment)
443
+ })
444
+
445
+ it("triggers a change:<relationKey> only once, after setting nested attributes again", function() {
446
+ model.set({ title: 'Some Title', comments: [{ body: 'some other comment' }] })
447
+ comment = model.get('comments').at(0)
448
+
449
+ model.on('change:comments', function (model) {
450
+ changedModel = model
451
+ changedCount += 1
452
+ })
453
+
454
+ comment.set({ body: 'some new body' })
455
+ expect(changedModel).toBe(comment)
456
+ expect(changedCount).toEqual(1)
457
+ })
458
+
459
+ describe("and the nested model triggers a nested:change", function() {
460
+ it("triggers a nested:change on the parent", function() {
461
+ model.on('nested:change', function (model) {
462
+ changedModel = model
463
+ })
464
+
465
+ comment.trigger('nested:change', comment)
466
+ expect(changedModel).toBe(comment)
467
+ })
468
+ })
469
+ })
470
+
471
+ describe("when adding a new model to a nested relation", function() {
472
+ it("triggers a nested:change event on the parent, with the added model as an argument", function() {
473
+ model.on('nested:change', function (model) {
474
+ changedModel = model
475
+ })
476
+
477
+ model.get('comments').add({ body: 'other comment' })
478
+ comment = model.get('comments').at(1)
479
+ expect(changedModel).toBe(comment)
480
+ })
481
+
482
+ it("triggers a change:<relationKey> event on the parent, with the added model as an argument", function() {
483
+ model.on('change:comments', function (model) {
484
+ changedModel = model
485
+ })
486
+
487
+ model.get('comments').add({ body: 'other comment' })
488
+ comment = model.get('comments').at(1)
489
+ expect(changedModel).toBe(comment)
490
+ })
491
+ })
492
+
493
+ describe("when removing a new model to a nested relation", function() {
494
+ it("triggers a nested:change event on the parent, with the removed model as an argument", function() {
495
+ model.on('nested:change', function (model) {
496
+ changedModel = model
497
+ })
498
+
499
+ comment = model.get('comments').at(0)
500
+ model.get('comments').remove(comment)
501
+ expect(changedModel).toBe(comment)
502
+ })
503
+
504
+ it("triggers a change:<relationKey> event on the parent, with the removed model as an argument", function() {
505
+ model.on('change:comments', function (model) {
506
+ changedModel = model
507
+ })
508
+
509
+ comment = model.get('comments').at(0)
510
+ model.get('comments').remove(comment)
511
+ expect(changedModel).toBe(comment)
512
+ })
513
+ })
514
+
515
+ describe("when clearing", function() {
516
+ it("stops listening to collection nested:change events", function() {
517
+ model.on('nested:change', function (model) {
518
+ changedModel = model
519
+ })
520
+
521
+ model.clear()
522
+ comment.set({ body: 'some new body' })
523
+ expect(changedModel).toBeUndefined()
524
+ })
525
+
526
+ it("stops listening to collection change:<relationKey> events", function() {
527
+ model.on('change:comments', function (model) {
528
+ changedModel = model
529
+ })
530
+
531
+ model.clear()
532
+ comment.set({ body: 'some new body' })
533
+ expect(changedModel).toBeUndefined()
534
+ })
535
+ })
536
+ })
537
+
538
+ describe("toJSON", function() {
539
+ beforeEach(function() {
540
+ model = new Post({ title: 'Some Title', comments: [{ body: 'some comment' }] })
541
+ })
542
+
543
+ it("serializes its own attributes, as well as the relation one", function() {
544
+ expect(model.toJSON()).toEqual({ title: 'Some Title', comments: [{ body: 'some comment' }] })
545
+ })
546
+
547
+ describe("with nested attributes support", function() {
548
+ it("serializes the relation attributes with a _attributes suffix", function() {
549
+ expect(model.toJSON({ nested: true })).toEqual({
550
+ title: 'Some Title',
551
+ comments_attributes: [{ body: 'some comment' }]
552
+ })
553
+ })
554
+
555
+ describe("when a nested model is removed", function() {
556
+ describe("and it is a new model", function() {
557
+ beforeEach(function() {
558
+ comment = model.get('comments').at(0)
559
+ })
560
+
561
+ it("is not serialized on the relation", function() {
562
+ model.get('comments').remove(comment)
563
+
564
+ expect(model.toJSON({ nested: true })).toEqual({
565
+ title: 'Some Title',
566
+ comments_attributes: []
567
+ })
568
+ })
569
+ })
570
+
571
+ describe("and it is not a new model", function() {
572
+ beforeEach(function() {
573
+ comment = model.get('comments').at(0)
574
+ comment.set({ id: '123' })
575
+ })
576
+
577
+ it("is serialized on the relation, with { _destroy: true } attribute, besides its own attributes", function() {
578
+ model.get('comments').remove(comment)
579
+
580
+ expect(model.toJSON({ nested: true })).toEqual({
581
+ title: 'Some Title',
582
+ comments_attributes: [{
583
+ id: comment.get('id'),
584
+ body: comment.get('body'),
585
+ _destroy: true
586
+ }]
587
+ })
588
+ })
589
+
590
+ describe("after synchronizing the parent model", function() {
591
+ beforeEach(function() {
592
+ jasmine.Ajax.useMock()
593
+ model.url = 'http://someapi.com'
594
+ })
595
+
596
+ it("is not serialized on the relation", function() {
597
+ model.get('comments').remove(comment)
598
+ model.save()
599
+
600
+ var request = mostRecentAjaxRequest();
601
+ request.response({status: 200, responseText: { title: 'Some Title', comments: [] }})
602
+
603
+ expect(model.toJSON({ nested: true })).toEqual({
604
+ title: 'Some Title',
605
+ comments_attributes: []
606
+ })
607
+ })
608
+ })
609
+
610
+ describe("after clearing the parent model", function() {
611
+ it("is not serialized on the relation", function() {
612
+ model.get('comments').remove(comment)
613
+ model.clear()
614
+
615
+ expect(model.toJSON({ nested: true })).toEqual({
616
+ title: undefined,
617
+ comments_attributes: []
618
+ })
619
+ })
620
+ })
621
+ })
622
+ })
623
+ })
624
+ })
625
+ })
626
+ })