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
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, _)