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:
parent
95d3925c3e
commit
0bfe99af72
11 changed files with 160 additions and 8 deletions
|
@ -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),
|
||||
|
|
70
ghost/admin/adapters/embedded-relation-adapter.js
Normal file
70
ghost/admin/adapters/embedded-relation-adapter.js
Normal 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;
|
|
@ -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),
|
||||
|
|
38
ghost/admin/adapters/user.js
Normal file
38
ghost/admin/adapters/user.js
Normal 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;
|
15
ghost/admin/models/role.js
Normal file
15
ghost/admin/models/role.js
Normal 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;
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import loadingIndicator from 'ghost/mixins/loading-indicator';
|
|||
var paginationSettings = {
|
||||
status: 'all',
|
||||
staticPages: 'all',
|
||||
include: 'tags',
|
||||
page: 1
|
||||
};
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
34
ghost/admin/serializers/user.js
Normal file
34
ghost/admin/serializers/user.js
Normal 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;
|
Loading…
Add table
Reference in a new issue