Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 150 additions & 38 deletions README.mkd
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@ Backbone Factory
Introduction
------------

Backbone Factory is a small javascript library for creating [Backbone.js](http://documentcloud.github.com/backbone/) objects for testing your code. It has no external dependency.
Backbone Factory is a small javascript library for creating [Backbone.js](http://documentcloud.github.com/backbone/) objects for testing your code. We have extended [this](https://github.com/SupportBee/Backbone-Factory) library to provide support from nested models/collections. This is a strict superset of the original authors functionality. It has no external dependency.

The API is heavily inspired by the awesome [Factory Girl](https://github.com/thoughtbot/factory_girl).
The API is heavily inspired by the awesome [Factory Girl](https://github.com/thoughtbot/factory_girl).


Installation
------------

To use it, just [download](https://github.com/SupportBee/Backbone-Factory/raw/master/public/javascripts/backbone-factory.js) the file and include it in your testing setup.
To use it, just [download](https://github.com/recruiterbox/Backbone-Factory/raw/master/public/javascripts/backbone-factory.js) the file and include it in your testing setup.
Usage
-----

Lets say you have two Backbone models, Post and User
Let's say you have two Backbone models - Post and User

```javascript
var User = Backbone.Model.extend({

name: null,
email: null
defaults: {
name: 'Raghu',
email: '[email protected]'
}

});

Expand All @@ -40,57 +42,171 @@ var Post = Backbone.Model.extend({
To define factories for them

```javascript
var postFactory = BackboneFactory.define('post', Post);
var userFactory = BackboneFactory.define('user', User);
BackboneFactory.define('post', Post);
BackboneFactory.define('user', User);
```

### Using Factories

To use these factories,
To use these factories,

```javscript
this.postObject = BackboneFactory.create('post');
this.userObject = BackboneFactory.create('user');
```javascript
var postObject = BackboneFactory.create('post');
var userObject = BackboneFactory.create('user');
```

This will create objects using the [defaults](http://documentcloud.github.com/backbone/#Model-defaults) you have in your class definitions.
This will create objects using the [defaults](http://documentcloud.github.com/backbone/#Model-defaults) you have in your class definitions. It is easy to override the defaults when creating an object
```javascript

var userObject = BackboneFactory.create('user', function(){
return {
name: "Custom Name",
email: "[email protected]"
};
});
```

### Defining and using Collections

### Defining Sequences
To define factories for collections

```javascript
var emailSequence = BackboneFactory.define_sequence('email', function(n){
return "person"+n+"@example.com";
});
var Posts = Backbone.Collection.extend({
model: Post
});
var Users = Backbone.Collection.extend({
model: User
});

BackboneFactory.define_collection('posts', Posts, 3); //Third arugement is the default size of the collection
BackboneFactory.define_collection('users', Users, 4);

var postsCollection = BackboneFactory.create_collection('posts'); //Creates 3 posts
var usersCollection = BackboneFactory.create_collection('users',2) //Creates only 2 users overriding the default of 4

```

### Using Sequences

### Schema Support

If your backbone models define a schema, it is used to set default values. Nested models are also supported.

```javascript
var email = BackboneFactory.next('email') // [email protected]

var PostWithSchema = Backbone.Model.extend({

schema: {
title: {
type: 'string',
default: 'Default value from schema'
},
body: {
type: 'string',
default: 'Default body'
},
author: {
type: 'related',
_constructor: User
}
}

});


BackboneFactory.define('post_with_schema', PostWithSchema);
var post = BackboneFactory.create('post_with_schema');

//Post title will be picked up from schema
console.log(post.get('title'); //Default value from schema

//Author was created automatically using the schema
var author = post.get('author');
console.log(author.get('name'); //Raghu

```
Cool! isn't it? If defaults and schema are both present, default values from the backbone model take precedence over the schema defaults.
Now, lets complicate this a bit further.

```javascript
var Comment = Backbone.Model.extend({

schema: {
msg: {
type: 'string',
default: 'Default comment msg'
}
}

});

var Comments = Backbone.Collection.extend({
model: Comment
});

var PostWithSchema = Backbone.Model.extend({

schema: {
title: {
type: 'string',
default: 'Default value from schema'
},
body: {
type: 'string',
default: 'Default body'
},
author: {
type: 'related',
_constructor: User
}
comments: {
type: 'related',
_constructor: Comments
}
}
});


BackboneFactory.define('post_with_schema', PostWithSchema);
BackboneFactory.define('comments', Comments, 2);

var post = BackboneFactory.create('post_with_schema');
var comments = post.get('comments');
console.log(comments.length) //gives a value of 2
```
Its very easy to change the size of the collection of comments

### Defining Factories with defaults
```javascript
var post = BackboneFactory.create('post_with_schema', function(){
return { comments: BackboneFactory.create_collection('comments', 4) };
});
var comments = post.get('comments');
console.log(comments.length) //gives a value of 4

```
If a comments BackboneFactory is not defined, an empty collection of comments is created when a post is created.

### Dynamically generating attribute values: Sequences
Often, we'd like to generate unique values for model attributes when new objects are created. For example, we may want the username/email of a user to be always unique. We can use sequences for this.
```javascript
var userFactory = BackboneFactory.define('user', User, function(){
return {
name : 'Backbone User',
email: BackboneFactory.next('person_email')
};
}
);
BackboneFactory.define_sequence('email', function(n){
return "person"+n+"@example.com";
});

var email = BackboneFactory.next('email') // [email protected]
```

### Overriding defaults when creating objects
### Defining Factories with Sequences

```javascript
var userWithEmail = BackboneFactory.create('user', function(){
return {
email: '[email protected]'
};
});
```
BackboneFactory.define('user', User, function(){
return {
name : 'Backbone User',
email: BackboneFactory.next('email')
};
});
var user = BackboneFactory.create('user);
console.log(user.get('email') // [email protected]
```

Contributing
------------
Expand Down Expand Up @@ -129,7 +245,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.




104 changes: 91 additions & 13 deletions public/javascripts/backbone-factory.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
/*global BackboneFactory */

// Backbone Factory JS
// https://github.com/SupportBee/Backbone-Factory
// https://github.com/Aplopio/Backbone-Factory

(function(){

function get_factory_name(klass){
var keys = _.keys(BackboneFactory.model_klasses),
values = _.values(BackboneFactory.model_klasses);
return keys[values.indexOf(klass)];
}

function get_collection_name(klass){
var keys = _.keys(BackboneFactory.collection_klasses),
values = _.values(BackboneFactory.collection_klasses);
return keys[values.indexOf(klass)];
}

window.BackboneFactory = {

factories: {},
model_klasses: {},

collections: {},
collection_klasses: {},

sequences: {},

define: function(factory_name, klass, defaults){
Expand All @@ -14,40 +34,98 @@
throw "Factory name should not contain spaces or other funky characters";
}

if(defaults === undefined) defaults = function(){return {}};
if(defaults === undefined) defaults = function(){return {};}

// The object creator
this.factories[factory_name] = function(options){
if(options === undefined) options = function(){return {}};
arguments = _.extend({}, {id: BackboneFactory.next("_" + factory_name + "_id")}, defaults.call(), options.call());
return new klass(arguments);
this.model_klasses[factory_name] = klass;
this.factories[factory_name] = function(attrs_generator, options){
if(attrs_generator === undefined) attrs_generator = function(){return {};};

var schema = klass.prototype.schema,
default_vals = _.result(klass.prototype, 'defaults') || {};

if(schema){
default_vals = _(default_vals).clone();

_(schema).each(function(attr, key){
if(_(attr).has('default') && default_vals[key] === undefined) {
default_vals[key] = attr.default;
}
});

_(schema).each(function(attr, key){
var name;
if(attr.type == 'related' && attr._constructor && !(key in default_vals)){
if(name = get_factory_name(attr._constructor)){
default_vals[key] = BackboneFactory.create(name);
}else if(name = get_collection_name(attr._constructor)){
default_vals[key] = BackboneFactory.create_collection(name);
}else{
default_vals[key] = new attr._constructor();
}
}
});

}

var attributes = _.extend(
{ id: BackboneFactory.next("_" + factory_name + "_id") },
default_vals,
defaults.call(),
attrs_generator.call()
);
return new klass(attributes, options);
};

// Lets define a sequence for id
BackboneFactory.define_sequence("_"+ factory_name +"_id", function(n){
return n
return n;
});
},

create: function(factory_name, options){
create: function(factory_name, attrs_generator, options){
if(this.factories[factory_name] === undefined){
throw "Factory with name " + factory_name + " does not exist";
}
return this.factories[factory_name].apply(null, [options]);
return this.factories[factory_name].call(null, attrs_generator, options);
},

define_sequence: function(sequence_name, callback){
this.sequences[sequence_name] = {}
this.sequences[sequence_name] = {};
this.sequences[sequence_name]['counter'] = 0;
this.sequences[sequence_name]['callback'] = callback;
this.sequences[sequence_name]['callback'] = callback;
},

next: function(sequence_name){
if(this.sequences[sequence_name] === undefined){
throw "Sequence with name " + sequence_name + " does not exist";
}
this.sequences[sequence_name]['counter'] += 1;
return this.sequences[sequence_name]['callback'].apply(null, [this.sequences[sequence_name]['counter']]); //= callback;
return this.sequences[sequence_name]['callback'].apply(null, [this.sequences[sequence_name]['counter']]); //= callback;
},

define_collection: function(collection_name, klass, default_size, default_options){
var factory_name = get_factory_name(klass.prototype.model);

this.collection_klasses[collection_name] = klass;
this.collections[collection_name] = function(size, attrs_generator, options){
var models = [];
if(typeof size!='number') size = default_size;
options = options || default_options || {};

for(var i=0; i<size; i++){
models.push(BackboneFactory.create(factory_name, attrs_generator));
}
return new klass(models, options);
};
},

create_collection: function(collection_name, size, attrs_generator, options){
if(this.collections[collection_name] === undefined){
throw "Collection with name " + collection_name + " does not exist";
}
return this.collections[collection_name].call(this, size, attrs_generator, options);
}
}

};
})();
Loading