0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Extend adapter to support automatic includes

Closes #3325
- Add Roles model and add hasMany roles to User model.
- Add EmbeddedRelationAdapter that will automatically include
  hasMany relations in calls to the API.
- UserAdapter and PostAdapter now extend EmbeddedRelationAdapter
  and all explicit includes from store.find() have been removed.
This commit is contained in:
Jason Williams 2014-07-20 21:48:24 +00:00
parent 95d3925c3e
commit 0bfe99af72
11 changed files with 160 additions and 8 deletions

View file

@ -1,7 +1,5 @@
import ghostPaths from 'ghost/utils/ghost-paths';
// export default DS.FixtureAdapter.extend({});
var ApplicationAdapter = DS.RESTAdapter.extend({
host: window.location.origin,
namespace: ghostPaths().apiRoot.slice(1),

View file

@ -0,0 +1,70 @@
import ApplicationAdapter from 'ghost/adapters/application';
// EmbeddedRelationAdapter will augment the query object in calls made to
// DS.Store#find, findQuery, and findAll with the correct "includes"
// (?include=relatedType) by introspecting on the provided subclass of the DS.Model.
//
// Example:
// If a model has an embedded hasMany relation, the related type will be included:
// roles: DS.hasMany('role', { embedded: 'always' }) => ?include=roles
var EmbeddedRelationAdapter = ApplicationAdapter.extend({
find: function (store, type, id) {
return this.findQuery(store, type, this.buildQuery(store, type, id));
},
findQuery: function (store, type, query) {
return this._super(store, type, this.buildQuery(store, type, query));
},
findAll: function (store, type, sinceToken) {
return this.findQuery(store, type, this.buildQuery(store, type, sinceToken));
},
buildQuery: function (store, type, options) {
var model,
toInclude = [],
query = {},
deDupe = {};
// Get the class responsible for creating records of this type
model = store.modelFor(type);
// Iterate through the model's relationships and build a list
// of those that need to be pulled in via "include" from the API
model.eachRelationship(function (name, meta) {
if (meta.kind === 'hasMany' &&
Object.prototype.hasOwnProperty.call(meta.options, 'embedded') &&
meta.options.embedded === 'always') {
toInclude.push(name);
}
});
if (toInclude.length) {
// If this is a find by id, build a query object and attach the includes
if (typeof options === 'string' || typeof options === 'number') {
query.id = options;
query.include = toInclude.join(',');
}
// If this is a find all (no existing query object) build one and attach
// the includes.
// If this is a find with an existing query object then merge the includes
// into the existing object. Existing properties and includes are preserved.
else if (typeof options === 'object' || Ember.isNone(options)) {
query = options || query;
toInclude = toInclude.concat(query.include ? query.include.split(',') : []);
toInclude.forEach(function (include) {
deDupe[include] = true;
});
query.include = Object.keys(deDupe).join(',');
}
}
return query;
}
});
export default EmbeddedRelationAdapter;

View file

@ -1,6 +1,6 @@
import ApplicationAdapter from 'ghost/adapters/application';
import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter';
var PostAdapter = ApplicationAdapter.extend({
var PostAdapter = EmbeddedRelationAdapter.extend({
createRecord: function (store, type, record) {
var data = {},
serializer = store.serializerFor(type.typeKey),

View file

@ -0,0 +1,38 @@
import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter';
var UserAdapter = EmbeddedRelationAdapter.extend({
createRecord: function (store, type, record) {
var data = {},
serializer = store.serializerFor(type.typeKey),
url = this.buildURL(type.typeKey);
// Ask the API to include full role objects in its response
url += '?include=roles';
// Use the UserSerializer to transform the model back into
// an array of user objects like the API expects
serializer.serializeIntoHash(data, type, record);
// Use the url from the ApplicationAdapter's buildURL method
return this.ajax(url, 'POST', { data: data });
},
updateRecord: function (store, type, record) {
var data = {},
serializer = store.serializerFor(type.typeKey),
id = Ember.get(record, 'id'),
url = this.buildURL(type.typeKey, id);
// Ask the API to include full role objects in its response
url += '?include=roles';
// Use the UserSerializer to transform the model back into
// an array of user objects like the API expects
serializer.serializeIntoHash(data, type, record);
// Use the url from the ApplicationAdapter's buildURL method
return this.ajax(url, 'PUT', { data: data });
}
});
export default UserAdapter;

View file

@ -0,0 +1,15 @@
var Role = DS.Model.extend({
uuid: DS.attr('string'),
name: DS.attr('string'),
description: DS.attr('string'),
created_at: DS.attr('moment-date'),
created_by: DS.belongsTo('user', { async: true }),
updated_at: DS.attr('moment-date'),
updated_by: DS.belongsTo('user', { async: true }),
lowerCaseName: Ember.computed('name', function () {
return this.get('name').toLocaleLowerCase();
})
});
export default Role;

View file

@ -23,6 +23,7 @@ var User = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
created_by: DS.belongsTo('user', { async: true }),
updated_at: DS.attr('moment-date'),
updated_by: DS.belongsTo('user', { async: true }),
roles: DS.hasMany('role', { embedded: 'always' }),
saveNewPassword: function () {
var url = this.get('ghostPaths.url').api('users', 'password');

View file

@ -24,7 +24,6 @@ var EditorEditRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixi
id: params.post_id,
status: 'all',
staticPages: 'all',
include: 'tags'
}).then(function (records) {
var post = records.get('firstObject');

View file

@ -5,7 +5,6 @@ import loadingIndicator from 'ghost/mixins/loading-indicator';
var paginationSettings = {
status: 'all',
staticPages: 'all',
include: 'tags',
page: 1
};

View file

@ -8,7 +8,6 @@ var PostsIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixi
return this.store.find('post', {
status: 'all',
staticPages: 'all',
include: 'tags'
}).then(function (records) {
var post = records.get('firstObject');

View file

@ -24,7 +24,6 @@ var PostsPostRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin
id: params.post_id,
status: 'all',
staticPages: 'all',
include: 'tags'
}).then(function (records) {
var post = records.get('firstObject');

View file

@ -0,0 +1,34 @@
import ApplicationSerializer from 'ghost/serializers/application';
var UserSerializer = ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
roles: { embedded: 'always' }
},
extractSingle: function (store, primaryType, payload) {
var root = this.keyForAttribute(primaryType.typeKey),
pluralizedRoot = Ember.String.pluralize(primaryType.typeKey);
payload[root] = payload[pluralizedRoot][0];
delete payload[pluralizedRoot];
return this._super.apply(this, arguments);
},
keyForAttribute: function (attr) {
return attr;
},
keyForRelationship: function (relationshipName) {
// this is a hack to prevent Ember-Data from deleting our `tags` reference.
// ref: https://github.com/emberjs/data/issues/2051
// @TODO: remove this once the situation becomes clearer what to do.
if (relationshipName === 'roles') {
return 'role';
}
return relationshipName;
}
});
export default UserSerializer;