backbone-associations-rails 0.3.0 → 0.3.1

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.
@@ -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: