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';
|
import ghostPaths from 'ghost/utils/ghost-paths';
|
||||||
|
|
||||||
// export default DS.FixtureAdapter.extend({});
|
|
||||||
|
|
||||||
var ApplicationAdapter = DS.RESTAdapter.extend({
|
var ApplicationAdapter = DS.RESTAdapter.extend({
|
||||||
host: window.location.origin,
|
host: window.location.origin,
|
||||||
namespace: ghostPaths().apiRoot.slice(1),
|
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) {
|
createRecord: function (store, type, record) {
|
||||||
var data = {},
|
var data = {},
|
||||||
serializer = store.serializerFor(type.typeKey),
|
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 }),
|
created_by: DS.belongsTo('user', { async: true }),
|
||||||
updated_at: DS.attr('moment-date'),
|
updated_at: DS.attr('moment-date'),
|
||||||
updated_by: DS.belongsTo('user', { async: true }),
|
updated_by: DS.belongsTo('user', { async: true }),
|
||||||
|
roles: DS.hasMany('role', { embedded: 'always' }),
|
||||||
|
|
||||||
saveNewPassword: function () {
|
saveNewPassword: function () {
|
||||||
var url = this.get('ghostPaths.url').api('users', 'password');
|
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,
|
id: params.post_id,
|
||||||
status: 'all',
|
status: 'all',
|
||||||
staticPages: 'all',
|
staticPages: 'all',
|
||||||
include: 'tags'
|
|
||||||
}).then(function (records) {
|
}).then(function (records) {
|
||||||
var post = records.get('firstObject');
|
var post = records.get('firstObject');
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import loadingIndicator from 'ghost/mixins/loading-indicator';
|
||||||
var paginationSettings = {
|
var paginationSettings = {
|
||||||
status: 'all',
|
status: 'all',
|
||||||
staticPages: 'all',
|
staticPages: 'all',
|
||||||
include: 'tags',
|
|
||||||
page: 1
|
page: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ var PostsIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixi
|
||||||
return this.store.find('post', {
|
return this.store.find('post', {
|
||||||
status: 'all',
|
status: 'all',
|
||||||
staticPages: 'all',
|
staticPages: 'all',
|
||||||
include: 'tags'
|
|
||||||
}).then(function (records) {
|
}).then(function (records) {
|
||||||
var post = records.get('firstObject');
|
var post = records.get('firstObject');
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ var PostsPostRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin
|
||||||
id: params.post_id,
|
id: params.post_id,
|
||||||
status: 'all',
|
status: 'all',
|
||||||
staticPages: 'all',
|
staticPages: 'all',
|
||||||
include: 'tags'
|
|
||||||
}).then(function (records) {
|
}).then(function (records) {
|
||||||
var post = records.get('firstObject');
|
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