backbone-nested-attributes 0.2.3 → 0.3.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.
- data/app/assets/javascripts/backbone-nested-attributes/model.js +30 -9
- data/app/assets/javascripts/backbone-nested-attributes/undoable.js +20 -3
- data/lib/backbone-nested-attributes/version.rb +1 -1
- data/spec/javascripts/backbone-nested-attributes/ModelSpec.js +74 -0
- data/spec/javascripts/backbone-nested-attributes/UndoableSpec.js +75 -0
- metadata +4 -4
@@ -35,10 +35,11 @@
|
|
35
35
|
function setNestedAttributeFor(model, relation, attributes) {
|
36
36
|
var key = relation.key,
|
37
37
|
value = attributes[key],
|
38
|
+
deletedValue = attributes['deleted_' + key],
|
38
39
|
currentValue = model.get(key),
|
39
40
|
nested = currentValue || createNestedAttributeCollection(relation)
|
40
41
|
|
41
|
-
value = value
|
42
|
+
value = valueOrSliceCollection(value)
|
42
43
|
|
43
44
|
configureEventBubbling(model, nested, relation)
|
44
45
|
|
@@ -46,11 +47,22 @@
|
|
46
47
|
nested.set(value)
|
47
48
|
}
|
48
49
|
|
50
|
+
if (deletedValue) {
|
51
|
+
delete attributes['deleted_' + key]
|
52
|
+
|
53
|
+
deletedValue = valueOrSliceCollection(deletedValue)
|
54
|
+
nested.deletedModels.set(deletedValue)
|
55
|
+
}
|
56
|
+
|
49
57
|
attributes[key] = nested
|
50
58
|
|
51
59
|
return attributes
|
52
60
|
}
|
53
61
|
|
62
|
+
function valueOrSliceCollection(value) {
|
63
|
+
return value instanceof Backbone.Collection ? value.slice() : value
|
64
|
+
}
|
65
|
+
|
54
66
|
function clearDeletedModelsFor(model) {
|
55
67
|
_(model.relations).each(function (relation) {
|
56
68
|
var collection = model.get(relation.key)
|
@@ -101,12 +113,20 @@
|
|
101
113
|
jsonValue
|
102
114
|
|
103
115
|
if (value) {
|
104
|
-
if (options
|
105
|
-
|
106
|
-
|
116
|
+
if (options) {
|
117
|
+
if (options.withDeleted) {
|
118
|
+
if (value.deletedModels) {
|
119
|
+
json['deleted_' + key] = value.deletedModels.toJSON(options)
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
if (options.nested) {
|
124
|
+
if (value.deletedModels) {
|
125
|
+
deleted = value.deletedModels.toJSON(options)
|
126
|
+
}
|
107
127
|
|
108
|
-
|
109
|
-
|
128
|
+
delete json[key]
|
129
|
+
key = key + '_attributes'
|
110
130
|
}
|
111
131
|
}
|
112
132
|
|
@@ -130,15 +150,16 @@
|
|
130
150
|
collection.model = _(relation).result('relatedModel') || collection.model
|
131
151
|
|
132
152
|
collection.deletedModels = new Backbone.Collection
|
133
|
-
collection.
|
153
|
+
collection.deletedModels.model = collection.model
|
154
|
+
collection.on('remove', nestedModelRemoved)
|
134
155
|
|
135
156
|
return collection
|
136
157
|
}
|
137
158
|
|
138
|
-
function nestedModelRemoved(model) {
|
159
|
+
function nestedModelRemoved(model, collection) {
|
139
160
|
if (!model.isNew()) {
|
140
161
|
model.set({ _destroy: true })
|
141
|
-
|
162
|
+
collection.deletedModels.add(model)
|
142
163
|
}
|
143
164
|
}
|
144
165
|
|
@@ -16,17 +16,34 @@
|
|
16
16
|
},
|
17
17
|
|
18
18
|
save: function () {
|
19
|
-
this.
|
20
|
-
this.changed = false
|
19
|
+
this.updateAttributes()
|
21
20
|
|
22
21
|
this.model.trigger('state:store')
|
23
22
|
},
|
24
23
|
|
25
24
|
undo: function () {
|
25
|
+
this.attributesToUnset().each(function (attribute) {
|
26
|
+
this.model.unset(attribute)
|
27
|
+
}, this)
|
28
|
+
|
26
29
|
this.model.set(this.attributes)
|
27
|
-
|
30
|
+
|
31
|
+
this.updateAttributes()
|
28
32
|
|
29
33
|
this.model.trigger('state:restore')
|
34
|
+
},
|
35
|
+
|
36
|
+
attributesToUnset: function () {
|
37
|
+
var previousAttributes = _(this.attributes || {}).keys()
|
38
|
+
|
39
|
+
return _(this.model.attributes).chain().keys().select(function (attribute) {
|
40
|
+
return !_(previousAttributes).include(attribute)
|
41
|
+
})
|
42
|
+
},
|
43
|
+
|
44
|
+
updateAttributes: function () {
|
45
|
+
this.attributes = this.model.toJSON({ withDeleted: true })
|
46
|
+
this.changed = false
|
30
47
|
}
|
31
48
|
})
|
32
49
|
|
@@ -285,6 +285,26 @@ describe("Backbone.NestedAttributesModel", function() {
|
|
285
285
|
expect(model.get('comments').at(1)).toBe(comments.at(1))
|
286
286
|
})
|
287
287
|
})
|
288
|
+
|
289
|
+
describe("passing a deleted_<collection> attribute", function() {
|
290
|
+
var comments
|
291
|
+
|
292
|
+
beforeEach(function() {
|
293
|
+
model = new Post({ title: 'Some Title', deleted_comments: [{ id: 123, body: "some deleted comment", _destroy: true }] })
|
294
|
+
})
|
295
|
+
|
296
|
+
it("does not save this key as an attribute", function() {
|
297
|
+
expect(model.get('deleted_comments')).toBeUndefined()
|
298
|
+
})
|
299
|
+
|
300
|
+
it("adds the models in the deleted_comments attribute to the deletedModels collection inside the relation collection", function() {
|
301
|
+
comments = model.get('comments')
|
302
|
+
expect(comments.deletedModels.at(0)).toBeAnInstanceOf(Comment)
|
303
|
+
expect(comments.deletedModels.at(0).get('id')).toEqual(123)
|
304
|
+
expect(comments.deletedModels.at(0).get('body')).toEqual('some deleted comment')
|
305
|
+
expect(comments.deletedModels.at(0).get('_destroy')).toBeTruthy()
|
306
|
+
})
|
307
|
+
})
|
288
308
|
})
|
289
309
|
})
|
290
310
|
|
@@ -355,6 +375,29 @@ describe("Backbone.NestedAttributesModel", function() {
|
|
355
375
|
expect(model.get('comments').at(1)).toBe(comments.at(1))
|
356
376
|
})
|
357
377
|
})
|
378
|
+
|
379
|
+
describe("passing a deleted_<collection> attribute", function() {
|
380
|
+
var comments
|
381
|
+
|
382
|
+
beforeEach(function() {
|
383
|
+
model = new Post({ title: 'Some Title' })
|
384
|
+
})
|
385
|
+
|
386
|
+
it("does not save this key as an attribute", function() {
|
387
|
+
model.set({ deleted_comments: [{ id: 123, body: "some deleted comment", _destroy: true }] })
|
388
|
+
expect(model.get('deleted_comments')).toBeUndefined()
|
389
|
+
})
|
390
|
+
|
391
|
+
it("adds the models in the deleted_comments attribute to the deletedModels collection inside the relation collection", function() {
|
392
|
+
model.set({ deleted_comments: [{ id: 123, body: "some deleted comment", _destroy: true }] })
|
393
|
+
comments = model.get('comments')
|
394
|
+
|
395
|
+
expect(comments.deletedModels.at(0)).toBeAnInstanceOf(Comment)
|
396
|
+
expect(comments.deletedModels.at(0).get('id')).toEqual(123)
|
397
|
+
expect(comments.deletedModels.at(0).get('body')).toEqual('some deleted comment')
|
398
|
+
expect(comments.deletedModels.at(0).get('_destroy')).toBeTruthy()
|
399
|
+
})
|
400
|
+
})
|
358
401
|
})
|
359
402
|
|
360
403
|
describe("cloning", function() {
|
@@ -621,6 +664,37 @@ describe("Backbone.NestedAttributesModel", function() {
|
|
621
664
|
})
|
622
665
|
})
|
623
666
|
})
|
667
|
+
|
668
|
+
describe("with deleted models", function() {
|
669
|
+
beforeEach(function() {
|
670
|
+
model = new Post({ id: 321, title: 'Some Title', comments: [{ id: 123, body: 'some comment' }] })
|
671
|
+
comment = model.get('comments').at(0)
|
672
|
+
})
|
673
|
+
|
674
|
+
it("serializes the deleted models in a relation in a delete_<relation> key", function() {
|
675
|
+
model.get('comments').remove(comment)
|
676
|
+
|
677
|
+
expect(model.toJSON({ withDeleted: true })).toEqual({
|
678
|
+
id: 321,
|
679
|
+
title: 'Some Title',
|
680
|
+
comments: [],
|
681
|
+
deleted_comments: [ {id: 123, body: 'some comment', _destroy: true} ]
|
682
|
+
})
|
683
|
+
})
|
684
|
+
|
685
|
+
describe("and nested attributes support", function() {
|
686
|
+
it("serializes the deleted models in a relation in a delete_<relation> key", function() {
|
687
|
+
model.get('comments').remove(comment)
|
688
|
+
|
689
|
+
expect(model.toJSON({ withDeleted: true, nested: true })).toEqual({
|
690
|
+
id: 321,
|
691
|
+
title: 'Some Title',
|
692
|
+
comments_attributes: [{ id: 123, body: 'some comment', _destroy: true }],
|
693
|
+
deleted_comments: [ { id: 123, body: 'some comment', _destroy: true } ]
|
694
|
+
})
|
695
|
+
})
|
696
|
+
})
|
697
|
+
})
|
624
698
|
})
|
625
699
|
})
|
626
700
|
})
|
@@ -293,6 +293,14 @@ describe("Backbone.UndoableModel", function() {
|
|
293
293
|
})
|
294
294
|
})
|
295
295
|
|
296
|
+
describe("when an attribute is added", function() {
|
297
|
+
it("unsets this attribute on undo", function() {
|
298
|
+
model.set({ newAttr: 'foo' })
|
299
|
+
model.undo()
|
300
|
+
expect(model.has('newAttr')).toBeFalsy()
|
301
|
+
})
|
302
|
+
})
|
303
|
+
|
296
304
|
describe("when it has a has one relationship", function() {
|
297
305
|
beforeEach(function() {
|
298
306
|
Post = Backbone.UndoableModel.extend({
|
@@ -485,5 +493,72 @@ describe("Backbone.UndoableModel", function() {
|
|
485
493
|
expect(called).toBeTruthy()
|
486
494
|
})
|
487
495
|
})
|
496
|
+
|
497
|
+
|
498
|
+
describe("when undoing changes after a nested model had been removed", function() {
|
499
|
+
beforeEach(function() {
|
500
|
+
originalAttributes = { id: 321, title: 'some title', comments: [ { id: 123, body: 'some body' } ] }
|
501
|
+
post = new Post(_(originalAttributes).clone())
|
502
|
+
comments = post.get('comments')
|
503
|
+
comment = comments.at(0)
|
504
|
+
|
505
|
+
comments.remove(comment)
|
506
|
+
})
|
507
|
+
|
508
|
+
it("undoes the deletedModels in the relation collection as well", function() {
|
509
|
+
post.undo()
|
510
|
+
expect(comments.deletedModels.length).toEqual(0)
|
511
|
+
})
|
512
|
+
|
513
|
+
describe("and the state saved", function() {
|
514
|
+
beforeEach(function() {
|
515
|
+
post.saveState()
|
516
|
+
})
|
517
|
+
|
518
|
+
it("keeps the deletedModels in the relation collection", function() {
|
519
|
+
post.undo()
|
520
|
+
expect(comments.deletedModels.at(0)).toBe(comment)
|
521
|
+
})
|
522
|
+
})
|
523
|
+
})
|
524
|
+
|
525
|
+
describe("when undoing changes after a undo", function() {
|
526
|
+
it("reverts the changes properly", function() {
|
527
|
+
post.set({ comments: [{ id: 123, body: 'first body' }] })
|
528
|
+
post.undo()
|
529
|
+
post.set({ comments: [{ id: 123, body: 'last body' }] })
|
530
|
+
post.undo()
|
531
|
+
expect(post.get('comments').at(0).get('body')).toEqual('some body')
|
532
|
+
})
|
533
|
+
|
534
|
+
describe("on a deep nested model", function() {
|
535
|
+
var Tag, tag
|
536
|
+
|
537
|
+
beforeEach(function() {
|
538
|
+
Comment = Backbone.UndoableModel.extend({
|
539
|
+
relations: [
|
540
|
+
{
|
541
|
+
key: 'tags',
|
542
|
+
relatedModel: function () { return Tag }
|
543
|
+
}
|
544
|
+
]
|
545
|
+
})
|
546
|
+
|
547
|
+
Tag = Backbone.UndoableModel.extend({})
|
548
|
+
|
549
|
+
originalAttributes = { id: 321, title: 'some title', comments: [ { id: 123, body: 'some body', tags: [ { id: 456, name: 'javascript' } ] } ] }
|
550
|
+
post = new Post(originalAttributes)
|
551
|
+
})
|
552
|
+
|
553
|
+
it("reverts the changes properly", function() {
|
554
|
+
post.set({ comments: [ { id: 123, body: 'some body', tags: [ { id: 456, name: 'ruby' } ] } ] })
|
555
|
+
post.undo()
|
556
|
+
post.set({ comments: [ { id: 123, body: 'some body', tags: [ { id: 456, name: 'python' } ] } ] })
|
557
|
+
post.undo()
|
558
|
+
|
559
|
+
expect(post.get('comments').at(0).get('tags').at(0).get('name')).toEqual('javascript')
|
560
|
+
})
|
561
|
+
})
|
562
|
+
})
|
488
563
|
})
|
489
564
|
})
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backbone-nested-attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -99,7 +99,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
99
|
version: '0'
|
100
100
|
segments:
|
101
101
|
- 0
|
102
|
-
hash: -
|
102
|
+
hash: -1599110684641567877
|
103
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
104
|
none: false
|
105
105
|
requirements:
|
@@ -108,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
108
|
version: '0'
|
109
109
|
segments:
|
110
110
|
- 0
|
111
|
-
hash: -
|
111
|
+
hash: -1599110684641567877
|
112
112
|
requirements: []
|
113
113
|
rubyforge_project:
|
114
114
|
rubygems_version: 1.8.24
|