From 89b736744fe1f0493310d65ff7a66a795bb2c377 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 15 Jul 2020 15:52:21 -0400 Subject: [PATCH] chore: add support for prefix and suffix with enhanced API --- addons/api/addon/adapters/application.js | 5 +- addons/api/addon/mixins/adapter-build-url.js | 78 +++++++++++++++++++ .../unit/mixins/adapter-build-url-test.js | 55 +++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 addons/api/addon/mixins/adapter-build-url.js create mode 100644 addons/api/tests/unit/mixins/adapter-build-url-test.js diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 182fa3493..85bf2f829 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -1,4 +1,5 @@ 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 { InvalidError } from '@ember-data/adapter/error'; @@ -36,7 +37,9 @@ function prenormalizeArrayResponse(response) { return payloadIsBlank(response) ? {items: []} : response; } -export default class ApplicationAdapter extends RESTAdapter { +export default class ApplicationAdapter extends RESTAdapter.extend( + AdapterBuildURLMixin +) { // =attributes /** diff --git a/addons/api/addon/mixins/adapter-build-url.js b/addons/api/addon/mixins/adapter-build-url.js new file mode 100644 index 000000000..8ff6d3474 --- /dev/null +++ b/addons/api/addon/mixins/adapter-build-url.js @@ -0,0 +1,78 @@ +import Mixin from '@ember/object/mixin'; +import { get } from '@ember/object'; + +/** + * Overrides URL-building methods in an adpater for greater flexibility. + * Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and + * `snapshot`, which aren't normally passed. And adds support for a + * `urlSuffix` method, the return value of which is appended to the end of + * a generated URL. + * + * While this mixin provides an enhanced API, it doesn't actually change the + * way URLs are generated by default. Override methods in the adapter that + * uses this mixin to alter generated URLs. + */ +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); + }, + + /** + * 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. + * This method simply passes arguments into the `urlPrefix` method for + * convenience, where by default it receives no arguments. + * + * Adds a suffix, generated by the `urlSuffix` method, to the URL. + * @override + * @param {String} modelName + * @param {String} id + * @return {String} url + */ + _buildURL(modelName, id, snapshot) { + let path; + let url = []; + const host = get(this, 'host'); + const prefix = this.urlPrefix(null, null, modelName, id, snapshot); + const suffix = this.urlSuffix(modelName, id, snapshot); + if (modelName) { + path = this.pathForType(modelName); + if (path) url.push(path); + } + if (id) url.push(encodeURIComponent(id)); + if (prefix) url.unshift(prefix); + url = url.join('/'); + if (!host && url && url.charAt(0) !== '/') url = '/' + url; + return `${url}${suffix}`; + } + +}); diff --git a/addons/api/tests/unit/mixins/adapter-build-url-test.js b/addons/api/tests/unit/mixins/adapter-build-url-test.js new file mode 100644 index 000000000..560f3f8fc --- /dev/null +++ b/addons/api/tests/unit/mixins/adapter-build-url-test.js @@ -0,0 +1,55 @@ +import RESTAdapter from '@ember-data/adapter/rest'; +import AdapterBuildURLMixin from 'api/mixins/adapter-build-url'; +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Mixin | build-url', function(hooks) { + setupTest(hooks); + + test('it does not interfere with basic URLs', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/models/1'); + }); + + test('it does not interfere with namespaced URLs', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin, { + namespace: 'api/v1' + }); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/api/v1/models/1'); + }); + + test('it can prepend a prefix', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin, { + urlPrefix() { + return '/foo/bars' + } + }); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/foo/bars/models/1'); + }); + + test('it can append a suffix', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin, { + urlSuffix() { + return ':method' + } + }); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/models/1:method'); + }); + +});