diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 85bf2f829..8a072b077 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -1,7 +1,7 @@ import RESTAdapter from '@ember-data/adapter/rest'; import AdapterBuildURLMixin from '../mixins/adapter-build-url'; import config from 'ember-get-config'; -import { get } from '@ember/object'; +import { get, getWithDefault } from '@ember/object'; import { InvalidError } from '@ember-data/adapter/error'; import { dasherize } from '@ember/string'; import { pluralize } from 'ember-inflector'; @@ -58,6 +58,40 @@ export default class ApplicationAdapter extends RESTAdapter.extend( // =methods + /** + * Prepends a scope to the URL prefix, if a scope is passed via the + * snapshot's `adapterOptions.scope_id` field. + * @override + * @param {string} path + * @param {string} parentURL + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} + */ + urlPrefix(path, parentURL, modelName, id, snapshot={}) { + const prefix = super.urlPrefix(...arguments); + let scopePath = getWithDefault(snapshot, 'adapterOptions.scope_id', ''); + // Ensure a slash is added between prefix + scope path if needed. + if (scopePath && prefix.charAt(prefix.length - 1) !== '/') { + scopePath = `/${scopePath}`; + } + return `${prefix}${scopePath}`; + } + + /** + * Appends a custom method after a colon, if a method is passed via the + * snapshot's `adapterOptions.method` field. + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} + */ + urlSuffix(modelName, id, snapshot={}) { + const method = getWithDefault(snapshot, 'adapterOptions.method', ''); + return method ? `:${method}` : ''; + } + /** * Transforms the type to a dasherized string used in our API paths. * diff --git a/addons/api/addon/mixins/adapter-build-url.js b/addons/api/addon/mixins/adapter-build-url.js index 8ff6d3474..264364d0d 100644 --- a/addons/api/addon/mixins/adapter-build-url.js +++ b/addons/api/addon/mixins/adapter-build-url.js @@ -16,36 +16,35 @@ export default Mixin.create({ // =methods + /** + * This override behaves identically to the default method by default. + * It exists only to document the additional paramters to the method. + * @override + * @param {string} path + * @param {string} parentURL + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} urlPrefix + */ + urlPrefix(/* path, parentURL, modelName, id, snapshot */) { + // What's this super syntax? + // Ember mixins are still based on the older Ember Object, rather than + // native ES classes. Thus we have to call super in this strange way. + // https://blog.emberjs.com/2019/01/26/emberjs-native-class-update-2019-edition.html + return this._super(...arguments); + }, - /** - * This override behaves identically to the default method by default. - * It exists only to document the additional paramters to the method. - * @override - * @param {string} path - * @param {string} parentURL - * @param {string} modelName - * @param {string} id - * @param {object} snapshot - * @return {string} urlPrefix - */ - urlPrefix(/* path, parentURL, modelName, id, snapshot */) { - // What's this super syntax? - // Ember mixins are still based on the older Ember Object, rather than - // native ES classes. Thus we have to call super in this strange way. - // https://blog.emberjs.com/2019/01/26/emberjs-native-class-update-2019-edition.html - return this._super(...arguments); - }, - - /** - * Generates a string to be appended to a URL. Empty string by default. - * @param {string} modelName - * @param {string} id - * @param {?object} snapshot - * @return {string} - */ - urlSuffix(/* modelName, id, snapshot */) { - return ''; - }, + /** + * Generates a string to be appended to a URL. Empty string by default. + * @param {string} modelName + * @param {string} id + * @param {?object} snapshot + * @return {string} + */ + urlSuffix(/* modelName, id, snapshot */) { + return ''; + }, /** * Overrides the default `_buildURL` with a nearly identical version. @@ -73,6 +72,82 @@ export default Mixin.create({ url = url.join('/'); if (!host && url && url.charAt(0) !== '/') url = '/' + url; return `${url}${suffix}`; + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForFindRecord(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForFindAll(modelName, snapshot) { + return this._buildURL(modelName, null, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForFindBelongsTo(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForCreateRecord(modelName, snapshot) { + return this._buildURL(modelName, null, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForUpdateRecord(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForDeleteRecord(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); } }); diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js index f84fc92ff..bec9d581b 100644 --- a/addons/api/tests/unit/adapters/application-test.js +++ b/addons/api/tests/unit/adapters/application-test.js @@ -18,9 +18,64 @@ module('Unit | Adapter | application', function (hooks) { }); test('it generates correct URL prefixes', function (assert) { + assert.expect(2); + const adapter = this.owner.lookup('adapter:application'); + assert.ok(config.api.namespace); + assert.equal(adapter.urlPrefix(), config.api.namespace); + }); + + test('it generates URL prefixes with optional `scope_id` from adapterOptions', function (assert) { + assert.expect(2); + const scope_id = 'global'; + const adapter = this.owner.lookup('adapter:application'); + const prefix = adapter.urlPrefix(null, null, null, null, { + adapterOptions: {scope_id} + }); + assert.ok(config.api.namespace); + assert.equal(prefix, `v1/${scope_id}`); + }); + + test('it generates correct default URL suffixes', function (assert) { assert.expect(1); const adapter = this.owner.lookup('adapter:application'); - assert.equal(adapter.urlPrefix(), config.api.namespace); + assert.equal(adapter.urlSuffix(), ''); + }); + + test('it generates URL suffixes with optional `method` from adapterOptions', function (assert) { + assert.expect(2); + const method = 'set-something'; + const adapter = this.owner.lookup('adapter:application'); + const suffix = adapter.urlSuffix(null, null, { + adapterOptions: {method} + }); + assert.ok(config.api.namespace); + assert.equal(suffix, ':set-something'); + }); + + test('it generates correct complete URLs', function (assert) { + assert.expect(6); + const scope_id = 'o_1'; + const method = 'my-custom-method'; + const mockSnapshot = { + adapterOptions: { + scope_id, + method + } + }; + const adapter = this.owner.lookup('adapter:application'); + // test URL generation for each request type... + const findRecordURL = adapter.buildURL('project', '1', mockSnapshot, 'findRecord'); + assert.equal(findRecordURL, '/v1/o_1/projects/1:my-custom-method'); + const findAllURL = adapter.buildURL('project', null, mockSnapshot, 'findAll'); + assert.equal(findAllURL, '/v1/o_1/projects:my-custom-method'); + const findBelongsToURL = adapter.buildURL('project', '2', mockSnapshot, 'findBelongsTo'); + assert.equal(findBelongsToURL, '/v1/o_1/projects/2:my-custom-method'); + const createRecordURL = adapter.buildURL('project', null, mockSnapshot, 'createRecord'); + assert.equal(createRecordURL, '/v1/o_1/projects:my-custom-method'); + const updateRecordURL = adapter.buildURL('project', '3', mockSnapshot, 'updateRecord'); + assert.equal(updateRecordURL, '/v1/o_1/projects/3:my-custom-method'); + const deleteRecordURL = adapter.buildURL('project', '4', mockSnapshot, 'deleteRecord'); + assert.equal(deleteRecordURL, '/v1/o_1/projects/4:my-custom-method'); }); test('it rewrites PUT to PATCH, but leaves others unchanged', function (assert) {