class BaseModel extends Backbone.Model { // Return a model class for a given resource name, based on our mapping // convention: // 'foos' -> App.Model.Foo static modelClassForResource(resourceName) { return App.Models[_.str.classify(_.singularize(resourceName))] } // Return a model with intelligent defaults set for a given resource name i.e. // automatically set 'type' and 'urlRoot' based on the name static forResource(resourceName) { return class extends BaseModel { constructor(attributes, options) { super(attributes, options) //jsonapi-resources dasherizes multi-word resources by default this.urlRoot = `/${_.str.dasherize(resourceName)}` } defaults() { return { type: resourceName } } } } // Create a model class from a JSON API link (with the right URL) static classFromLink(related) { return class extends BaseModel { url() { return `${related.links.self}.json` } toJSON() { return {data: {id: related.data.id, type: related.data.type}} } } } // Create a model instance from a link (useful for saving updates to the // link) static fromLink(related) { return new (this.classFromLink(related))(related) } // Extend Backbone base behavior with JSON API behavior. In this case, // warn if the model does not include a type attribute, as this is required // in JSON API constructor(attributes, options) { var parsedAttrs if(attributes) { parsedAttrs = _.chain(attributes) .extend(attributes.attributes) .compactObject() .value() } super(parsedAttrs, options) if(!this.get('type')) { console.warn('type attribute of model is required') } } formatPostfix(opts) { if(opts.format == 'html') { return '' } else { return `.${opts.format}` } } // Add .json to the end of the base URL to avoid any issues with the browser // history (otherwise, certain browsers will occasionally dump out raw JSON) // when you use the back arrow url(opts) { opts = opts || {format: 'json'} var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError() if (this.isNew()) return base + this.formatPostfix(opts) return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id) + this.formatPostfix(opts) } // Map through the relationship object and remove 'cid' field and remove // relationship if its empty relationshipData() { return _.mapObject(this.get('relationships'), (relationship) => { if (!relationship.data) return return _.extend(relationship, { data: _.mapIfArray( relationship.data, (data) => _.omit(data, 'cid') ) }) }) } // Wrap the JSON representation in 'data' to make JSON API happy toJSON(options) { var key = {id: this.id, type: this.get('type'), relationships: this.relationshipData()} var inner = _(super.toJSON(options)).omit('links', 'id', 'type', 'attributes', 'relationships') return { data: _.extend({attributes: inner}, key) } } sync(method, model, options) { if(!options.contentType) { options.contentType = 'application/vnd.api+json' } super.sync(method, model, options) } }