diff --git a/core/client/adapters/application.js b/core/client/adapters/application.js index 23b5a3a9a3..af47f0c087 100644 --- a/core/client/adapters/application.js +++ b/core/client/adapters/application.js @@ -1,44 +1,5 @@ -import ghostPaths from 'ghost/utils/ghost-paths'; +import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter'; -var ApplicationAdapter = DS.RESTAdapter.extend({ - host: window.location.origin, - namespace: ghostPaths().apiRoot.slice(1), - - findQuery: function (store, type, query) { - var id; - - if (query.id) { - id = query.id; - delete query.id; - } - - return this.ajax(this.buildURL(type.typeKey, id), 'GET', {data: query}); - }, - - buildURL: function (type, id) { - // Ensure trailing slashes - var url = this._super(type, id); - - if (url.slice(-1) !== '/') { - url += '/'; - } - - return url; - }, - - // Override deleteRecord to disregard the response body on 2xx responses. - // This is currently needed because the API is returning status 200 along - // with the JSON object for the deleted entity and Ember expects an empty - // response body for successful DELETEs. - // Non-2xx (failure) responses will still work correctly as Ember will turn - // them into rejected promises. - deleteRecord: function () { - var response = this._super.apply(this, arguments); - - return response.then(function () { - return null; - }); - } -}); +var ApplicationAdapter = EmbeddedRelationAdapter.extend(); export default ApplicationAdapter; diff --git a/core/client/adapters/base.js b/core/client/adapters/base.js new file mode 100644 index 0000000000..82f4063876 --- /dev/null +++ b/core/client/adapters/base.js @@ -0,0 +1,44 @@ +import ghostPaths from 'ghost/utils/ghost-paths'; + +var BaseAdapter = DS.RESTAdapter.extend({ + host: window.location.origin, + namespace: ghostPaths().apiRoot.slice(1), + + findQuery: function (store, type, query) { + var id; + + if (query.id) { + id = query.id; + delete query.id; + } + + return this.ajax(this.buildURL(type.typeKey, id), 'GET', {data: query}); + }, + + buildURL: function (type, id) { + // Ensure trailing slashes + var url = this._super(type, id); + + if (url.slice(-1) !== '/') { + url += '/'; + } + + return url; + }, + + // Override deleteRecord to disregard the response body on 2xx responses. + // This is currently needed because the API is returning status 200 along + // with the JSON object for the deleted entity and Ember expects an empty + // response body for successful DELETEs. + // Non-2xx (failure) responses will still work correctly as Ember will turn + // them into rejected promises. + deleteRecord: function () { + var response = this._super.apply(this, arguments); + + return response.then(function () { + return null; + }); + } +}); + +export default BaseAdapter; diff --git a/core/client/adapters/embedded-relation-adapter.js b/core/client/adapters/embedded-relation-adapter.js index 19f6975d15..49d0662b4e 100644 --- a/core/client/adapters/embedded-relation-adapter.js +++ b/core/client/adapters/embedded-relation-adapter.js @@ -1,16 +1,18 @@ -import ApplicationAdapter from 'ghost/adapters/application'; +import BaseAdapter from 'ghost/adapters/base'; // 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. +// 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 -var EmbeddedRelationAdapter = ApplicationAdapter.extend({ +var EmbeddedRelationAdapter = BaseAdapter.extend({ find: function (store, type, id) { - return this.findQuery(store, type, this.buildQuery(store, type, id)); + return this.ajax(this.buildIncludeURL(store, type, id), 'GET'); }, findQuery: function (store, type, query) { @@ -18,31 +20,66 @@ var EmbeddedRelationAdapter = ApplicationAdapter.extend({ }, findAll: function (store, type, sinceToken) { - return this.findQuery(store, type, this.buildQuery(store, type, sinceToken)); + var query = {}; + + if (sinceToken) { + query.since = sinceToken; + } + + return this.findQuery(store, type, query); + }, + + createRecord: function (store, type, record) { + return this.saveRecord(store, type, record, {method: 'POST'}); + }, + + updateRecord: function (store, type, record) { + var options = { + method: 'PUT', + id: Ember.get(record, 'id') + }; + + return this.saveRecord(store, type, record, options); + }, + + saveRecord: function (store, type, record, options) { + options = options || {}; + + var url = this.buildIncludeURL(store, type, options.id), + payload = this.preparePayload(store, type, record); + + return this.ajax(url, options.method, payload); + }, + + preparePayload: function (store, type, record) { + var serializer = store.serializerFor(type.typeKey), + payload = {}; + + serializer.serializeIntoHash(payload, type, record); + + return {data: payload}; + }, + + buildIncludeURL: function (store, type, id) { + var url = this.buildURL(type.typeKey, id), + includes = this.getEmbeddedRelations(store, type); + + if (includes.length) { + url += '?include=' + includes.join(','); + } + + return url; }, buildQuery: function (store, type, options) { - var model, - toInclude = [], - query = {}, + var toInclude = this.getEmbeddedRelations(store, type), + query = options || {}, 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 = {}; query.id = options; query.include = toInclude.join(','); } else if (typeof options === 'object' || Ember.isNone(options)) { @@ -50,7 +87,7 @@ var EmbeddedRelationAdapter = ApplicationAdapter.extend({ // 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 = options || query; + query = query || {}; toInclude = toInclude.concat(query.include ? query.include.split(',') : []); toInclude.forEach(function (include) { @@ -62,6 +99,23 @@ var EmbeddedRelationAdapter = ApplicationAdapter.extend({ } return query; + }, + + getEmbeddedRelations: function (store, type) { + var model = store.modelFor(type), + ret = []; + + // 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') { + ret.push(name); + } + }); + + return ret; } }); diff --git a/core/client/adapters/post.js b/core/client/adapters/post.js deleted file mode 100644 index b528d39269..0000000000 --- a/core/client/adapters/post.js +++ /dev/null @@ -1,37 +0,0 @@ -import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter'; - -var PostAdapter = EmbeddedRelationAdapter.extend({ - createRecord: function (store, type, record) { - var data = {}, - serializer = store.serializerFor(type.typeKey), - url = this.buildURL(type.typeKey); - - // make the server return with the tags embedded - url = url + '?include=tags'; - - // use the PostSerializer to transform the model back into - // an array with a post object like the API expects - serializer.serializeIntoHash(data, type, record); - - 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); - - // make the server return with the tags embedded - url = url + '?include=tags'; - - // use the PostSerializer to transform the model back into - // an array of posts objects like the API expects - serializer.serializeIntoHash(data, type, record); - - // use the ApplicationAdapter's buildURL method - return this.ajax(url, 'PUT', {data: data}); - } -}); - -export default PostAdapter; diff --git a/core/client/adapters/user.js b/core/client/adapters/user.js index d67b7c435a..4039d59fa2 100644 --- a/core/client/adapters/user.js +++ b/core/client/adapters/user.js @@ -1,43 +1,8 @@ -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}); - }, +import ApplicationAdapter from 'ghost/adapters/application'; +var UserAdapter = ApplicationAdapter.extend({ find: function (store, type, id) { - var url = this.buildQuery(store, type, id); - url.status = 'all'; - return this.findQuery(store, type, url); + return this.findQuery(store, type, {id: id, status: 'all'}); } });