Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion addon/adapters/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}}`
Expand Down
23 changes: 23 additions & 0 deletions addon/mixins/adapter-api-host-proxy.js
Original file line number Diff line number Diff line change
@@ -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({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aars thanks for adding this as a mixin :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My pleasure. When I find the time I'll look through all the docs. Those needs some touching up I reckon.

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;
}
});
23 changes: 13 additions & 10 deletions blueprints/jsonapi-adapter/files/__root__/__path__/__name__.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('/')
});
23 changes: 13 additions & 10 deletions tests/acceptance/polymorphic-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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;
});
Expand Down
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/author.js
Original file line number Diff line number Diff line change
@@ -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'
});
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/comment.js
Original file line number Diff line number Diff line change
@@ -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'
});
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/commenter.js
Original file line number Diff line number Diff line change
@@ -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'
});
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/employee.js
Original file line number Diff line number Diff line change
@@ -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'
});
11 changes: 0 additions & 11 deletions tests/dummy/app/adapters/imageable.js
Original file line number Diff line number Diff line change
@@ -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() {},
Expand Down
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/picture.js
Original file line number Diff line number Diff line change
@@ -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'
});
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/post.js
Original file line number Diff line number Diff line change
@@ -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'
});
14 changes: 1 addition & 13 deletions tests/dummy/app/adapters/product.js
Original file line number Diff line number Diff line change
@@ -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'
});
3 changes: 1 addition & 2 deletions tests/dummy/config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
49 changes: 49 additions & 0 deletions tests/unit/adapters/application-test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
Expand Down