Last active
September 5, 2016 16:25
-
-
Save jasoncrawford/7060426 to your computer and use it in GitHub Desktop.
Simple relations for Backbone models. Not as full-featured as Backbone-relational (http://backbonerelational.org/) or Supermodel (http://pathable.github.io/supermodel/), but pretty lightweight and concise. Disclaimer: I excerpted this from some other code I wrote and haven't tested it independently. Use at your own risk
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var relationEvents = ['add', 'change', 'remove', 'reset', 'sort', 'destroy', 'request', 'sync']; | |
var Model = exports.Model = Backbone.Model.extend({ | |
hasMany: { | |
// Subclasses can override to set relations like this: | |
// | |
// key: function () { return new Collection([], options); }, | |
}, | |
hasOne: { | |
// Subclasses can override to set relations like this: | |
// | |
// key: function (attrs) { return new Model(attrs, options); }, | |
// | |
// Note that we don't really care about the difference between hasOne and belongsTo. If you want | |
// a single model as the value of this key, put it here; if you want a collection, put it under | |
// hasMany. | |
}, | |
allRelationKeys: function () { | |
return Object.keys(this.hasMany).concat(Object.keys(this.hasOne)); | |
}, | |
// Updates the given hasMany relation with the given models, creating a collection for it if | |
// needed. | |
updateHasManyRelation: function (key, models, options) { | |
var collection = this.get(key); | |
if (!collection) { | |
var constructor = _.bind(this.hasMany[key], this); | |
collection = this.attributes[key] = constructor(); | |
collection.parent = this; | |
this.listenToRelation(key, collection); | |
} | |
if (models instanceof Collection) models = models.models; | |
collection.reset(models, _.extend({silent: false}, options)); | |
}, | |
// Updates the given hasOne relation with the given attributes. If the current value of the key is | |
// a model with the same ID as the incoming model, updates the current model in-place with the new | |
// attributes. Otherwise, creates a new model. | |
updateHasOneRelation: function (key, attributes, options) { | |
var model = this.get(key); | |
if (attributes instanceof Model) attributes = attributes.attributes; | |
if (model && model.id === attributes[this.idAttribute]) { | |
model.set(attributes); | |
} else { | |
var constructor = _.bind(this.hasOne[key], this); | |
model = constructor(attributes); | |
this.listenToRelation(key, model); | |
} | |
return model; | |
}, | |
// Listens to events on a relation and proxy them through. E.g., if you have a collection of | |
// records, and it fires an 'add' event, refire that as 'records:add'. | |
listenToRelation: function (key, relation) { | |
var self = this; | |
var callback = this.onRelationEvent; | |
relationEvents.forEach(function (event) { | |
relation.on(event, callback, {parent: self, key: key, event: event}); | |
}) | |
}, | |
// Callback triggered on a relation event, used by listenToRelation | |
onRelationEvent: function () { | |
var event = this.key + ':' + this.event; | |
var args = _.toArray(arguments); | |
args.unshift(event); | |
this.parent.trigger.apply(this.parent, args); | |
}, | |
// Overrides the Backbone.Model#set method to deal with relations, using the update*Relation | |
// methods. | |
set: function (key, val, options) { | |
if (key == null) return this; | |
// Handle both `"key", value` and `{key: value}` -style arguments. | |
var attrs; | |
if (typeof key === 'object') { | |
attrs = _.clone(key); | |
options = val; | |
} else { | |
(attrs = {})[key] = val; | |
} | |
var self = this; | |
Object.keys(this.hasMany).forEach(function (key) { | |
if (key in attrs) { | |
self.updateHasManyRelation(key, attrs[key], options); | |
delete attrs[key]; | |
} | |
}); | |
Object.keys(this.hasOne).forEach(function (key) { | |
if (key in attrs) { | |
attrs[key] = self.updateHasOneRelation(key, attrs[key], options); | |
} | |
}); | |
return Backbone.Model.prototype.set.call(this, attrs, options); | |
}, | |
// Overrides Backbone.Model#toJSON to deal with relations. Calls toJSON recursively on the | |
// child collections and adds them to the resulting hash. | |
toJSON: function () { | |
var attributes = _.clone(this.attributes); | |
this.allRelationKeys().forEach(function (key) { | |
if (key in attributes) { | |
attributes[key] = attributes[key].toJSON(); | |
} | |
}) | |
return attributes; | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var User = Model.extend({ | |
urlRoot: function () { | |
// Let the collection, if any, define the create URL for new models | |
if (this.isNew() && this.collection && this.collection.url) return null; | |
return '/users'; | |
} | |
}) | |
var Users = Backbone.Collection.extend({ | |
model: User | |
}) | |
var Group = Model.extend({ | |
hasMany: { | |
users: function () { return new Users([], {url: function () { return this.parent.url() + '/users'; }}); }, | |
}, | |
hasOne: { | |
owner: function (attrs) { return new User(attrs); }, | |
}, | |
}) | |
// Now you can do: | |
var group = new Group({id: '1234'}); | |
group.get('users'); // --> undefined, not yet populated | |
group.get('owner'); // --> ditto | |
group.fetch(); // assume this returns JSON including { owner: {id: 1, name: ...}, users: [{id: 1, name: ...}, {id: 2, name: ...}]} | |
group.get('users'); // --> a collection of User models | |
group.get('owner'); // --> a User model | |
var user = group.get('users').create({name: 'John'}); // does a POST to /groups/1234/users to create the user in the group | |
user.save({...}); // now that the user is created, this will PUT directly to /users/:id | |
// To get notified when a user is added to the group: | |
group.listenTo('users:add', function (user, collection) { ... }); | |
// To get notified when the owner attributes change: | |
group.listenTo('owner:change', function (user), { ... }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment