backbone-associations-rails 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,7 +4,7 @@ This gem vendors the [Backbone-associations](https://github.com/dhruvaray/backbo
4
4
 
5
5
  ## Dependencies
6
6
 
7
- ```backbone-associations``` requires Backbone.js >= 0.9.2 and Underscore.js >= 1.3.3
7
+ ```backbone-associations``` requires Backbone.js >= 0.9.10 and Underscore.js >= 1.4.3
8
8
 
9
9
  ## Installation
10
10
 
@@ -1,7 +1,7 @@
1
1
  module Backbone
2
2
  module Associations
3
3
  module Rails
4
- VERSION = '0.4.0'
4
+ VERSION = '0.4.1'
5
5
  end
6
6
  end
7
7
  end
@@ -1,34 +1,39 @@
1
1
  //
2
- // Backbone-associations.js 0.4.0
2
+ // Backbone-associations.js 0.4.1
3
+ //
4
+ // (c) 2013 Dhruva Ray, Jaynti Kanani, Persistent Systems Ltd.
5
+ // Backbone-associations may be freely distributed under the MIT license.
6
+ // For all details and documentation:
7
+ // https://github.com/dhruvaray/backbone-associations/
3
8
  //
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.
9
9
 
10
10
  // Initial Setup
11
11
  // --------------
12
12
  (function () {
13
13
  "use strict";
14
14
 
15
+ // Save a reference to the global object (`window` in the browser, `exports`
16
+ // on the server).
17
+ var root = this;
15
18
 
16
19
  // The top-level namespace. All public Backbone classes and modules will be attached to this.
17
20
  // Exported for the browser and CommonJS.
18
21
  var _, Backbone, BackboneModel, BackboneCollection, ModelProto,
19
- defaultEvents, AssociatedModel;
22
+ defaultEvents, AssociatedModel, pathChecker;
23
+
20
24
  if (typeof require !== 'undefined') {
21
25
  _ = require('underscore');
22
26
  Backbone = require('backbone');
23
27
  exports = module.exports = Backbone;
24
28
  } else {
25
- _ = window._;
26
- Backbone = window.Backbone;
29
+ _ = root._;
30
+ Backbone = root.Backbone;
27
31
  }
28
32
  // Create local reference `Model` prototype.
29
33
  BackboneModel = Backbone.Model;
30
34
  BackboneCollection = Backbone.Collection;
31
35
  ModelProto = BackboneModel.prototype;
36
+ pathChecker = /[\.\[\]]+/g;
32
37
 
33
38
  // Built-in Backbone `events`.
34
39
  defaultEvents = ["change", "add", "remove", "reset", "destroy",
@@ -45,12 +50,13 @@
45
50
  // Define relations with Associated Model.
46
51
  relations:undefined,
47
52
  // Define `Model` property which can keep track of already fired `events`,
48
- // and prevent redundant event to be triggered in case of circular model graph.
53
+ // and prevent redundant event to be triggered in case of cyclic model graphs.
49
54
  _proxyCalls:undefined,
50
55
 
51
56
  // Get the value of an attribute.
52
57
  get:function (attr) {
53
- return this.getAttr.apply(this, arguments);
58
+ var obj = ModelProto.get.call(this, attr);
59
+ return obj ? obj : this.getAttr.apply(this, arguments);
54
60
  },
55
61
 
56
62
  // Set a hash of model attributes on the Backbone Model.
@@ -67,14 +73,19 @@
67
73
  }
68
74
  if (!attributes) return this;
69
75
  for (attr in attributes) {
70
- var pathTokens = getPathArray(attr), initials = _.initial(pathTokens), last = _.last(pathTokens),
71
- root = this, parentModel = this.get(initials);
72
-
76
+ //Create a map for each unique object whose attributes we want to set
73
77
  modelMap || (modelMap = {});
74
- if ((!parentModel && _.size(initials) > 0) || parentModel instanceof BackboneCollection) continue;
75
- parentModel instanceof AssociatedModel && (root = parentModel);
76
- obj = modelMap[root.cid] || (modelMap[root.cid] = {'model':root, 'data':{}});
77
- obj.data[last] = attributes[attr];
78
+ if (attr.match(pathChecker)) {
79
+ var pathTokens = getPathArray(attr), initials = _.initial(pathTokens), last = pathTokens[pathTokens.length - 1],
80
+ parentModel = this.get(initials);
81
+ if (parentModel instanceof AssociatedModel) {
82
+ obj = modelMap[parentModel.cid] || (modelMap[parentModel.cid] = {'model':parentModel, 'data':{}});
83
+ obj.data[last] = attributes[attr];
84
+ }
85
+ } else {
86
+ obj = modelMap[this.cid] || (modelMap[this.cid] = {'model':this, 'data':{}});
87
+ obj.data[attr] = attributes[attr];
88
+ }
78
89
  }
79
90
  if (modelMap) {
80
91
  for (modelId in modelMap) {
@@ -82,7 +93,7 @@
82
93
  this.setAttr.call(obj.model, obj.data, options) || (result = false);
83
94
  }
84
95
  } else {
85
- result = this.setAttr.call(this, attributes, options);
96
+ return this.setAttr.call(this, attributes, options);
86
97
  }
87
98
  return result;
88
99
  },
@@ -92,7 +103,7 @@
92
103
  // It maintains relations between models during the set operation.
93
104
  // It also bubbles up child events to the parent.
94
105
  setAttr:function (attributes, options) {
95
- var processedRelations, tbp, attr;
106
+ var attr;
96
107
  // Extract attributes and options.
97
108
  options || (options = {});
98
109
  if (options.unset) for (attr in attributes) attributes[attr] = void 0;
@@ -120,106 +131,90 @@
120
131
  throw new Error('collectionType must inherit from Backbone.Collection');
121
132
  }
122
133
 
123
- // If `attributes` has no property present,
124
- // create `Collection` having `relation.collectionType` as type and
125
- // `relation.Model` as model reference and perform Backbone `set`.
126
134
  if (val instanceof BackboneCollection) {
127
- ModelProto.set.call(this, relationKey, val, relationOptions);
128
- } else if (!this.attributes[relationKey]) {
129
- data = collectionType ? new collectionType() : this._createCollection(relatedModel);
130
- data.add(val, relationOptions);
131
- ModelProto.set.call(this, relationKey, data, relationOptions);
135
+ data = val;
136
+ attributes[relationKey] = data;
132
137
  } else {
133
- this.attributes[relationKey].reset(val, relationOptions);
138
+ if (!this.attributes[relationKey]) {
139
+ data = collectionType ? new collectionType() : this._createCollection(relatedModel);
140
+ data.add(val, relationOptions);
141
+ attributes[relationKey] = data;
142
+ } else {
143
+ this.attributes[relationKey].reset(val, relationOptions);
144
+ delete attributes[relationKey];
145
+ }
134
146
  }
147
+
135
148
  } else if (relation.type === Backbone.One && relatedModel) {
136
- // If passed data is not instance of `Backbone.AssociatedModel`,
137
- // create `AssociatedModel` and perform backbone `set`.
138
149
  data = val instanceof AssociatedModel ? val : new relatedModel(val);
139
- ModelProto.set.call(this, relationKey, data, relationOptions)
150
+ attributes[relationKey] = data;
140
151
  }
141
152
 
142
- relationValue = this.attributes[relationKey];
153
+ relationValue = data;
143
154
 
144
155
  // Add proxy events to respective parents.
145
156
  // Only add callback if not defined.
146
157
  if (relationValue && !relationValue._proxyCallback) {
147
158
  relationValue._proxyCallback = function () {
148
- return this._bubbleEvent.call(this, relationKey, arguments);
159
+ return this._bubbleEvent.call(this, relationKey, relationValue, arguments);
149
160
  };
150
161
  relationValue.on("all", relationValue._proxyCallback, this);
151
162
  }
152
163
 
153
- // Create a local `processedRelations` array to store the relation key which has been processed.
154
- // We cannot use `this.relations` because if there is no value defined for `relationKey`,
155
- // it will not get processed by either `BackboneModel` `set` or the `AssociatedModel` `set`.
156
- !processedRelations && (processedRelations = []);
157
- if (_.indexOf(processedRelations, relationKey) === -1) {
158
- processedRelations.push(relationKey);
159
- }
160
164
  }
161
165
  }, this);
162
166
  }
163
- if (processedRelations) {
164
- // Find attributes yet to be processed - `tbp`.
165
- tbp = {};
166
- for (attr in attributes) {
167
- if (_.indexOf(processedRelations, attr) === -1) {
168
- tbp[attr] = attributes[attr];
169
- }
170
- }
171
- } else {
172
- // Set all `attributes` to `tbp`.
173
- tbp = attributes;
174
- }
175
167
  // Return results for `BackboneModel.set`.
176
- return ModelProto.set.call(this, tbp, options);
168
+ return ModelProto.set.call(this, attributes, options);
177
169
  },
178
170
  // Bubble-up event to `parent` Model
179
- _bubbleEvent:function (relationKey, eventArguments) {
171
+ _bubbleEvent:function (relationKey, relationValue, eventArguments) {
180
172
  var args = eventArguments,
181
173
  opt = args[0].split(":"),
182
174
  eventType = opt[0],
183
175
  eventObject = args[1],
184
176
  indexEventObject = -1,
185
- relationValue = this.attributes[relationKey],
186
177
  _proxyCalls = relationValue._proxyCalls,
187
178
  eventPath,
188
179
  eventAvailable;
189
180
  // Change the event name to a fully qualified path.
190
- if (_.contains(defaultEvents, eventType)) {
191
- _.size(opt) > 1 && (eventPath = opt[1]);
192
- // Find the specific object in the collection which has changed.
193
- if (relationValue instanceof BackboneCollection && "change" === eventType && eventObject) {
194
- //indexEventObject = _.indexOf(relationValue.models, eventObject);
195
- var pathTokens = getPathArray(eventPath),
196
- initialTokens = _.initial(pathTokens), colModel;
181
+ _.size(opt) > 1 && (eventPath = opt[1]);
182
+ // Find the specific object in the collection which has changed.
183
+ if (relationValue instanceof BackboneCollection && "change" === eventType && eventObject) {
184
+ var pathTokens = getPathArray(eventPath),
185
+ initialTokens = _.initial(pathTokens), colModel;
197
186
 
198
- colModel = relationValue.find(function (model) {
199
- var changedModel = model.get(pathTokens);
200
- return eventObject === (changedModel instanceof AssociatedModel
201
- || changedModel instanceof BackboneCollection)
202
- ? changedModel : (model.get(initialTokens) || model);
203
- });
204
- colModel && (indexEventObject = relationValue.indexOf(colModel));
205
- }
206
- // Manipulate `eventPath`.
207
- eventPath = relationKey + (indexEventObject !== -1 ?
208
- "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
209
- args[0] = eventType + ":" + eventPath;
187
+ colModel = relationValue.find(function (model) {
188
+ var changedModel = model.get(pathTokens);
189
+ return eventObject === (changedModel instanceof AssociatedModel
190
+ || changedModel instanceof BackboneCollection)
191
+ ? changedModel : (model.get(initialTokens) || model);
192
+ });
193
+ colModel && (indexEventObject = relationValue.indexOf(colModel));
194
+ }
195
+ // Manipulate `eventPath`.
196
+ eventPath = relationKey + (indexEventObject !== -1 ?
197
+ "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
198
+ args[0] = eventType + ":" + eventPath;
210
199
 
211
- // If event has been already triggered as result of same source `eventPath`,
212
- // no need to re-trigger event to prevent cycle.
213
- if (_proxyCalls) {
214
- eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
215
- return eventPath.indexOf(eventKey, eventPath.length - eventKey.length) !== -1;
216
- });
217
- if (eventAvailable) return this;
218
- } else {
219
- _proxyCalls = relationValue._proxyCalls = {};
220
- }
221
- // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
222
- _proxyCalls[eventPath] = true;
200
+ // If event has been already triggered as result of same source `eventPath`,
201
+ // no need to re-trigger event to prevent cycle.
202
+ if (_proxyCalls) {
203
+ eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
204
+ return eventPath.indexOf(eventKey, eventPath.length - eventKey.length) !== -1;
205
+ });
206
+ if (eventAvailable) return this;
207
+ } else {
208
+ _proxyCalls = relationValue._proxyCalls = {};
209
+ }
210
+ // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
211
+ _proxyCalls[eventPath] = true;
212
+
213
+
214
+ //Set up previous attributes correctly. Backbone v0.9.10 upwards...
215
+ if ("change" === eventType) {
216
+ this._previousAttributes[relationKey] = relationValue._previousAttributes;
217
+ this.changed[relationKey] = relationValue;
223
218
  }
224
219
 
225
220
  // Bubble up event to parent `model` with new changed arguments.
@@ -246,103 +241,6 @@
246
241
  }
247
242
  return collection;
248
243
  },
249
- // Has the model changed. Traverse the object hierarchy to compute dirtyness.
250
- hasChanged:function (attr) {
251
- var isDirty, relation, attrValue, i, dirtyObjects;
252
- // To prevent cycles, check if this node is visited.
253
- if (!this.visitedHC) {
254
- this.visitedHC = true;
255
- isDirty = ModelProto.hasChanged.apply(this, arguments);
256
- if (!isDirty && this.relations) {
257
- // Go down the hierarchy to see if anything has `changed`.
258
- for (i = 0; i < this.relations.length; ++i) {
259
- relation = this.relations[i];
260
- attrValue = this.attributes[relation.key];
261
- if (attrValue) {
262
- if (attrValue instanceof BackboneCollection) {
263
- dirtyObjects = attrValue.filter(function (m) {
264
- return m.hasChanged() === true;
265
- });
266
- _.size(dirtyObjects) > 0 && (isDirty = true);
267
- } else {
268
- isDirty = attrValue.hasChanged && attrValue.hasChanged();
269
- }
270
- if (isDirty) {
271
- break;
272
- }
273
- }
274
- }
275
- }
276
- delete this.visitedHC;
277
- }
278
- return !!isDirty;
279
- },
280
- // Returns a hash of the changed attributes.
281
- changedAttributes:function (diff) {
282
- var delta, relation, attrValue, changedCollection, i;
283
- // To prevent cycles, check if this node is visited.
284
- if (!this.visited) {
285
- this.visited = true;
286
- delta = ModelProto.changedAttributes.apply(this, arguments);
287
- if (this.relations) {
288
- for (i = 0; i < this.relations.length; ++i) {
289
- relation = this.relations[i];
290
- attrValue = this.attributes[relation.key];
291
- if (attrValue) {
292
- if (attrValue instanceof BackboneCollection) {
293
- changedCollection = _.filter(attrValue.map(function (m) {
294
- return m.changedAttributes();
295
- }), function (m) {
296
- return !!m;
297
- });
298
- if (_.size(changedCollection) > 0) {
299
- delta[relation.key] = changedCollection;
300
- }
301
- } else if (attrValue instanceof AssociatedModel && attrValue.hasChanged()) {
302
- delta[relation.key] = attrValue.toJSON();
303
- }
304
- }
305
- }
306
- }
307
- delete this.visited;
308
- }
309
- return !delta ? false : delta;
310
- },
311
- // Returns the hash of the previous attributes of the graph.
312
- previousAttributes:function () {
313
- var pa, attrValue, pattrValue, pattrJSON;
314
- // To prevent cycles, check if this node is visited.
315
- if (!this.visited) {
316
- this.visited = true;
317
- pa = ModelProto.previousAttributes.apply(this, arguments);
318
- if (this.relations) {
319
- _.each(this.relations, function (relation) {
320
- attrValue = this.attributes[relation.key];
321
- pattrValue = pa[relation.key];
322
- pattrJSON = pattrValue ? pattrValue.toJSON() : undefined;
323
- if (pattrValue && pattrValue == attrValue) {
324
- if (attrValue instanceof AssociatedModel) {
325
- pa[relation.key] = attrValue.previousAttributes();
326
- } else if (attrValue instanceof BackboneCollection) {
327
- pa[relation.key] = attrValue.map(function (m) {
328
- return m.previousAttributes();
329
- });
330
- }
331
- } else {
332
- if (pattrValue)
333
- pa[relation.key] = pattrJSON;
334
- }
335
- }, this);
336
- }
337
- delete this.visited;
338
- }
339
- return pa;
340
- },
341
- // Return the previous value of the passed in attribute.
342
- previous:function (attr) {
343
- return this.previousAttributes()[attr];
344
- },
345
-
346
244
  // The JSON representation of the model.
347
245
  toJSON:function (options) {
348
246
  var json, aJson;
@@ -371,36 +269,30 @@
371
269
  return new this.constructor(this.toJSON());
372
270
  },
373
271
 
374
- // Get `reduced` result using passed `path` array or string.
375
- getAttr:function (path, iterator) {
272
+ //Navigate the path to the leaf object in the path to query for the attribute value
273
+ getAttr:function (path) {
274
+
376
275
  var result = this,
276
+ //Tokenize the path
377
277
  attrs = getPathArray(path),
378
278
  key,
379
279
  i;
380
280
  if (_.size(attrs) < 1) return;
381
- iterator || (iterator = function (memo, key) {
382
- return memo instanceof BackboneCollection && _.isNumber(key) ? memo.at(key) : memo.attributes[key];
383
- });
384
281
  for (i = 0; i < attrs.length; i++) {
385
282
  key = attrs[i];
386
283
  if (!result) break;
387
- result = iterator.call(this, result, key, attrs);
284
+ //Navigate the path to get to the result
285
+ result = result instanceof BackboneCollection && (!isNaN(key)) ? result.at(key) : result.attributes[key];
388
286
  }
389
287
  return result;
390
288
  }
391
289
  });
392
290
 
393
- var _index = /^\d+$/;
394
- var _pathTokenizer = /[^\.\[\]]+/g;
291
+ var delimiters = /[^\.\[\]]+/g;
395
292
 
396
- // Get Path `attrs` as Array
397
- var getPathArray = function (path, iterator, context) {
398
- if (_.isString(path)) {
399
- iterator || (iterator = function (value) {
400
- return value.match(_index) ? parseInt(value, 10) : value;
401
- });
402
- return _.map(path.match(_pathTokenizer) || [''], iterator, context);
403
- }
404
- return path || [''];
293
+ // Tokenize the fully qualified event path
294
+ var getPathArray = function (path) {
295
+ if (path === '') return [''];
296
+ return _.isString(path) ? (path.match(delimiters)) : path || [];
405
297
  }
406
- })();
298
+ }).call(this);
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.4.0
4
+ version: 0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: