mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-22 02:11:44 +03:00
8d8dbb7479
refs https://github.com/TryGhost/Ghost/pull/9426/files - when using `?include=foo` for a related model that itself normally has embedded relationships we need to add the nested relationship to the `include` param - eg. `/posts?include=authors,authors.roles` - this is necessary to ensure we don't introduce partial models into the Ember Data store by missing the embedded relationships on some requests
142 lines
4.9 KiB
JavaScript
142 lines
4.9 KiB
JavaScript
import BaseAdapter from 'ghost-admin/adapters/base';
|
|
import {get} from '@ember/object';
|
|
import {isNone} from '@ember/utils';
|
|
|
|
// EmbeddedRelationAdapter will augment the query object in calls made to
|
|
// DS.Store#findRecord, findAll, query, and queryRecord with the correct "includes"
|
|
// (?include=relatedType) by introspecting on the provided subclass of the DS.Model.
|
|
// In cases where there is no query object (DS.Model#save, or simple finds) the URL
|
|
// that is built will be augmented with ?include=... where appropriate.
|
|
//
|
|
// Example:
|
|
// If a model has an embedded hasMany relation, the related type will be included:
|
|
// roles: DS.hasMany('role', { embedded: 'always' }) => ?include=roles
|
|
|
|
export default BaseAdapter.extend({
|
|
find(store, type, id, snapshot) {
|
|
return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'find'), 'GET');
|
|
},
|
|
|
|
findRecord(store, type, id, snapshot) {
|
|
return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'), 'GET');
|
|
},
|
|
|
|
findAll(store, type, sinceToken) {
|
|
let query, url;
|
|
|
|
if (sinceToken) {
|
|
query = {since: sinceToken};
|
|
}
|
|
|
|
url = this.buildIncludeURL(store, type.modelName, null, null, 'findAll');
|
|
|
|
return this.ajax(url, 'GET', {data: query});
|
|
},
|
|
|
|
query(store, type, query) {
|
|
return this._super(store, type, this.buildQuery(store, type.modelName, query));
|
|
},
|
|
|
|
queryRecord(store, type, query) {
|
|
return this._super(store, type, this.buildQuery(store, type.modelName, query));
|
|
},
|
|
|
|
createRecord(store, type, snapshot) {
|
|
return this.saveRecord(store, type, snapshot, {method: 'POST'}, 'createRecord');
|
|
},
|
|
|
|
updateRecord(store, type, snapshot) {
|
|
let options = {
|
|
method: 'PUT',
|
|
id: get(snapshot, 'id')
|
|
};
|
|
|
|
return this.saveRecord(store, type, snapshot, options, 'updateRecord');
|
|
},
|
|
|
|
saveRecord(store, type, snapshot, options, requestType) {
|
|
let _options = options || {};
|
|
let url = this.buildIncludeURL(store, type.modelName, _options.id, snapshot, requestType);
|
|
let payload = this.preparePayload(store, type, snapshot);
|
|
|
|
return this.ajax(url, _options.method, payload);
|
|
},
|
|
|
|
preparePayload(store, type, snapshot) {
|
|
let serializer = store.serializerFor(type.modelName);
|
|
let payload = {};
|
|
|
|
serializer.serializeIntoHash(payload, type, snapshot);
|
|
|
|
return {data: payload};
|
|
},
|
|
|
|
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
|
|
let includes = this.getEmbeddedRelations(store, modelName);
|
|
let url = this.buildURL(modelName, id, snapshot, requestType, query);
|
|
|
|
if (includes.length) {
|
|
url += `?include=${includes.join(',')}`;
|
|
}
|
|
|
|
return url;
|
|
},
|
|
|
|
buildQuery(store, modelName, options) {
|
|
let deDupe = {};
|
|
let toInclude = this.getEmbeddedRelations(store, modelName);
|
|
let query = options || {};
|
|
|
|
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 = {};
|
|
query.id = options;
|
|
query.include = toInclude.join(',');
|
|
} else if (typeof options === 'object' || isNone(options)) {
|
|
// 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.
|
|
query = query || {};
|
|
toInclude = toInclude.concat(query.include ? query.include.split(',') : []);
|
|
|
|
toInclude.forEach((include) => {
|
|
deDupe[include] = true;
|
|
});
|
|
|
|
query.include = Object.keys(deDupe).join(',');
|
|
}
|
|
}
|
|
|
|
return query;
|
|
},
|
|
|
|
getEmbeddedRelations(store, modelName) {
|
|
let model = store.modelFor(modelName);
|
|
let ret = [];
|
|
let embedded = [];
|
|
|
|
// 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((name, meta) => {
|
|
if (
|
|
meta.kind === 'hasMany'
|
|
&& Object.prototype.hasOwnProperty.call(meta.options, 'embedded')
|
|
&& meta.options.embedded === 'always'
|
|
) {
|
|
ret.push(name);
|
|
embedded.push([name, meta.type]);
|
|
}
|
|
});
|
|
|
|
embedded.forEach(([relName, modelName]) => {
|
|
this.getEmbeddedRelations(store, modelName).forEach((name) => {
|
|
ret.push(`${relName}.${name}`);
|
|
});
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
});
|