backbone-associations-rails 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module Backbone
2
2
  module Associations
3
3
  module Rails
4
- VERSION = '0.3.0'
4
+ VERSION = '0.3.1'
5
5
  end
6
6
  end
7
7
  end
@@ -1,12 +1,11 @@
1
1
  //
2
- // Backbone-associations.js 0.3.0
2
+ // Backbone-associations.js 0.3.1
3
3
  //
4
- // (c) 2012 Dhruva Ray, Jaynti Kanani
5
- // Backbone-associations may be freely distributed under the MIT license;
6
- // see the accompanying LICENSE.txt.
7
- // Depends on [Backbone](https://github.com/documentcloud/backbone)
8
- // and [Underscore](https://github.com/documentcloud/underscore/) as well.
9
- // A complete [Test & Benchmark Suite](../test/test-suite.html) is included for your perusal.
4
+ // (c) 2013 Dhruva Ray, Jaynti Kanani
5
+ // Backbone-associations may be freely distributed under the MIT license;
6
+ // see the accompanying LICENSE.txt.
7
+ // Depends on [Backbone](https://github.com/documentcloud/backbone) and [Underscore](https://github.com/documentcloud/underscore/) as well.
8
+ // A complete [Test & Benchmark Suite](../test/test-suite.html) is included for your perusal.
10
9
 
11
10
  // Initial Setup
12
11
  // --------------
@@ -15,7 +14,8 @@
15
14
 
16
15
  // The top-level namespace. All public Backbone classes and modules will be attached to this.
17
16
  // Exported for the browser and CommonJS.
18
- var _, Backbone, BackboneModel, defaultEvents, AssociatedModel;
17
+ var _, Backbone, BackboneModel, BackboneCollection, ModelProto,
18
+ defaultEvents, AssociatedModel;
19
19
  if (typeof require !== 'undefined') {
20
20
  _ = require('underscore');
21
21
  Backbone = require('backbone');
@@ -25,9 +25,13 @@
25
25
  Backbone = window.Backbone;
26
26
  }
27
27
  // Create local reference `Model` prototype.
28
- BackboneModel = Backbone.Model.prototype;
28
+ BackboneModel = Backbone.Model;
29
+ BackboneCollection = Backbone.Collection;
30
+ ModelProto = BackboneModel.prototype;
31
+
29
32
  // Built-in Backbone `events`.
30
- defaultEvents = ["change", "add", "remove", "reset", "destroy", "sync", "error", "sort", "request"];
33
+ defaultEvents = ["change", "add", "remove", "reset", "destroy",
34
+ "sync", "error", "sort", "request"];
31
35
 
32
36
  // Backbone.AssociatedModel
33
37
  // --------------
@@ -36,17 +40,23 @@
36
40
  Backbone.Many = "Many";
37
41
  Backbone.One = "One";
38
42
  // Define `AssociatedModel` (Extends Backbone.Model).
39
- AssociatedModel = Backbone.AssociatedModel = Backbone.Model.extend({
43
+ AssociatedModel = Backbone.AssociatedModel = BackboneModel.extend({
40
44
  // Define relations with Associated Model.
41
- relations:undefined,
45
+ relations: undefined,
42
46
  // Define `Model` property which can keep track of already fired `events`,
43
47
  // and prevent redundant event to be triggered in case of circular model graph.
44
- _proxyCalls:undefined,
48
+ _proxyCalls: undefined,
49
+
50
+ // Get the value of an attribute.
51
+ get: function(attr){
52
+ return this.getAttr.apply(this, arguments);
53
+ },
54
+
45
55
  // Set a hash of model attributes on the object,
46
56
  // fire Backbone `event` with options.
47
57
  // It maintains relations between models during the set operation.
48
58
  // It also bubbles up child events to the parent.
49
- set:function (key, value, options) {
59
+ set: function (key, value, options) {
50
60
  var attributes, processedRelations, tbp, attr;
51
61
  // Duplicate backbone's behavior to allow separate key/value parameters,
52
62
  // instead of a single 'attributes' object.
@@ -68,7 +78,7 @@
68
78
  _.each(this.relations, function (relation) {
69
79
  var relationKey = relation.key, relatedModel = relation.relatedModel,
70
80
  collectionType = relation.collectionType,
71
- val, relationOptions, data;
81
+ val, relationOptions, data, relationValue;
72
82
  if (attributes[relationKey]) {
73
83
  //Get value of attribute with relation key in `val`.
74
84
  val = _.result(attributes, relationKey);
@@ -81,91 +91,42 @@
81
91
  // create `Backbone.Collection` with passed data and perform Backbone `set`.
82
92
  if (relation.type === Backbone.Many) {
83
93
  // `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
84
- if (collectionType && !collectionType.prototype instanceof Backbone.Collection) {
94
+ if (collectionType && !collectionType.prototype instanceof BackboneCollection) {
85
95
  throw new Error('collectionType must inherit from Backbone.Collection');
86
96
  }
87
97
 
88
- if (val instanceof Backbone.Collection) {
89
- BackboneModel.set.call(this, relationKey, val, relationOptions);
98
+ // If `attributes` has no property present,
99
+ // create `Collection` having `relation.collectionType` as type and
100
+ // `relation.Model` as model reference and perform Backbone `set`.
101
+ if (val instanceof BackboneCollection) {
102
+ ModelProto.set.call(this, relationKey, val, relationOptions);
90
103
  } else if (!this.attributes[relationKey]) {
91
- // If `attributes` has no property present,
92
- // create `Collection` having `relation.collectionType` as type and
93
- // `relation.Model` as model reference, and perform Backbone `set`.
94
104
  data = collectionType ? new collectionType() : this._createCollection(relatedModel);
95
105
  data.add(val, relationOptions);
96
- BackboneModel.set.call(this, relationKey, data, relationOptions);
106
+ ModelProto.set.call(this, relationKey, data, relationOptions);
97
107
  } else {
98
108
  this.attributes[relationKey].reset(val, relationOptions);
99
109
  }
100
-
101
110
  } else if (relation.type === Backbone.One && relatedModel) {
102
111
  // If passed data is not instance of `Backbone.AssociatedModel`,
103
112
  // create `AssociatedModel` and perform backbone `set`.
104
113
  data = val instanceof AssociatedModel ? val : new relatedModel(val);
105
- BackboneModel.set.call(this, relationKey, data, relationOptions)
114
+ ModelProto.set.call(this, relationKey, data, relationOptions)
106
115
  }
107
116
 
117
+ relationValue = this.attributes[relationKey];
118
+
108
119
  // Add proxy events to respective parents.
109
120
  // Only add callback if not defined.
110
- if (!this.attributes[relationKey]._proxyCallback) {
111
- this.attributes[relationKey]._proxyCallback = function () {
112
- var args = arguments,
113
- opt = args[0].split(":"),
114
- eventType = opt[0],
115
- eventObject = args[1],
116
- indexEventObject = -1,
117
- _proxyCalls = this.attributes[relationKey]._proxyCalls,
118
- eventPath,
119
- eventAvailable;
120
-
121
- // Change the event name to a fully qualified path.
122
- if (_.contains(defaultEvents, eventType)) {
123
- if (opt && _.size(opt) > 1) {
124
- eventPath = opt[1];
125
- }
126
- // Find the specific object in the collection which has changed.
127
- if (this.attributes[relationKey] instanceof Backbone.Collection && "change" === eventType) {
128
- indexEventObject = _.indexOf(this.attributes[relationKey].models, eventObject);
129
- }
130
- // Manipulate `eventPath`.
131
- eventPath = relationKey + (indexEventObject !== -1 ?
132
- "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
133
- args[0] = eventType + ":" + eventPath;
134
-
135
- if (_proxyCalls) {
136
- // If event has been already triggered as result of same source `eventPath`,
137
- // no need to re-trigger event to prevent cycle.
138
- eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
139
- // `event` ends with eventKey.
140
- var d = eventPath.length - eventKey.length;
141
- return eventPath.indexOf(eventKey, d) !== -1;
142
- });
143
- if (eventAvailable) {
144
- return this;
145
- }
146
- } else {
147
- _proxyCalls = this.attributes[relationKey]._proxyCalls = {};
148
- }
149
- // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
150
- _proxyCalls[eventPath] = true;
151
- }
152
-
153
- // Bubble up event to parent `model` with new changed arguments.
154
- this.trigger.apply(this, args);
155
-
156
- // Remove `eventPath` from `_proxyCalls`,
157
- // if `eventPath` and `_proxCalls` are available,
158
- // which allow event to be triggered on for next operation of `set`.
159
- if (eventPath && _proxyCalls) {
160
- delete _proxyCalls[eventPath];
161
- }
162
- return this;
121
+ if (relationValue && !relationValue._proxyCallback) {
122
+ relationValue._proxyCallback = function () {
123
+ return this._bubbleEvent.call(this, relationKey, arguments);
163
124
  };
164
- this.attributes[relationKey].on("all", this.attributes[relationKey]._proxyCallback, this);
125
+ relationValue.on("all", relationValue._proxyCallback, this);
165
126
  }
166
127
 
167
128
  // Create a local `processedRelations` array to store the relation key which has been processed.
168
- // We cannot use `this.relations` because if there is no value defined for `relationKey`,
129
+ // We cannot use `this.relations` because if there is no value defined for `relationKey`,
169
130
  // it will not get processed by either `BackboneModel` `set` or the `AssociatedModel` `set`.
170
131
  !processedRelations && (processedRelations = []);
171
132
  if (_.indexOf(processedRelations, relationKey) === -1) {
@@ -187,15 +148,73 @@
187
148
  tbp = attributes;
188
149
  }
189
150
  // Return results for `BackboneModel.set`.
190
- return BackboneModel.set.call(this, tbp, options);
151
+ return ModelProto.set.call(this, tbp, options);
152
+ },
153
+ // Bubble-up event to `parent` Model
154
+ _bubbleEvent: function (relationKey, eventArguments) {
155
+ var args = eventArguments,
156
+ opt = args[0].split(":"),
157
+ eventType = opt[0],
158
+ eventObject = args[1],
159
+ indexEventObject = -1,
160
+ relationValue = this.attributes[relationKey],
161
+ _proxyCalls = relationValue._proxyCalls,
162
+ eventPath,
163
+ eventAvailable;
164
+ // Change the event name to a fully qualified path.
165
+ if (_.contains(defaultEvents, eventType)) {
166
+ _.size(opt) > 1 && (eventPath = opt[1]);
167
+ // Find the specific object in the collection which has changed.
168
+ if (relationValue instanceof BackboneCollection && "change" === eventType && eventObject) {
169
+ //indexEventObject = _.indexOf(relationValue.models, eventObject);
170
+ var pathTokens = getPathArray(eventPath),
171
+ initialTokens = _.initial(pathTokens),
172
+ colModel;
173
+ colModel = relationValue.find(function (model) {
174
+ var changedModel = model.get(pathTokens);
175
+ return eventObject === !(changedModel instanceof AssociatedModel
176
+ || changedModel instanceof BackboneCollection)
177
+ ? model.get(initialTokens) : changedModel;
178
+ });
179
+ colModel && (indexEventObject = relationValue.indexOf(colModel));
180
+ }
181
+ // Manipulate `eventPath`.
182
+ eventPath = relationKey + (indexEventObject !== -1 ?
183
+ "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
184
+ args[0] = eventType + ":" + eventPath;
185
+
186
+ // If event has been already triggered as result of same source `eventPath`,
187
+ // no need to re-trigger event to prevent cycle.
188
+ if (_proxyCalls) {
189
+ eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
190
+ return eventPath.indexOf(eventKey, eventPath.length - eventKey.length) !== -1;
191
+ });
192
+ if (eventAvailable) return this;
193
+ } else {
194
+ _proxyCalls = relationValue._proxyCalls = {};
195
+ }
196
+ // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
197
+ _proxyCalls[eventPath] = true;
198
+ }
199
+
200
+ // Bubble up event to parent `model` with new changed arguments.
201
+ this.trigger.apply(this, args);
202
+
203
+ // Remove `eventPath` from `_proxyCalls`,
204
+ // if `eventPath` and `_proxCalls` are available,
205
+ // which allow event to be triggered on for next operation of `set`.
206
+ if (eventPath && _proxyCalls) {
207
+ delete _proxyCalls[eventPath];
208
+ }
209
+ return this;
191
210
  },
192
211
  // Returns New `collection` of type `relation.relatedModel`.
193
- _createCollection:function (type) {
212
+ _createCollection: function (type) {
194
213
  var collection, relatedModel = type;
195
214
  _.isString(relatedModel) && (relatedModel = eval(relatedModel));
196
215
  // Creates new `Backbone.Collection` and defines model class.
197
216
  if (relatedModel && relatedModel.prototype instanceof AssociatedModel) {
198
- collection = new Backbone.Collection();
217
+ collection = new BackboneCollection();
199
218
  collection.model = relatedModel;
200
219
  } else {
201
220
  throw new Error('type must inherit from Backbone.AssociatedModel');
@@ -203,19 +222,19 @@
203
222
  return collection;
204
223
  },
205
224
  // Has the model changed. Traverse the object hierarchy to compute dirtyness.
206
- hasChanged:function (attr) {
225
+ hasChanged: function (attr) {
207
226
  var isDirty, relation, attrValue, i, dirtyObjects;
208
227
  // To prevent cycles, check if this node is visited.
209
228
  if (!this.visitedHC) {
210
229
  this.visitedHC = true;
211
- isDirty = BackboneModel.hasChanged.apply(this, arguments);
230
+ isDirty = ModelProto.hasChanged.apply(this, arguments);
212
231
  if (!isDirty && this.relations) {
213
232
  // Go down the hierarchy to see if anything has `changed`.
214
233
  for (i = 0; i < this.relations.length; ++i) {
215
234
  relation = this.relations[i];
216
235
  attrValue = this.attributes[relation.key];
217
236
  if (attrValue) {
218
- if (attrValue instanceof Backbone.Collection) {
237
+ if (attrValue instanceof BackboneCollection) {
219
238
  dirtyObjects = attrValue.filter(function (m) {
220
239
  return m.hasChanged() === true;
221
240
  });
@@ -234,18 +253,18 @@
234
253
  return !!isDirty;
235
254
  },
236
255
  // Returns a hash of the changed attributes.
237
- changedAttributes:function (diff) {
256
+ changedAttributes: function (diff) {
238
257
  var delta, relation, attrValue, changedCollection, i;
239
258
  // To prevent cycles, check if this node is visited.
240
259
  if (!this.visited) {
241
260
  this.visited = true;
242
- delta = BackboneModel.changedAttributes.apply(this, arguments);
261
+ delta = ModelProto.changedAttributes.apply(this, arguments);
243
262
  if (this.relations) {
244
263
  for (i = 0; i < this.relations.length; ++i) {
245
264
  relation = this.relations[i];
246
265
  attrValue = this.attributes[relation.key];
247
266
  if (attrValue) {
248
- if (attrValue instanceof Backbone.Collection) {
267
+ if (attrValue instanceof BackboneCollection) {
249
268
  changedCollection = _.filter(attrValue.map(function (m) {
250
269
  return m.changedAttributes();
251
270
  }), function (m) {
@@ -265,12 +284,12 @@
265
284
  return !delta ? false : delta;
266
285
  },
267
286
  // Returns the hash of the previous attributes of the graph.
268
- previousAttributes:function () {
287
+ previousAttributes: function () {
269
288
  var pa, attrValue, pattrValue, pattrJSON;
270
289
  // To prevent cycles, check if this node is visited.
271
290
  if (!this.visited) {
272
291
  this.visited = true;
273
- pa = BackboneModel.previousAttributes.apply(this, arguments);
292
+ pa = ModelProto.previousAttributes.apply(this, arguments);
274
293
  if (this.relations) {
275
294
  _.each(this.relations, function (relation) {
276
295
  attrValue = this.attributes[relation.key];
@@ -279,7 +298,7 @@
279
298
  if (pattrValue && pattrValue == attrValue) {
280
299
  if (attrValue instanceof AssociatedModel) {
281
300
  pa[relation.key] = attrValue.previousAttributes();
282
- } else if (attrValue instanceof Backbone.Collection) {
301
+ } else if (attrValue instanceof BackboneCollection) {
283
302
  pa[relation.key] = attrValue.map(function (m) {
284
303
  return m.previousAttributes();
285
304
  });
@@ -295,16 +314,17 @@
295
314
  return pa;
296
315
  },
297
316
  // Return the previous value of the passed in attribute.
298
- previous:function (attr) {
317
+ previous: function (attr) {
299
318
  return this.previousAttributes()[attr];
300
319
  },
320
+
301
321
  // The JSON representation of the model.
302
- toJSON:function (options) {
322
+ toJSON: function (options) {
303
323
  var json, aJson;
304
324
  if (!this.visited) {
305
325
  this.visited = true;
306
326
  // Get json representation from `BackboneModel.toJSON`.
307
- json = BackboneModel.toJSON.apply(this, arguments);
327
+ json = ModelProto.toJSON.apply(this, arguments);
308
328
  // If `this.relations` is defined, iterate through each `relation`
309
329
  // and added it's json representation to parents' json representation.
310
330
  if (this.relations) {
@@ -320,37 +340,40 @@
320
340
  }
321
341
  return json;
322
342
  },
323
- // Deep `clone` the model.
324
- clone:function () {
325
- var cloneObj, newCollection;
326
- if (!this.visited) {
327
- this.visited = true;
328
- // Get shallow clone from `BackboneModel.clone`.
329
- cloneObj = BackboneModel.clone.apply(this, arguments);
330
- // If `this.relations` is defined, iterate through each `relation` and `clone`.
331
- if (this.relations) {
332
- _.each(this.relations, function (relation) {
333
- if (this.attributes[relation.key]) {
334
- var sourceObj = cloneObj.attributes[relation.key];
335
- if (sourceObj instanceof Backbone.Collection) {
336
- // Creates new `collection` using `relation`.
337
- newCollection = relation.collectionType ? new relation.collectionType() :
338
- this._createCollection(relation.relatedModel);
339
- // Added each `clone` model to `newCollection`.
340
- sourceObj.each(function (model) {
341
- var mClone = model.clone();
342
- mClone && newCollection.add(mClone);
343
- });
344
- cloneObj.attributes[relation.key] = newCollection;
345
- } else if (sourceObj instanceof Backbone.Model) {
346
- cloneObj.attributes[relation.key] = sourceObj.clone();
347
- }
348
- }
349
- }, this);
350
- }
351
- delete this.visited;
343
+
344
+ // Create a new model with identical attributes to this one.
345
+ clone: function () {
346
+ return new this.constructor(this.toJSON());
347
+ },
348
+
349
+ // Get `reduced` result using passed `path` array or string.
350
+ getAttr: function (path, iterator) {
351
+ var result = this,
352
+ attrs = getPathArray(path),
353
+ key,
354
+ i;
355
+ iterator || (iterator = function (memo, key) {
356
+ return memo instanceof BackboneCollection && _.isNumber(key) ? memo.at(key) : memo.attributes[key];
357
+ });
358
+ for (i = 0; i < attrs.length; i++) {
359
+ key = attrs[i];
360
+ if (!result) break;
361
+ result = iterator.call(this, result, key, attrs);
352
362
  }
353
- return cloneObj;
363
+ return result;
354
364
  }
355
365
  });
366
+
367
+ // Get Path `attrs` as Array
368
+ // Example:
369
+ // 'employee.works_for.locations[2].name' -> ['employee', 'works_for', 'locations', 2, 'name']
370
+ var getPathArray = function (path, iterator, context) {
371
+ if (_.isString(path)) {
372
+ iterator || (iterator = function (value) {
373
+ return value.match(/^\d+$/) ? parseInt(value, 10) : value;
374
+ });
375
+ return _.map(path.match(/[^\.\[\]]+/g) || [], iterator, context);
376
+ }
377
+ return path || [];
378
+ }
356
379
  })();
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backbone-associations-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: