Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

backbone-model-factory

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

backbone-model-factory - npm Package Compare versions

Comparing version 1.2.0 to 1.3.0

banner.ejs

9

backbone-model-factory-min.js

@@ -1,1 +0,8 @@

(function(root,factory){"use strict";if(typeof exports!=="undefined"&&typeof module!=="undefined"&&module.exports){exports=module.exports=require("backbone");factory(require("underscore"),exports)}else if(typeof define==="function"&&define.amd){define(["underscore","backbone"],factory)}else{factory(root._,root.Backbone)}})(this,function(_,Backbone){"use strict";var methods={wipe:function(){if(_.isObject(this.constructor._cache)){delete this.constructor._cache[""+this.get(this.idAttribute)]}}};function checkId(model,value){var key=""+model.get(model.idAttribute);var cache=model.constructor._cache;if(_.has(cache,key)){throw new Error("model idAttribute attribute value already exists in cache")}else{cache[key]=model}}Backbone.ModelFactory=function(Base,prototype){var Model,cache;prototype=_.extend({},methods,_.isObject(Base)&&!_.isFunction(Base)?Base:_.isObject(prototype)?prototype:null);Base=_.isFunction(Base)?_.has(Base,"_Model")?Base._Model:Base:Backbone.Model;Model=Base.extend(prototype);function Constructor(attrs,options){attrs=_.isObject(attrs)?attrs:null;var idAttribute=Model.prototype.idAttribute;var hasId=attrs!==null&&_.has(attrs,idAttribute);var key=hasId&&""+attrs[idAttribute];var exists=key&&_.has(cache,key);var model=exists?cache[key]:new Model(attrs,options);model.constructor=Constructor;if(key&&!exists){cache[key]=model}else{model.set(options&&options.parse?model.parse(attrs,options):attrs,options)}if(!hasId){model.once("change:"+idAttribute,checkId)}return model}Constructor._Model=Model;cache=Constructor._cache={};Constructor.prototype=Model.prototype;Constructor.wipe=function(models){models=models instanceof Backbone.Collection?models.models:models;if(!models||_.isArray(models)){_.invoke(models||_.values(this._cache),"wipe")}else if(_.isObject(models)){models.wipe()}else{throw new Error("invalid argument")}};return Constructor};return Backbone});
/*!
* backbone-model-factory
* @version 1.2.0
* @copyright 2015 Pat O'Neill <pgoneill@gmail.com>
* @license MIT
* @see {@link https://github.com/misteroneill/backbone-model-factory|GitHub}
*/
(function(e,t){"use strict";if(typeof exports!=="undefined"&&typeof module!=="undefined"&&module.exports){exports=module.exports=require("backbone");t(require("underscore"),exports)}else if(typeof define==="function"&&define.amd){define(["underscore","backbone"],t)}else{t(e._,e.Backbone)}})(this,function(e,t){"use strict";function r(e){return String(e.get(e.idAttribute))}t.ModelFactory=function(n,i){var o={};var s=function(t){var r=e.values(o);var n="wipe wipe:"+(r.length?"some":"all");u.trigger(n,u,t,r)};var u=function(t,n){t=e.isObject(t)?t:null;var i=a.prototype.idAttribute;var s=t!==null&&t.hasOwnProperty(i);var f=s&&String(t[i]);var c=f&&o.hasOwnProperty(f);var l=c?o[f]:new a(t,n);l.constructor=u;if(f&&!c){o[f]=l}else{if(n&&n.parse){t=l.parse(t,n)}l.set(t,n)}if(!s){l.once("change:"+i,function(e,t){var n=r(e);if(o.hasOwnProperty(n)){throw new Error("model idAttribute attribute value already exists in cache")}else{o[n]=e}})}return l};var a=u._Model=function(n,i){var u=typeof n==="function";i=e.extend({wipe:function(e){e=e||{};delete o[r(this)];if(!e.silent){this.trigger("wipe",this);if(!e._suppress){s([this])}}}},u?i:n);n=u?n._Model||n:t.Model;return n.extend(i)}(n,i);u._cache=o;u.prototype=a.prototype;u.wipe=function(r,n){n=n||{};if(r instanceof t.Collection){r=[].concat(r.models)}else if(!r){r=e.values(this._cache)}else if(!e.isArray(r)){r=[r]}if(!r.length){return}r=e.filter(r,function(e){return e instanceof this},this);if(r.length){e.invoke(r,"wipe",e.extend({_suppress:true},n));s(r)}else{throw new Error("invalid argument")}};e.extend(u,t.Events);return u};return t});

371

backbone-model-factory.js

@@ -1,10 +0,2 @@

/*!
Backbone Model Factory 1.2.0
Depends on: Backbone 0.9.9 - 1.1.0, Underscore
(c) 2014 Patrick G. O'Neill
Backbone Model Factory may be freely distributed under the MIT license
https://github.com/misteroneill/backbone-model-factory
*/
/* global define */
(function (root, factory) {

@@ -30,115 +22,69 @@ 'use strict';

var methods = {
/**
Removes a model instance from its constructor's cache. Will exist on the
`prototype` chain of all model instances generated from
`Backbone.ModelFactory` constructors.
*/
wipe: function () {
if (_.isObject(this.constructor._cache)) {
delete this.constructor._cache[''+this.get(this.idAttribute)];
}
}
};
/**
A function which is bound to the first change of a model's idAttribute
attribute value. Note that this function is optimistic and will allow
_any_ value to be set - it does not want to assume anything about what sort
of value to store as the idAttribute attribute.
The only limitation is that this method will throw an error if you are
supplying an ID which already exists, for example:
var Model = Backbone.ModelFactory();
var a = new Model({id: 1});
var b = new Model(); // (a === b) === false
b.set('id', 1); // throws error
The reason for this behavior is that there is no way to change the object
referenced by `b` from a change listener!
@private
@memberof Backbone.ModelFactory
@param {Object} model
The model that changed.
@param {Mixed} value
The new value of the idAttribute attribute.
*/
function checkId(model, value) {
var key = ''+model.get(model.idAttribute);
var cache = model.constructor._cache;
if (_.has(cache, key)) {
// This should not happen unless you're doing something really strange
// with your `idAttribute` attribute values!
throw new Error('model idAttribute attribute value already exists in cache');
} else {
cache[key] = model;
}
* Gets the model's `idAttribute` value as a string.
*
* @private
* @param {Object} model
* @return {String}
*/
function getId(model) {
return String(model.get(model.idAttribute));
}
/**
This method on the `Backbone` object is used to generate model constructors
which will enforce the existence of only a single object with any given
value for the `idAttribute` attribute.
@example
var Foo = Backbone.ModelFactory();
var foo1 = new Foo({id: 1});
var foo2 = new Foo({id: 1});
var foo3 = new Foo({id: 2});
console.log(foo1 === foo2); // true
console.log(foo1 === foo3); // false
@param {Function|Object} [Base]
The model constructor which the new model constructor should extend. If
a non-function object is given here, the new model constructor will
extend a and this argument will be applied as the
`prototype` instead.
@param {Object} [prototype]
If a `Base` model constructor is given, this argument should be the
prototype object of the new model constructor.
@return {Function}
A model constructor.
*/
* This method on the `Backbone` object is used to generate model
* constructors which will enforce the existence of only a single object
* with any given value for the `idAttribute` attribute.
*
* @example
* var Foo = Backbone.ModelFactory();
* var foo1 = new Foo({id: 1});
* var foo2 = new Foo({id: 1});
* var foo3 = new Foo({id: 2});
*
* console.log(foo1 === foo2); // true
* console.log(foo1 === foo3); // false
*
* @param {Function|Object} Base
* The model constructor which the new model constructor should
* extend. If a non-function object is given here, the new model
* constructor will extend a and this argument will be applied
* as the `prototype` instead.
*
* @param {Object} prototype
* If a `Base` model constructor is given, this argument should be
* the prototype object of the new model constructor.
*
* @return {Function} A model constructor.
*/
Backbone.ModelFactory = function (Base, prototype) {
var Model, cache;
var cache = {};
prototype = _.extend(
{},
methods,
/**
* Fires an appropriate constructor wipe event given a constructor
* and an array of models.
*
* @private
* @param {Array} models
*/
var constructorWipeEvent = function (models) {
var remainder = _.values(cache);
var events = 'wipe wipe:' + (remainder.length ? 'some' : 'all');
Constructor.trigger(events, Constructor, models, remainder);
};
// Support prototype object passed as first or second argument (or not
// at all).
_.isObject(Base) && !_.isFunction(Base) ? Base : (_.isObject(prototype) ? prototype : null)
);
Base = _.isFunction(Base) ?
// Support both models generated with ModelFactory or via normal means.
(_.has(Base, '_Model') ? Base._Model : Base) :
Backbone.Model;
Model = Base.extend(prototype);
/**
Return a factory function which is treated like a constructor, but
really defers back to Model and creates instances only as needed.
@param {Object} [attrs]
*/
function Constructor(attrs, options) {
* A factory function which is treated like a constructor, but really
* defers back to Model and creates instances only as needed.
*
* @param {Object} attrs
* @param {Object} options
*/
var Constructor = function (attrs, options) {
attrs = _.isObject(attrs) ? attrs : null;
var idAttribute = Model.prototype.idAttribute;
var hasId = attrs !== null && _.has(attrs, idAttribute);
var key = hasId && ''+attrs[idAttribute];
var exists = key && _.has(cache, key);
var hasId = attrs !== null && attrs.hasOwnProperty(idAttribute);
var key = hasId && String(attrs[idAttribute]);
var exists = key && cache.hasOwnProperty(key);

@@ -157,3 +103,6 @@ // Use any cached model or instantiate a new one.

} else {
model.set(options && options.parse ? model.parse(attrs, options) : attrs, options);
if (options && options.parse) {
attrs = model.parse(attrs, options);
}
model.set(attrs, options);
}

@@ -164,3 +113,14 @@

if (!hasId) {
model.once('change:' + idAttribute, checkId);
model.once('change:' + idAttribute, function (model, value) {
var key = getId(model);
if (cache.hasOwnProperty(key)) {
// This should not happen unless you're doing something really strange
// with your `idAttribute` attribute values!
throw new Error('model idAttribute attribute value already exists in cache');
} else {
cache[key] = model;
}
});
}

@@ -171,73 +131,142 @@

return model;
}
};
/**
A reference to the actual model on the constructor.
* A reference to the actual model on the constructor.
*
* @property {Function} _Model
* @example
* var Foo = Backbone.ModelFactory();
* var foo = new Foo();
*
* console.log(foo instanceof Foo.Model); // true
*/
var Model = Constructor._Model = (function (B, p) {
var baseIsFunc = typeof B === 'function';
@example
var Foo = Backbone.ModelFactory();
var foo = new Foo();
p = _.extend({
console.log(foo instanceof Foo.Model); // true
*/
Constructor._Model = Model;
/**
* Removes a model instance from its constructor's cache. Will exist on
* the `prototype` chain of all model instances generated from
* `Backbone.ModelFactory` constructors.
*
* @method wipe
* @param {Object} [options]
* @param {Boolean} [options.silent=false]
* If `true`, suppresses triggering of all event(s).
* @param {Boolean} [options._suppress=false]
* Intended to be for internal use, if `true`, suppresses
* triggering of constructor-level event(s). `silent` takes
* precedence over this option.
*/
wipe: function (options) {
options = options || {};
delete cache[getId(this)];
if (!options.silent) {
this.trigger('wipe', this);
if (!options._suppress) {
constructorWipeEvent([this]);
}
}
}
}, baseIsFunc ? p : B);
// Support both models generated with ModelFactory or via normal means.
B = baseIsFunc ? (B._Model || B) : Backbone.Model;
return B.extend(p);
})(Base, prototype);
/**
Since garbage collection will _not_ clear model instances created by model
factory constructors, it may be useful to uncache model instances which
are no longer needed from this object directly - or use the `wipe` method
on the constructor or the model itself.
* Since garbage collection will _not_ clear model instances created by
* model factory constructors, it may be useful to uncache model
* instances which are no longer needed from this object directly - or
* use the `wipe` method on the constructor or the model itself.
*
* Model instances are stored under the value of their `idAttribute` -
* for example, by default they will be stored using the value of their
* `id` attribute. Models without an `idAttribute` value (i.e. newly
* created models) will not be stored in cache until they gain an
* `idAttribute` value.
*
* @property {Object} _cache
* @example
* var Foo = Backbone.ModelFactory();
* var foo = new Foo({id: 1});
*
* Foo.wipe(foo);
*
* var bar = new Foo({id: 1});
* console.log(bar === foo); // false
* bar.wipe();
*
* var baz = new Foo();
* baz.set({id: 2});
* console.log(new Foo({id: 2}) === baz); // true
*/
Constructor._cache = cache;
Model instances are stored under the value of their `idAttribute` - for
example, by default they will be stored using the value of their `id`
attribute. Models without an `idAttribute` value (i.e. newly created
models) will not be stored in cache until they gain an `idAttribute`
value.
/**
* The `Constructor` and `Constructor.Model` share `prototype`s so that
* `instanceof` checks can support either function.
*
* @property {Object} prototype
* @example
* var Foo = Backbone.ModelFactory();
* var foo = new Foo();
*
* console.log(foo instanceof Foo.Model); // true
* console.log(foo instanceof Foo); // true
*/
Constructor.prototype = Model.prototype;
@example
var Foo = Backbone.ModelFactory();
var foo = new Foo({id: 1});
/**
* Remove a model instance, a subset of instances, or _all_ instances from
* this constructor's cache.
*
* @method wipe
* @param {Object|Backbone.Collection|Array|undefined} models
* Pass a single model instance (of this constructor only!), an
* array of instances, or a collection to wipe only those
* instances from the cache.
*
* Pass nothing (or any `falsy` value) to wipe _all_ models
* from the cache.
*
* @param {Object} [options]
* @param {Boolean} [options.silent=false]
*/
Constructor.wipe = function (models, options) {
options = options || {};
Foo.wipe(foo);
// Handle case of a collection.
if (models instanceof Backbone.Collection) {
models = [].concat(models.models);
var bar = new Foo({id: 1});
console.log(bar === foo); // false
bar.wipe();
// Handle case of a full wipe.
} else if (!models) {
models = _.values(this._cache);
var baz = new Foo();
baz.set({id: 2});
console.log(new Foo({id: 2}) === baz); // true
*/
cache = Constructor._cache = {};
// Handle case of an arbitrary value (presumably a model instance, but
// the filter will take care of it if not).
} else if (!_.isArray(models)) {
models = [models];
}
/**
The `Constructor` and `Constructor.Model` share `prototype`s so that
`instanceof` checks can support either function.
// At this point, if we have an empty array, it likely means either the
// collection or array was empty. No action is taken.
if (!models.length) {
return;
}
@example
var Foo = Backbone.ModelFactory();
var foo = new Foo();
// Filter down to model instances of this specific model only!
models = _.filter(models, function (model) {
return model instanceof this;
}, this);
console.log(foo instanceof Foo.Model); // true
console.log(foo instanceof Foo); // true
*/
Constructor.prototype = Model.prototype;
/**
Remove a model instance, a subset of instances, or _all_ instances from
this constructor's cache.
@param {Object|Backbone.Collection|Array|undefined} models
Pass a single model instance, an array of instances, or a collection to
wipe only those instances from the cache.
Pass nothing (or any `falsy` value) to wipe _all_ models from the cache.
*/
Constructor.wipe = function (models) {
models = models instanceof Backbone.Collection ? models.models : models;
if (!models || _.isArray(models)) {
_.invoke(models || _.values(this._cache), 'wipe');
} else if (_.isObject(models)) {
models.wipe();
// Finally, wipe all models still in the array - triggering a single
// event for each - and trigger the appropriate event on the constructor.
if (models.length) {
_.invoke(models, 'wipe', _.extend({_suppress: true}, options));
constructorWipeEvent(models);
} else {

@@ -248,2 +277,5 @@ throw new Error('invalid argument');

// Make the constructor into an event bus.
_.extend(Constructor, Backbone.Events);
return Constructor;

@@ -253,3 +285,2 @@ };

return Backbone;
});
{
"name": "backbone-model-factory",
"version": "1.2.0",
"version": "1.3.0",
"description": "Provides a factory for generating model constructors that will never duplicate instances of a model with the same unique identifier. Useful for sharing model instances across views.",
"homepage": "https://github.com/misteroneill/backbone-model-factory",
"main": "backbone-model-factory.js",

@@ -11,4 +12,4 @@ "repository": {

"dependencies": {
"underscore": ">= 1.5.0",
"backbone": ">= 0.9.9"
"backbone": "^1.2.0",
"underscore": "^1.8.0"
},

@@ -24,3 +25,17 @@ "keywords": [

"author": "Pat O'Neill <pgoneill@gmail.com>",
"license": "MIT"
"license": "MIT",
"scripts": {
"build": "uglifyjs backbone-model-factory.js --mangle -o backbone-model-factory-min.js && bannerize backbone-model-factory-min.js",
"preversion": "npm run build && npm test",
"postversion": "git push && git push --tags",
"test": "mocha test/test.js"
},
"devDependencies": {
"bannerize": "^1.0.0",
"cowsay": "^1.1.2",
"jquery": "^2.1.4",
"mocha": "^2.2.5",
"sinon": "^1.17.2",
"uglify-js": "^2.6.0"
}
}

@@ -7,8 +7,10 @@ # Backbone.ModelFactory

var user1 = new User({id: 1});
var user2 = new User({id: 1});
var user3 = new User({id: 2});
```js
var user1 = new User({id: 1});
var user2 = new User({id: 1});
var user3 = new User({id: 2});
console.log(user1 === user2); // true
console.log(user3 === user1); // false
console.log(user1 === user2); // true
console.log(user3 === user1); // false
```

@@ -25,6 +27,2 @@ ## Benefits

## Demo
Coming soon!
## Dependencies

@@ -34,3 +32,3 @@

- Underscore: tested against 1.5.2, but 1.2.0+ should work
- Underscore: 1.2.0+
- Backbone: 0.9.9+

@@ -46,14 +44,20 @@

var Backbone = require('backbone-model-factory');
```js
var Backbone = require('backbone-model-factory');
```
2. AMD/[RequireJS](http://requirejs.org):
require(['backbone-model-factory'], function (Backbone) {
// Do stuff...
});
```js
require(['backbone-model-factory'], function (Backbone) {
// Do stuff...
});
```
3. Browser Globals:
<script src="path/to/backbone.js"></script>
<script src="path/to/backbone-model-factory.js"></script>
```html
<script src="path/to/backbone.js"></script>
<script src="path/to/backbone-model-factory.js"></script>
```

@@ -64,42 +68,56 @@ ## Usage

var User = Backbone.Model.extend({
defaults: {
firstName: 'John',
lastName: 'Doe'
}
});
```js
var User = Backbone.Model.extend({
defaults: {
firstName: 'John',
lastName: 'Doe'
}
});
```
...do this:
var User = Backbone.ModelFactory({
defaults: {
firstName: 'John',
lastName: 'Doe',
isAdmin: false
}
});
```js
var User = Backbone.ModelFactory({
defaults: {
firstName: 'John',
lastName: 'Doe',
isAdmin: false
}
});
```
`ModelFactory` also supports inheritance, so model constructors can extend each other by providing a model constructor (whether generated by `ModelFactory` or not) as the first argument:
var Admin = Backbone.ModelFactory(User, {
defaults: _.extend({}, User.prototype.defaults, {
isAdmin: true
})
});
```js
var Admin = Backbone.ModelFactory(User, {
defaults: _.defaults({
isAdmin: true
}, User.prototype.defaults)
});
```
### Consequences of Extending Models
Models created with `ModelFactory` will __not__ share their unique-enforcement capabilities with models which they extend or which extend them. For example, using the `User` and `Admin` models above giving each the same `id` would __not__ result in the same object:
Models created with `ModelFactory` will __not__ share their unique-enforcement behavior with models which they extend or which extend them. For example, using the `User` and `Admin` models above giving each the same `id` would __not__ result in the same object:
var user = new User({id: 1});
var admin = new Admin({id: 1});
```js
var user = new User({id: 1});
var admin = new Admin({id: 1});
console.log(user === admin); // false
console.log(user === admin); // false
```
Subclassing to achieve different `defaults` is usually a bad idea. The `User`/`Admin` example models are not intended to be an example of good practices. :)
## Potential Gotchas
### Recursion and Relations
### Relationships and Recursion
If you are doing any sort of recursive relationships (models or collections within models) and then serializing those relationships you can run into some complex recursion-related problems. Also, I have not tried this library with `Backbone-Relational`, so use at your own risk!
If you are using any sort of nested relationships (models or collections within models) and operating on those relationships in a recursive manner, it can cause an infinite loop if an identical model instance exists in its own relationship graph.
For example, let's say a User has a collection of Posts and each Post has a User. Having an infinite path of object references (User -> Posts -> Post -> User -> Posts -> ad infinitum) is not harmful, but if you do any _automated/recursive serialization_ of these relationships (into JSON, etc), you might encounter an infinite loop if the serialization is unchecked.
This entirely depends on how you're using Backbone and what you've built upon it.
### Caching and Wiping

@@ -111,28 +129,68 @@

var User = Backbone.ModelFactory();
```js
var User = Backbone.ModelFactory();
var jake = new User({id: 1});
var joe1 = new User({id: 2});
var joe2 = new User({id: 2});
var jane = new User({id: 3});
var jen = new User({id: 4});
var josh = new User({id: 5});
var james = new User({id: 6});
var jake = new User({id: 1});
var joe1 = new User({id: 2});
var joe2 = new User({id: 2});
var jane = new User({id: 3});
var jen = new User({id: 4});
var josh = new User({id: 5});
var james = new User({id: 6});
// Wipe the instance of User with id: 2 from cache. "jake" will still exist in memory.
jake.wipe();
// Wipe the instance of User with id: 2 from cache.
// "jake" will still exist in memory.
jake.wipe();
// Wipe a single cached instance from the model. "joe1" and "joe2" will still exist in memory.
User.wipe(joe);
// Wipe a single cached instance from the model.
// "joe1" and "joe2" will still exist in memory.
User.wipe(joe);
// Wipe multiple cached instances in an array or a collection. As before, "jane", "jen", "josh", and "james" exist in memory still.
User.wipe([jane, jen]);
User.wipe(new Backbone.Collection([josh, james]));
// Wipe multiple cached instances in an array or a collection.
// As before, "jane", "jen", "josh", and "james" exist in
// memory still.
User.wipe([jane, jen]);
User.wipe(new Backbone.Collection([josh, james]));
console.log(_.keys(User._cache).length); // 0
console.log(_.keys(User._cache).length); // 0
```
__Note:__ This will only remove them from the _internal cache_ - any references in your views, collections, or elsewhere will __not__ be deleted!
__Note:__ This will only remove them from the _internal cache_ - any references in your views, collections, or elsewhere will __not__ be deleted! To manage _your_ references to models, hook into the various wipe events...
In the example here, all the local variables ("jake", "joe1", etc) will be garbage collected, but it's up to the developer to clean up references from his/her view, collection, etc. objects.
#### Cleaning up Your References with Wipe Events
Inspired by an issue raised by GitHub user @niksy, starting in `ModelFactory` 1.3.0, developers can clean up their own model references easily using wipe events. These are triggered with the following logic:
- Calling `model.wipe()` will trigger `"wipe"` on that instance. The instance is passed as an argument to any callback(s).
- Calling `model.wipe()` will also trigger the model/constructor-level events (see below).
- Calling `MyModel.wipe()` will trigger individual `"wipe"` events on every instance being wiped and...
- If no models are wiped, no events are fired.
- If wiping all instances (one or more), trigger `"wipe:all"`.
- If wiping fewer than all, trigger `"wipe:some"`.
- In both cases, callbacks receive the following arguments:
- `Constructor`: The model constructor on which the event was triggered. In this case, `MyModel`.
- `models`: An array of models that were removed from the cache.
- `remainder`: An array of models remaining in the cache (empty in the case of `"wipe:all"`).
In all cases, `{silent: true}` will be respected.
```js
var User = Backbone.ModelFactory();
var jake = new User({id: 1});
var jane = new User({id: 2});
var jen = new User({id: 3});
var josh = new User({id: 4});
// Triggers "wipe" on jake.
// Triggers "wipe" and "wipe:some" on User with arguments:
// - [User, [jake], [jane, jen, josh]]
jake.wipe();
// Triggers individual "wipe" events on jane, jen, and josh.
// Triggers "wipe" and "wipe:all" on User with arguments:
// - [User, [jane, jen, josh], []];
User.wipe([jane, jen, josh]);
```
## Tests

@@ -142,10 +200,14 @@

There are 3 files in `/test/inclusion` and they account for the 3 supported methods of including this module. To execute these tests, simply open the HTML files in a browser or install the npm module `backbone` and run `node node-module.js`.
There are 3 files in `test/inclusion` and they account for the 3 supported methods of including this module. To execute these tests, simply open the HTML files in a browser or install the npm dependencies and run `node test/inclusion/node-module.js`.
### Unit
[Jasmine](http://pivotal.github.io/jasmine/) tests exist in `test/test.js`. To run, simply open `test/index.html` in a browser and choose a Backbone version to test against.
[Mocha](http://mochajs.org/) tests exist in `test/test.js`.
To run the tests, make sure development dependencies are installed and use `npm test`.
To test against different versions of Backbone (or Underscore), install the version desired (e.g., `npm install backbone@~1.0.0`).
## Inspriation
This is inspired by SoundCloud's approach detailed in [Building the Next SoundCloud](http://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/) under "Sharing Models between Views."

@@ -1,222 +0,532 @@

var root = this;
/* global afterEach, beforeEach, describe, it */
var _ = require('underscore');
var assert = require('assert');
var cowsay = require('cowsay');
var sinon = require('sinon');
var Backbone = require('../backbone-model-factory');
console.log(cowsay.say({
text: 'Testing with BackboOoOoOone ' + Backbone.VERSION
}));
describe('Backbone.ModelFactory', function () {
'use strict';
var Test = root.Test = Backbone.ModelFactory({
defaults: {
foo: 'bar'
},
methodOnTest: function () {
return true;
}
beforeEach(function () {
this.sinon = sinon.sandbox.create();
this.Test = Backbone.ModelFactory({
defaults: {
foo: 'bar'
},
methodOnTest: function () {
return true;
}
});
this.TestBare = Backbone.ModelFactory();
this.TestCustomIdAttr = Backbone.ModelFactory({
idAttribute: 'foo'
});
this.TestExtended = Backbone.ModelFactory(this.Test, {
defaults: {
foo: 'baz'
}
});
this.TestCollection = Backbone.Collection.extend({
model: this.Test,
url: 'test-fetch',
sync: function (method, collection, options) {
options.success([
{id: 1, foo: 'test1'},
{id: 2, foo: 'test2'}
]);
}
});
});
var TestBare = root.TestBare = Backbone.ModelFactory();
afterEach(function () {
this.sinon.restore();
var TestCustomIdAttr = root.TestCustomIdAttr = Backbone.ModelFactory({
idAttribute: 'foo'
this.Test.off().wipe();
this.TestBare.off().wipe();
this.TestCustomIdAttr.off().wipe();
this.TestExtended.off().wipe();
});
var TestExtended = root.TestExtended = Backbone.ModelFactory(Test, {
defaults: {
foo: 'baz'
}
it('creates constructor functions', function () {
assert.strictEqual(
typeof this.Test,
'function',
'the Test constructor is a function'
);
assert.strictEqual(
typeof this.TestCustomIdAttr,
'function',
'the TestCustomIdAttr constructor is a function'
);
assert.strictEqual(
typeof this.TestExtended,
'function',
'the TestExtended constructor is a function'
);
});
var TestCollection = root.TestCollection = Backbone.Collection.extend({
model: Test,
url: 'test-fetch'
it('instantiate objects with or without an `id`', function () {
var a = new this.Test({id: 1});
var b = new this.Test();
assert.strictEqual(
a.get('foo'),
'bar',
'an instance gets defaults from its model'
);
assert.strictEqual(
a.methodOnTest,
this.Test.prototype.methodOnTest,
'an instance gets methods from its model'
);
assert.strictEqual(
b.get('foo'),
'bar',
'an instance with no `id` gets defaults from its model'
);
assert.strictEqual(
b.methodOnTest,
this.Test.prototype.methodOnTest,
'an instance with no `id` gets methods from its model'
);
assert.notStrictEqual(a, b, 'instances are separate objects');
});
it('creates constructor functions', function () {
expect(typeof Test).toBe('function');
expect(typeof TestCustomIdAttr).toBe('function');
expect(typeof TestExtended).toBe('function');
it('instantiate objects with a custom `idAttribute`', function () {
var a = new this.TestCustomIdAttr({foo: 'x'});
var b = new this.TestCustomIdAttr();
assert.strictEqual(
a.idAttribute,
'foo',
'an instance gets a custom `idAttribute` from its model'
);
assert.strictEqual(
a.get('foo'),
'x',
'an instance gets custom attributes'
);
assert.strictEqual(
b.idAttribute,
'foo',
'an instance gets a custom `idAttribute` from its model'
);
assert.strictEqual(
typeof b.get('foo'),
'undefined',
'an instance gets custom attributes'
);
assert.notStrictEqual(a, b, 'instances are separate objects');
});
describe('factory-generated constructors', function () {
it('instantiate objects with proper inheritance', function () {
var a = new this.TestExtended();
afterEach(function () {
Test.wipe();
TestBare.wipe();
TestCustomIdAttr.wipe();
TestExtended.wipe();
});
assert.strictEqual(
a.constructor,
this.TestExtended,
'an instance has the correct constructor'
);
it('instantiate objects with or without an `id`', function () {
var a = new Test({id: 1});
var b = new Test();
assert.strictEqual(
a.get('foo'),
'baz',
'an instance gets defaults from its model'
);
expect(a.get('foo')).toBe('bar');
expect(a.methodOnTest).toBe(Test.prototype.methodOnTest);
assert.strictEqual(
a.methodOnTest,
this.Test.prototype.methodOnTest,
'an instance may get methods from its model\'s super-class'
);
});
expect(b.get('foo')).toBe('bar');
expect(b.methodOnTest).toBe(Test.prototype.methodOnTest);
it('instantiate objects which pass `instanceof` checks against the constructor and the actual model', function () {
var a = new this.Test({id: 1});
expect(a).not.toBe(b);
});
assert(
a instanceof this.Test,
'an instance is an `instanceof` its constructor'
);
it('instantiate objects with a custom `idAttribute`', function () {
var a = new TestCustomIdAttr({foo: 'x'});
var b = new TestCustomIdAttr();
assert(
a instanceof this.Test._Model,
'an instance is also an `instanceof` its model'
);
});
expect(a.idAttribute).toBe('foo');
expect(a.get('foo')).toBe('x');
expect(b.idAttribute).toBe('foo');
expect(typeof b.get('foo')).toBe('undefined');
it('return an existing instance if a duplicate value for `id` is given', function () {
var a = new this.Test({id: 1});
expect(a).not.toBe(b);
});
assert.strictEqual(
new this.Test({id: 1}),
a,
'constructors return existing instances for duplicate `id`s'
);
it('instantiate objects with proper inheritance', function () {
var a = new TestExtended();
assert.notStrictEqual(
new this.Test({id: 2}),
a,
'constructors return new instances for new `id`s'
);
});
expect(a.constructor).toBe(TestExtended);
expect(a.get('foo')).toBe('baz');
expect(a.methodOnTest).toBe(Test.prototype.methodOnTest);
});
it('return an existing instance with custom `idAttribute` if a duplicate value for `idAttribute` attribute is given', function () {
var a = new this.TestCustomIdAttr({foo: 'x'});
it('instantiate objects which pass `instanceof` checks against the constructor and the actual model', function () {
var a = new Test({id: 1});
expect(a instanceof Test).toBe(true);
expect(a instanceof Test._Model).toBe(true);
});
assert.strictEqual(
new this.TestCustomIdAttr({foo: 'x'}),
a,
'constructors return existing instances for duplicate `id`s'
);
it('return an existing instance if a duplicate value for `id` is given', function () {
var a = new Test({id: 1});
expect(new Test({id: 1})).toBe(a);
expect(new Test({id: 2})).not.toBe(a);
});
assert.notStrictEqual(
new this.TestCustomIdAttr({foo: 'y'}),
a,
'constructors return new instances for new `id`s'
);
});
it('return an existing instance with custom `idAttribute` if a duplicate value for `idAttribute` attribute is given', function () {
var a = new TestCustomIdAttr({foo: 'x'});
it('return an updated existing instance if a duplicate `id` and new attributes are given', function () {
var a = new this.Test({id: 1});
var b = new this.Test({id: 1, foo: 'bar'});
expect(new TestCustomIdAttr({foo: 'x'})).toBe(a);
expect(new TestCustomIdAttr({foo: 'y'})).not.toBe(a);
});
assert.strictEqual(
a,
b,
'constructors return existing instances for duplicate `id`s'
);
it('return an updated existing instance if a duplicate `id` and new attributes are given', function () {
var a = new Test({id: 1});
var b = new Test({id: 1, foo: 'bar'});
assert.strictEqual(
a.get('foo'),
'bar',
'subsequent duplicate instances update all other instances'
);
});
expect(a).toBe(b);
expect(a.get('foo')).toBe('bar');
});
it('store an instance when a new instance gains an `id` that did not exist', function () {
var a = new this.Test();
it('store an instance when a new instance gains an `id` that did not exist', function () {
var a = new Test();
a.set({id: 4});
a.set({id: 4});
expect(Test._cache['4']).toBe(a);
});
assert.strictEqual(
this.Test._cache['4'],
a,
'an instance is cached when it gains an `id`'
);
});
it('throw an error when an instance gains an `idAttribute` attribute value which already exists', function () {
var a = new Test({id: 5});
var b = new Test();
it('throw an error when an instance gains an `idAttribute` attribute value which already exists', function () {
new this.Test({id: 5});
var b = new this.Test();
expect(function () {
b.set({id: 5});
}).toThrow();
});
assert.throws(function () {
b.set({id: 5});
}, Error, 'an error is thrown when an instance gains a duplicate `id`');
});
it('store an instance when a new instance with a custom `idAttribute` gains an `idAttribute` attribute value that did not exist', function () {
var a = new TestCustomIdAttr();
it('store an instance when a new instance with a custom `idAttribute` gains an `idAttribute` attribute value that did not exist', function () {
var a = new this.TestCustomIdAttr();
a.set({foo: 'x'});
expect(TestCustomIdAttr._cache.x).toBe(a);
});
a.set({foo: 'x'});
it('throw an error when a new instance with a custom `idAttribute` to gain an `idAttribute` attribute value that already exists', function () {
var a = new TestCustomIdAttr({foo: 'x'});
var b = new TestCustomIdAttr();
assert.strictEqual(
this.TestCustomIdAttr._cache.x,
a,
'an instance is cached when it gains a custom `idAttribute` value'
);
});
expect(function () {
it('throw an error when a new instance with a custom `idAttribute` to gain an `idAttribute` attribute value that already exists', function () {
new this.TestCustomIdAttr({foo: 'x'});
var b = new this.TestCustomIdAttr();
assert.throws(
function () {
b.set({foo: 'x'});
}).toThrow();
});
},
Error,
'an error is thrown when an instance gains a duplicate custom `idAttribute` value'
);
});
it('work when used as the `model` for a `Backbone.Collection`', function () {
var collection = new TestCollection([
{foo: 'boo'}
]);
it('work when used as the `model` for a `Backbone.Collection`', function () {
var collection = new this.TestCollection([
{foo: 'boo'}
]);
var model = collection.first();
var model = collection.first();
expect(model.constructor).toBe(Test);
expect(model.methodOnTest).toBe(Test.prototype.methodOnTest);
});
assert.strictEqual(
model.constructor,
this.Test,
'models constructed through a collection have the correct constructor'
);
it('are able to be fetched from the server (#5)', function () {
var server = sinon.fakeServer.create();
assert.strictEqual(
model.methodOnTest,
this.Test.prototype.methodOnTest,
'models constructed through a collection have inherited methods'
);
});
server.autoRespond = true;
server.autoRespondAfter = 50;
it('are able to be fetched from the server (#5)', function () {
var collection = new this.TestCollection();
server.respondWith(/test-fetch/, function (xhr) {
var results = [];
collection.fetch();
xhr.respond(
200,
{'Content-Type': 'application/json'},
JSON.stringify([
{id: 1, foo: 'test1'},
{id: 2, foo: 'test2'}
])
);
});
assert.strictEqual(
collection.length,
2,
'models were fetched from the server'
);
var collection = new TestCollection();
var request = collection.fetch();
assert.strictEqual(
collection.at(0).get('foo'),
'test1',
'the models look as expected'
);
});
waitsFor(function () {
return request.status === 200;
}, 'the request to return', 200);
it('generate instances which can `wipe` themselves from cache', function () {
var a = new this.Test({id: 1});
runs(function () {
expect(collection.length).toBe(2);
expect(collection.at(0).get('foo')).toBe('test1');
});
});
assert.strictEqual(
typeof a.wipe,
'function',
'`wipe` should be a method'
);
it('generate instances which can `wipe` themselves from cache', function () {
var a = new Test({id: 1});
a.wipe();
expect(_.isFunction(a.wipe)).toBe(true);
a.wipe();
expect(new Test({id: 1})).not.toBe(a);
});
assert.notStrictEqual(
new this.Test({id: 1}),
a,
'newly created instances with previously existing `id`s are not equal to previously cached instances'
);
});
it('can `wipe` an instance or instances depending on arguments', function () {
var a = new Test({id: 1});
var b = new Test({id: 2});
var c = new Test({id: 3});
var d = new Test({id: 6});
var e = new Test({id: 7});
it('can `wipe` an instance or instances depending on arguments', function () {
var a = new this.Test({id: 1});
var b = new this.Test({id: 2});
var c = new this.Test({id: 3});
var d = new this.Test({id: 6});
var e = new this.Test({id: 7});
var x = new TestCollection([
{id: 4},
{id: 5}
]);
var x = new this.TestCollection([
{id: 4},
{id: 5}
]);
var strOfKeys = function () {
return _.sortBy(_.keys(Test._cache), _.identity).join('');
};
var strOfKeys = _.bind(function () {
return _.sortBy(_.keys(this.Test._cache), _.identity).join('');
}, this);
expect(_.isFunction(Test.wipe)).toBe(true);
expect(strOfKeys()).toBe('1234567');
assert.strictEqual(typeof this.Test.wipe, 'function');
assert.strictEqual(strOfKeys(), '1234567', 'should have all instances cached');
Test.wipe(a);
expect(strOfKeys()).toBe('234567');
this.Test.wipe(a);
assert.strictEqual(strOfKeys(), '234567', '1 should have been wiped');
Test.wipe([b, c]);
expect(strOfKeys()).toBe('4567');
this.Test.wipe([b, c]);
assert.strictEqual(strOfKeys(), '4567', '2 and 3 should have been wiped');
Test.wipe(x);
expect(strOfKeys()).toBe('67');
this.Test.wipe(x);
assert.strictEqual(strOfKeys(), '67', '4 and 5 should have been wiped');
Test.wipe();
expect(strOfKeys()).toBe('');
});
this.Test.wipe();
assert.strictEqual(strOfKeys(), '', 'cache should be empty');
});
it('triggers events on the instance and constructor when an instance `wipe`s itself', function () {
var Test = this.Test;
var a = new Test({id: 1});
var aSpy = this.sinon.spy();
var wipeSpy = this.sinon.spy();
var someSpy = this.sinon.spy();
var allSpy = this.sinon.spy();
var spyArgsAssertions = function(args) {
assert.strictEqual(
args[0],
Test,
'the spy was called with the constructor first'
);
assert.strictEqual(
args[1].length,
1,
'the spy was called with an array of wiped instances second'
);
assert.strictEqual(
args[1][0],
a,
'the array contained only the instance wiped'
);
assert.strictEqual(
args[2].length,
0,
'the spy was called with an array of remaining instances, which was empty'
);
};
Test.on('wipe', wipeSpy);
Test.on('wipe:some', someSpy);
Test.on('wipe:all', allSpy);
a.on('wipe', aSpy);
a.wipe();
assert.notStrictEqual(
Test._cache['1'],
a,
'the wiped instance should no longer be in cache'
);
assert(
aSpy.calledOnce,
'the wiped instance triggered a "wipe" event'
);
assert(
aSpy.getCall(0).calledWithExactly(a),
'the callback was called with the instance'
);
assert(
wipeSpy.calledOnce,
'the "wipe" event was triggered once'
);
spyArgsAssertions(wipeSpy.getCall(0).args);
assert(
!someSpy.called,
'the "wipe:some" spy was not called'
);
assert(
allSpy.calledOnce,
'the "wipe:all" spy was called once'
);
spyArgsAssertions(allSpy.getCall(0).args);
});
it('triggers events on the instance and constructor when the constructor `wipe`s itself', function () {
var Test = this.Test;
var a = new Test({id: 1});
var b = new Test({id: 2});
var aSpy = this.sinon.spy();
var bSpy = this.sinon.spy();
var wipeSpy = this.sinon.spy();
var someSpy = this.sinon.spy();
var allSpy = this.sinon.spy();
var spyArgsAssertions = function(args) {
assert.strictEqual(
args[0],
Test,
'the spy was called with the constructor first'
);
assert.strictEqual(
args[1].length,
1,
'the spy was called with an array of wiped instances second'
);
assert.strictEqual(
args[1][0],
b,
'the array contained only the instance wiped'
);
assert.strictEqual(
args[2].length,
1,
'the spy was called with an array of remaining instances'
);
assert.strictEqual(
args[2][0],
a,
'the array contained only the instance NOT wiped'
);
};
Test.on('wipe', wipeSpy);
Test.on('wipe:some', someSpy);
Test.on('wipe:all', allSpy);
a.on('wipe', aSpy);
b.on('wipe', bSpy);
b.wipe();
assert.notStrictEqual(
Test._cache['2'],
b,
'the wiped instance should no longer be in cache'
);
assert(
!aSpy.called,
'the remaining instance triggered no event'
);
assert(
bSpy.calledOnce,
'the wiped instance triggered a "wipe" event'
);
assert(
bSpy.getCall(0).calledWithExactly(b),
'the callback was called with the instance'
);
assert(
wipeSpy.calledOnce,
'the "wipe" event was triggered once'
);
spyArgsAssertions(wipeSpy.getCall(0).args);
assert(
!allSpy.called,
'the "wipe:all" spy was not called'
);
assert(
someSpy.calledOnce,
'the "wipe:some" spy was called once'
);
spyArgsAssertions(someSpy.getCall(0).args);
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc