backbone-nested-attributes 0.2.2

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 (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
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/dummy/db/*.sqlite3
19
+ spec/dummy/log/*.log
20
+ spec/dummy/tmp/
21
+ spec/dummy/.sass-cache
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3-p0@backbone-nested-attributes --create
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in backbone-nested-attributes.gemspec
4
+ gemspec
5
+
6
+ gem 'sqlite3'
7
+
8
+ gem 'jquery-rails'
9
+
10
+ gem 'backbone-on-rails'
11
+
12
+ gem 'jasmine'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Vicente Mundim
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Backbone.NestedAttributesModel
2
+
3
+ Add Rails-like nested attributes support for Backbone.Model.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'backbone-nested-attributes'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install backbone-nested-attributes
18
+
19
+ Then, add this line in your `application.js`:
20
+
21
+ //= require backbone-nested-attributes/all
22
+
23
+ ## Usage
24
+
25
+ Make your model extend from `Backbone.NestedAttributesModel`, instead of `Backbone.Model` and declare your relationships:
26
+
27
+ ```javascript
28
+ var Post = Backbone.NestedAttributesModel.extend({
29
+ relations: [
30
+ {
31
+ type: 'one',
32
+ key: 'author',
33
+ relatedModel: function () { return Person }
34
+ },
35
+ {
36
+ key: 'comments',
37
+ relatedModel: function () { return Comment }
38
+ }
39
+ ]
40
+ })
41
+
42
+ var Comment = Backbone.NestedAttributesModel.extend({})
43
+ var Person = Backbone.NestedAttributesModel.extend({})
44
+ ```
45
+
46
+ Now you can create your posts like this:
47
+
48
+ ```javascript
49
+ var post = new Post({
50
+ id: 123,
51
+ title: 'My Title',
52
+ author: { id: 987, name: "Vicente Mundim" },
53
+ comments: [
54
+ {
55
+ id: 765,
56
+ body: "Nice writeup!"
57
+ },
58
+ {
59
+ id: 766,
60
+ body: "Keep it going!"
61
+ }
62
+ ]
63
+ })
64
+
65
+ post.get('author') // returns a Person model
66
+ post.get('comments') // returns a Backbone.Collection of Comment models
67
+ ```
68
+
69
+ When saving data, you can choose whether to send attributes as usual, or with nested attributes support by giving `{ nested: true }` to `save`:
70
+
71
+ ```javascript
72
+ post.save({}, { nested: true })
73
+ ```
74
+
75
+ This will send data to the server like this:
76
+
77
+ ```javascript
78
+ {
79
+ id: 123,
80
+ title: 'My Title',
81
+ author_attributes: { id: 987, name: "Vicente Mundim" },
82
+ comments_attributes: [
83
+ {
84
+ id: 765,
85
+ body: "Nice writeup!"
86
+ },
87
+ {
88
+ id: 766,
89
+ body: "Keep it going!"
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ It keeps track of deleted models in `1-N` relations:
96
+
97
+ ```javascript
98
+ var comment = post.get('comments').at(0)
99
+ post.get('comments').remove(comment)
100
+
101
+ post.save({}, { nested: true })
102
+ ```
103
+
104
+ Send this data to the server:
105
+
106
+ ```javascript
107
+ {
108
+ id: 123,
109
+ title: 'My Title',
110
+ author_attributes: { id: 987, name: "Vicente Mundim" },
111
+ comments_attributes: [
112
+ {
113
+ id: 765,
114
+ body: "Nice writeup!",
115
+ _destroy: true
116
+ },
117
+ {
118
+ id: 766,
119
+ body: "Keep it going!"
120
+ }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ## Backbone.UndoableModel
126
+
127
+ If you're using some [bind](https://github.com/NYTimes/backbone.stickit) plugin and you want to cancel changes that were made without reloading the page or hitting the backend you'll definitively want to take a look at Backbone.UndoableModel:
128
+
129
+ ```javascript
130
+ var Post = Backbone.UndoableModel.extend({
131
+ relations: [ // UndoableModel is a NestedAttributesModel, so it can have relations
132
+ {
133
+ type: 'one',
134
+ key: 'author',
135
+ relatedModel: function () { return Person }
136
+ },
137
+ {
138
+ key: 'comments',
139
+ relatedModel: function () { return Comment }
140
+ }
141
+ ]
142
+ })
143
+
144
+ var Comment = Backbone.UndoableModel.extend({})
145
+ var Person = Backbone.UndoableModel.extend({})
146
+
147
+ var post = new Post({
148
+ id: 123,
149
+ title: 'My Title',
150
+ author: { id: 987, name: "Vicente Mundim" },
151
+ comments: [
152
+ {
153
+ id: 765,
154
+ body: "Nice writeup!"
155
+ },
156
+ {
157
+ id: 766,
158
+ body: "Keep it going!"
159
+ }
160
+ ]
161
+ })
162
+
163
+ post.set({ title: 'My new title' })
164
+ post.get('author').set({ name: 'Jon Snow' })
165
+ post.get('comments').at(0).set({ body: 'Great post!' })
166
+
167
+ post.undo() // that's it, post is now reverted to its initial attributes, as well as its relations
168
+
169
+ post.get('title') // 'My Title'
170
+ post.get('author').get('name') // 'Vicente Mundim'
171
+ post.get('comments').at(0).get('body') // "Nice writeup!"
172
+ ```
173
+
174
+ ## More info
175
+
176
+ Check out the specs:
177
+
178
+ * [Backbone.NestedAttributesModel](https://github.com/dtmconsultoria/backbone-nested-attributes/blob/master/spec/javascripts/backbone-nested-attributes/ModelSpec.js)
179
+ * [Backbone.UndoableModel](https://github.com/dtmconsultoria/backbone-nested-attributes/blob/master/spec/javascripts/backbone-nested-attributes/UndoableSpec.js)
180
+
181
+ ## Contributing
182
+
183
+ 1. Fork it
184
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
185
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
186
+ 4. Push to the branch (`git push origin my-new-feature`)
187
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
9
+ load 'rails/tasks/engine.rake'
10
+
11
+ Bundler::GemHelper.install_tasks
12
+
13
+ task :jasmine => 'app:jasmine'
14
+
15
+ task :default => 'app:jasmine:ci'
@@ -0,0 +1,2 @@
1
+ //= require backbone-nested-attributes/model
2
+ //= require backbone-nested-attributes/undoable
@@ -0,0 +1,175 @@
1
+ (function(Backbone, _) {
2
+ var BackboneModelPrototype = Backbone.Model.prototype
3
+
4
+ function setNestedAttributes(model, key, value, options) {
5
+ var attributes = attributesFor(key, value, options)
6
+
7
+ if (attributes) {
8
+ _(model.relations).each(function (relation) {
9
+ if (relation.type == 'one') {
10
+ setHasOneNestedAttributeFor(model, relation, attributes)
11
+ } else {
12
+ setNestedAttributeFor(model, relation, attributes)
13
+ }
14
+ })
15
+ }
16
+
17
+ configureNestedAttributesEvents(model)
18
+
19
+ return attributes
20
+ }
21
+
22
+ function setHasOneNestedAttributeFor(model, relation, attributes) {
23
+ var key = relation.key,
24
+ value = attributes[key],
25
+ ModelClass = _(relation).result('relatedModel')
26
+
27
+ if (value) {
28
+ value = value instanceof Backbone.Model ? value : new ModelClass(value)
29
+
30
+ configureEventBubbling(model, value, relation)
31
+ attributes[key] = value
32
+ }
33
+ }
34
+
35
+ function setNestedAttributeFor(model, relation, attributes) {
36
+ var key = relation.key,
37
+ value = attributes[key],
38
+ currentValue = model.get(key),
39
+ nested = currentValue || createNestedAttributeCollection(relation)
40
+
41
+ value = value instanceof Backbone.Collection ? value.slice() : value
42
+
43
+ configureEventBubbling(model, nested, relation)
44
+
45
+ if (value) {
46
+ nested.set(value)
47
+ }
48
+
49
+ attributes[key] = nested
50
+
51
+ return attributes
52
+ }
53
+
54
+ function configureNestedAttributesEvents(model) {
55
+ if (!model._hasNestedAttributesEventsConfigured) {
56
+ model.on('sync', function () {
57
+ _(model.relations).each(function (relation) {
58
+ var collection = model.get(relation.key)
59
+
60
+ collection.deletedModels.reset()
61
+ })
62
+ })
63
+
64
+ model._hasNestedAttributesEventsConfigured = true
65
+ }
66
+ }
67
+
68
+ function configureEventBubbling(model, nested, relation) {
69
+ if (!nested._hasEventBubblingConfigured) {
70
+ model.listenTo(nested, 'add change nested:change remove', function (nestedModel, options) {
71
+ model.trigger('nested:change change:' + relation.key, nestedModel, options)
72
+ })
73
+
74
+ nested._hasEventBubblingConfigured = true
75
+ }
76
+ }
77
+
78
+ function clearNestedEvents(model) {
79
+ _(model.relations).each(function (relation) {
80
+ var nested = model.get(relation.key)
81
+
82
+ model.stopListening(nested)
83
+ nested.off('remove', nestedModelRemoved, nested)
84
+
85
+ if (nested.deletedModels) {
86
+ nested.deletedModels.reset()
87
+ }
88
+ }, model)
89
+ }
90
+
91
+ function nestedToJson(json, relations, options) {
92
+ _(relations).each(function (relation) {
93
+ var key = relation.key,
94
+ value = json[key],
95
+ deleted = [],
96
+ jsonValue
97
+
98
+ if (value) {
99
+ if (options && options.nested) {
100
+ delete json[key]
101
+ key = key + '_attributes'
102
+
103
+ if (value.deletedModels) {
104
+ deleted = value.deletedModels.toJSON(options)
105
+ }
106
+ }
107
+
108
+ jsonValue = value.toJSON(options)
109
+
110
+ if (_(jsonValue).isArray()) {
111
+ json[key] = jsonValue.concat(deleted)
112
+ } else {
113
+ json[key] = jsonValue
114
+ }
115
+ }
116
+ })
117
+
118
+ return json
119
+ }
120
+
121
+ function createNestedAttributeCollection(relation) {
122
+ var CollectionType = _(relation).result('collectionType') || Backbone.Collection,
123
+ collection = new CollectionType
124
+
125
+ collection.model = _(relation).result('relatedModel') || collection.model
126
+
127
+ collection.deletedModels = new Backbone.Collection
128
+ collection.on('remove', nestedModelRemoved, collection)
129
+
130
+ return collection
131
+ }
132
+
133
+ function nestedModelRemoved(model) {
134
+ if (!model.isNew()) {
135
+ model.set({ _destroy: true })
136
+ this.deletedModels.add(model) // this refers to the collection
137
+ }
138
+ }
139
+
140
+ function attributesFor(key, value, options) {
141
+ var attributes
142
+
143
+ // Duplicate backbone's behavior to allow separate key/value parameters,
144
+ // instead of a single 'attributes' object.
145
+ if (_.isObject(key) || key == null) {
146
+ attributes = key
147
+ options = value
148
+ } else {
149
+ attributes = {}
150
+ attributes[key] = value
151
+ }
152
+
153
+ return attributes
154
+ }
155
+
156
+ Backbone.NestedAttributesModel = Backbone.Model.extend({
157
+ set: function (key, value, options) {
158
+ var attributes = setNestedAttributes(this, key, value, options)
159
+ return BackboneModelPrototype.set.call(this, attributes, options)
160
+ },
161
+
162
+ toJSON: function (options) {
163
+ return nestedToJson(BackboneModelPrototype.toJSON.apply(this, arguments), this.relations, options)
164
+ },
165
+
166
+ clone: function() {
167
+ return new this.constructor(this.toJSON());
168
+ },
169
+
170
+ clear: function (options) {
171
+ clearNestedEvents(this)
172
+ return BackboneModelPrototype.clear.apply(this, arguments)
173
+ }
174
+ })
175
+ })(Backbone, _)