diff --git a/addon/adapters/application.js b/addon/adapters/application.js index 720d92f..e03746a 100644 --- a/addon/adapters/application.js +++ b/addon/adapters/application.js @@ -32,11 +32,22 @@ export default Ember.Object.extend(FetchMixin, Evented, { /** The url for the entity, e.g. /posts or /api/v1/posts + defaults to config.APP.API_HOST/config.APP.API_PATH/pluralize(type) if + not set explicitly. + @property url @type String @required */ - url: null, + url: Ember.computed('type', { + get() { + const config = this.container.lookupFactory('config:environment'); + const enclosingSlashes = /^\/|\/$/g; + const host = config.APP.API_HOST.replace(enclosingSlashes, ''); + const path = config.APP.API_PATH.replace(enclosingSlashes, ''); + return [host, path, pluralize(this.get('type'))].join('/'); + } + }), /** Find resource(s) using an id or a using a query `{id: '', query: {}}` diff --git a/addon/mixins/adapter-api-host-proxy.js b/addon/mixins/adapter-api-host-proxy.js new file mode 100644 index 0000000..d729f17 --- /dev/null +++ b/addon/mixins/adapter-api-host-proxy.js @@ -0,0 +1,23 @@ +/** + @module ember-jsonapi-resources + @submodule adapter-api-host-proxy-mixin +**/ +import Ember from 'ember'; + +/** + Mixin to provide url rewrite for proxied api. Mostly used as example. + + @class AdapterApiHostProxyMixin + @static +*/ +export default Ember.Mixin.create({ + fetchUrl: function(url) { + const config = this.container.lookupFactory('config:environment'); + const proxy = config.APP.API_HOST_PROXY; + const host = config.APP.API_HOST; + if (proxy && host) { + url = url.replace(host, proxy); + } + return url; + } +}); diff --git a/blueprints/jsonapi-adapter/files/__root__/__path__/__name__.js b/blueprints/jsonapi-adapter/files/__root__/__path__/__name__.js index 446aeca..641bc11 100644 --- a/blueprints/jsonapi-adapter/files/__root__/__path__/__name__.js +++ b/blueprints/jsonapi-adapter/files/__root__/__path__/__name__.js @@ -4,14 +4,17 @@ import config from '../config/environment'; export default <%= baseClass %>.extend({ type: '<%= entity %>', - url: config.APP.API_PATH + '/<%= resource %>', - - /*fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - }*/ + /* + * url: full url/path to API endpoint for this resource. + * + * This property is optional, as the application adapter provides a sane + * default combining config.APP.API_HOST, config.APP.API_PATH and type + * through Ember computed properties. This is faster however, and provides + * easy customization per adapter. + * + * Url is always fetched through #fetchUrl method to provide runtime + * manipulation of the url, either per adapter or application-wide on the + * application adapter. See AdapterApiHostProxyMixin for an example. + */ + url: [config.APP.API_HOST, config.APP.API_PATH, '<%= resource %>'].join('/') }); diff --git a/tests/acceptance/polymorphic-test.js b/tests/acceptance/polymorphic-test.js index 3b08347..ace7647 100644 --- a/tests/acceptance/polymorphic-test.js +++ b/tests/acceptance/polymorphic-test.js @@ -9,6 +9,8 @@ import pictures1ImageableMock from 'fixtures/api/pictures/1/imageable'; import pictures5Mock from 'fixtures/api/pictures/5'; import pictures5ImageableMock from 'fixtures/api/pictures/5/imageable'; +import config from '../../config/environment'; + module('Acceptance | polymorphic', { beforeEach: function() { this.sandbox = window.sinon.sandbox.create(); @@ -23,7 +25,7 @@ module('Acceptance | polymorphic', { test('visiting /pictures list', function(assert) { assert.expect(6); - setupFetchResonses(this.sandbox); + setupFetchResponses(this.sandbox); visit('/pictures'); andThen(function() { @@ -39,7 +41,7 @@ test('visiting /pictures list', function(assert) { test('visiting /pictures/1, picture with an (imageable) product relation', function(assert) { assert.expect(3); - setupFetchResonses(this.sandbox); + setupFetchResponses(this.sandbox); visit('/pictures/1'); andThen(function() { @@ -54,7 +56,7 @@ test('visiting /pictures/1, picture with an (imageable) product relation', funct test('visiting /pictures/5, picture with an (imageable) employee relation', function(assert) { assert.expect(3); - setupFetchResonses(this.sandbox); + setupFetchResponses(this.sandbox); visit('/pictures/5'); andThen(function() { @@ -68,27 +70,28 @@ test('visiting /pictures/5, picture with an (imageable) employee relation', func }); -function setupFetchResonses(sandbox) { +function setupFetchResponses(sandbox) { + const apiUrl = [config.APP.API_HOST, config.APP.API_PATH].join('/'); sandbox.stub(window, 'fetch', function (url) { let resp; switch(url) { - case 'api/v1/pictures?sort=id&include=imageable': + case [apiUrl, 'pictures?sort=id&include=imageable'].join('/'): resp = picturesMockResponse(); break; - case 'api/v1/pictures/1': + case [apiUrl, 'pictures/1'].join('/'): resp = pictures1MockResponse(); break; - case '/api/v1/pictures/1/imageable': + case [apiUrl, 'pictures/1/imageable'].join('/'): resp = pictures1ImageableMockResponse(); break; - case 'api/v1/pictures/5': + case [apiUrl, 'pictures/5'].join('/'): resp = pictures5MockResponse(); break; - case '/api/v1/pictures/5/imageable': + case [apiUrl, 'pictures/5/imageable'].join('/'): resp = pictures5ImageableMockResponse(); break; default: - throw('no mocked fetch reponse for request'); + throw('no mocked fetch reponse for request: ' + url); } return resp; }); diff --git a/tests/dummy/app/adapters/author.js b/tests/dummy/app/adapters/author.js index 7aaaf9e..7a32f25 100644 --- a/tests/dummy/app/adapters/author.js +++ b/tests/dummy/app/adapters/author.js @@ -1,17 +1,5 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; export default ApplicationAdapter.extend({ - type: 'author', - - url: config.APP.API_PATH + '/authors', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'author' }); diff --git a/tests/dummy/app/adapters/comment.js b/tests/dummy/app/adapters/comment.js index aae57e6..20b8def 100644 --- a/tests/dummy/app/adapters/comment.js +++ b/tests/dummy/app/adapters/comment.js @@ -1,17 +1,5 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; export default ApplicationAdapter.extend({ - type: 'comment', - - url: config.APP.API_PATH + '/comments', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'comment' }); diff --git a/tests/dummy/app/adapters/commenter.js b/tests/dummy/app/adapters/commenter.js index fcf1aaf..2f8c747 100644 --- a/tests/dummy/app/adapters/commenter.js +++ b/tests/dummy/app/adapters/commenter.js @@ -1,17 +1,5 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; export default ApplicationAdapter.extend({ - type: 'commenter', - - url: config.APP.API_PATH + '/commenters', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'commenter' }); diff --git a/tests/dummy/app/adapters/employee.js b/tests/dummy/app/adapters/employee.js index 3231dd9..6193646 100644 --- a/tests/dummy/app/adapters/employee.js +++ b/tests/dummy/app/adapters/employee.js @@ -1,17 +1,5 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; export default ApplicationAdapter.extend({ - type: 'employee', - - url: config.APP.API_PATH + '/employees', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'employee' }); diff --git a/tests/dummy/app/adapters/imageable.js b/tests/dummy/app/adapters/imageable.js index 710697d..38a9732 100644 --- a/tests/dummy/app/adapters/imageable.js +++ b/tests/dummy/app/adapters/imageable.js @@ -1,21 +1,10 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; -//import { pluralize } from 'ember-inflector'; export default ApplicationAdapter.extend({ type: 'imageable', url: null, - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - }, - find() {}, findOne() {}, findQuery() {}, diff --git a/tests/dummy/app/adapters/picture.js b/tests/dummy/app/adapters/picture.js index b803c8d..295e5ca 100644 --- a/tests/dummy/app/adapters/picture.js +++ b/tests/dummy/app/adapters/picture.js @@ -1,17 +1,5 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; export default ApplicationAdapter.extend({ - type: 'picture', - - url: config.APP.API_PATH + '/pictures', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'picture' }); diff --git a/tests/dummy/app/adapters/post.js b/tests/dummy/app/adapters/post.js index 40f3d5b..9d6acc1 100644 --- a/tests/dummy/app/adapters/post.js +++ b/tests/dummy/app/adapters/post.js @@ -1,18 +1,6 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; import AuthorizationMixin from '../mixins/authorization'; export default ApplicationAdapter.extend(AuthorizationMixin, { - type: 'post', - - url: config.APP.API_PATH + '/posts', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'post' }); diff --git a/tests/dummy/app/adapters/product.js b/tests/dummy/app/adapters/product.js index 2c5d5b9..499b327 100644 --- a/tests/dummy/app/adapters/product.js +++ b/tests/dummy/app/adapters/product.js @@ -1,17 +1,5 @@ import ApplicationAdapter from './application'; -import config from '../config/environment'; export default ApplicationAdapter.extend({ - type: 'product', - - url: config.APP.API_PATH + '/products', - - fetchUrl: function(url) { - const proxy = config.APP.API_HOST_PROXY; - const host = config.APP.API_HOST; - if (proxy && host) { - url = url.replace(proxy, host); - } - return url; - } + type: 'product' }); diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index 8c04527..e6f6d66 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -17,8 +17,7 @@ module.exports = function(environment) { APP: { // Here you can pass flags/options to your application instance // when it is created - API_HOST: '/', - API_HOST_PROXY: 'http://api.pixelhandler.com/', + API_HOST: 'http://api.pixelhandler.com', API_PATH: 'api/v1', }, contentSecurityPolicy: { diff --git a/tests/unit/adapters/application-test.js b/tests/unit/adapters/application-test.js index 63c1852..a86578b 100644 --- a/tests/unit/adapters/application-test.js +++ b/tests/unit/adapters/application-test.js @@ -1,5 +1,6 @@ import { moduleFor, test } from 'ember-qunit'; import Adapter from 'ember-jsonapi-resources/adapters/application'; +import AdapterApiHostProxyMixin from 'ember-jsonapi-resources/mixins/adapter-api-host-proxy'; import Ember from 'ember'; import RSVP from 'rsvp'; import { setup, teardown, mockServices } from 'dummy/tests/helpers/resources'; @@ -28,6 +29,54 @@ moduleFor('adapter:application', 'Unit | Adapter | application', { } }); +test('adapter has sane default url', function (assert) { + assert.expect(1); + this.registry.register('config:environment', { + APP: { + API_HOST: 'http://api.pixelhandler.com', + API_PATH: 'api/v1' + } + }); + const adapter = this.subject({type: 'posts'}); + assert.equal(adapter.get('url'), 'http://api.pixelhandler.com/api/v1/posts'); +}); + +test('adapter default url handles enclosing slashes in config', function (assert) { + assert.expect(1); + this.registry.register('config:environment', { + APP: { + API_HOST: 'http://api.pixelhandler.com/', + API_PATH: '/api/v1/' + } + }); + const adapter = this.subject({type: 'posts'}); + assert.equal(adapter.get('url'), 'http://api.pixelhandler.com/api/v1/posts'); +}); + +test('adapter allows custom url', function (assert) { + assert.expect(1); + const adapter = this.subject({type: 'posts', url: 'http://example.com/posts'}); + assert.equal(adapter.get('url'), 'http://example.com/posts'); +}); + +test('adapter with ApiHostProxyMixin rewrites API_HOST to API_HOST_PROXY', function (assert) { + assert.expect(2); + const host = 'http://api.pixelhandler.com'; + const proxy = 'http://localhost:3000'; + this.registry.register('config:environment', { + APP: { + API_HOST: host, + API_HOST_PROXY: proxy, + API_PATH: 'api/v1/', + } + }); + this.registry.register('service:posts', Adapter.extend(AdapterApiHostProxyMixin, {type: 'posts'})); + const service = this.container.lookup('service:posts'); + const url = service.get('url'); + assert.equal(url, 'http://api.pixelhandler.com/api/v1/posts', 'non-proxied-url ok'); + assert.equal(service.fetchUrl(url), url.replace(host, proxy), 'API_HOST_PROXY replaced API_HOST through in fetchUrl'); +}); + test('#find calls #findOne when options arg is a string', function(assert) { assert.expect(3); const done = assert.async();