From 8770603065cc67f9c9e63c62ff573c300eced1d0 Mon Sep 17 00:00:00 2001 From: Bill Heaton Date: Sat, 22 Aug 2015 00:30:29 -0700 Subject: [PATCH] Add support for polymorphic association in a resource --- addon/adapters/application.js | 3 +- addon/models/resource.js | 35 +-- fixtures/api/pictures.json | 206 ++++++++++++++++++ fixtures/api/pictures/1.json | 20 ++ fixtures/api/pictures/1/imageable.json | 20 ++ fixtures/api/pictures/5.json | 20 ++ fixtures/api/pictures/5/imageable.json | 20 ++ tests/acceptance/polymorphic-test.js | 139 ++++++++++++ tests/dummy/app/adapters/employee.js | 17 ++ tests/dummy/app/adapters/imageable.js | 25 +++ tests/dummy/app/adapters/picture.js | 17 ++ tests/dummy/app/adapters/product.js | 17 ++ tests/dummy/app/initializers/employee.js | 20 ++ tests/dummy/app/initializers/imageable.js | 18 ++ tests/dummy/app/initializers/picture.js | 20 ++ tests/dummy/app/initializers/product.js | 20 ++ tests/dummy/app/models/employee.js | 12 + tests/dummy/app/models/picture.js | 12 + tests/dummy/app/models/product.js | 12 + tests/dummy/app/router.js | 9 + tests/dummy/app/routes/employees.js | 19 ++ tests/dummy/app/routes/pictures.js | 19 ++ tests/dummy/app/routes/products.js | 19 ++ tests/dummy/app/serializers/employee.js | 3 + tests/dummy/app/serializers/imageable.js | 3 + tests/dummy/app/serializers/picture.js | 3 + tests/dummy/app/serializers/product.js | 3 + tests/dummy/app/services/employees.js | 6 + tests/dummy/app/services/imageables.js | 6 + tests/dummy/app/services/pictures.js | 6 + tests/dummy/app/services/products.js | 6 + tests/dummy/app/templates/application.hbs | 3 + tests/dummy/app/templates/employees.hbs | 7 + .../dummy/app/templates/employees/detail.hbs | 3 + tests/dummy/app/templates/pictures.hbs | 7 + tests/dummy/app/templates/pictures/detail.hbs | 4 + tests/dummy/app/templates/products.hbs | 7 + tests/dummy/app/templates/products/detail.hbs | 3 + 38 files changed, 771 insertions(+), 18 deletions(-) create mode 100644 fixtures/api/pictures.json create mode 100644 fixtures/api/pictures/1.json create mode 100644 fixtures/api/pictures/1/imageable.json create mode 100644 fixtures/api/pictures/5.json create mode 100644 fixtures/api/pictures/5/imageable.json create mode 100644 tests/acceptance/polymorphic-test.js create mode 100644 tests/dummy/app/adapters/employee.js create mode 100644 tests/dummy/app/adapters/imageable.js create mode 100644 tests/dummy/app/adapters/picture.js create mode 100644 tests/dummy/app/adapters/product.js create mode 100644 tests/dummy/app/initializers/employee.js create mode 100644 tests/dummy/app/initializers/imageable.js create mode 100644 tests/dummy/app/initializers/picture.js create mode 100644 tests/dummy/app/initializers/product.js create mode 100644 tests/dummy/app/models/employee.js create mode 100644 tests/dummy/app/models/picture.js create mode 100644 tests/dummy/app/models/product.js create mode 100644 tests/dummy/app/routes/employees.js create mode 100644 tests/dummy/app/routes/pictures.js create mode 100644 tests/dummy/app/routes/products.js create mode 100644 tests/dummy/app/serializers/employee.js create mode 100644 tests/dummy/app/serializers/imageable.js create mode 100644 tests/dummy/app/serializers/picture.js create mode 100644 tests/dummy/app/serializers/product.js create mode 100644 tests/dummy/app/services/employees.js create mode 100644 tests/dummy/app/services/imageables.js create mode 100644 tests/dummy/app/services/pictures.js create mode 100644 tests/dummy/app/services/products.js create mode 100644 tests/dummy/app/templates/employees.hbs create mode 100644 tests/dummy/app/templates/employees/detail.hbs create mode 100644 tests/dummy/app/templates/pictures.hbs create mode 100644 tests/dummy/app/templates/pictures/detail.hbs create mode 100644 tests/dummy/app/templates/products.hbs create mode 100644 tests/dummy/app/templates/products/detail.hbs diff --git a/addon/adapters/application.js b/addon/adapters/application.js index e13a48b..3d29c85 100644 --- a/addon/adapters/application.js +++ b/addon/adapters/application.js @@ -103,7 +103,8 @@ export default Ember.Object.extend(Ember.Evented, { resource = resource.resource; type = resource.type; } - let service = this.container.lookup('service:' + pluralize(type)); + // use resource's service if in container, otherwise use this service to fetch + let service = this.container.lookup('service:' + pluralize(type)) || this; url = this.fetchUrl(url); return service.fetch(url, { method: 'GET' }); }, diff --git a/addon/models/resource.js b/addon/models/resource.js index 4b03f71..da08ffa 100644 --- a/addon/models/resource.js +++ b/addon/models/resource.js @@ -280,26 +280,27 @@ Resource.reopenClass({ } let type = instance.get('type'); let msg = (type) ? Ember.String.capitalize(singularize(type)) : 'Resource'; + let factory = 'model:' + type; if (!type) { Ember.Logger.warn(msg + '#create called, instead you should first use ' + msg + '.extend({type:"entity"})'); - } - let factory = 'model:' + type; - if (instance.container) { - factory = instance.container.lookupFactory(factory); - let proto = factory.proto(); - factory.eachComputedProperty(function(prop) { - if (proto[prop] && proto[prop]._meta && typeof proto[prop]._meta === 'object') { - if (proto[prop]._meta.kind === 'hasOne') { - setupRelationship.call(instance, prop); - } else if (proto[prop]._meta.kind === 'hasMany') { - setupRelationship.call(instance, prop, Ember.A([])); - } - } - }); } else { - msg += '#create should only be called from a container lookup (relationships not setup) '; - msg += 'use this.container.lookupFactory("' + factory + '").create() instead.'; - Ember.Logger.warn(msg); + if (instance.container) { + factory = instance.container.lookupFactory(factory); + let proto = factory.proto(); + factory.eachComputedProperty(function(prop) { + if (proto[prop] && proto[prop]._meta && typeof proto[prop]._meta === 'object') { + if (proto[prop]._meta.kind === 'hasOne') { + setupRelationship.call(instance, prop); + } else if (proto[prop]._meta.kind === 'hasMany') { + setupRelationship.call(instance, prop, Ember.A([])); + } + } + }); + } else { + msg += '#create should only be called from a container lookup (relationships not setup) '; + msg += 'use this.container.lookupFactory("' + factory + '").create() instead.'; + Ember.Logger.warn(msg); + } } return instance; } diff --git a/fixtures/api/pictures.json b/fixtures/api/pictures.json new file mode 100644 index 0000000..a546389 --- /dev/null +++ b/fixtures/api/pictures.json @@ -0,0 +1,206 @@ +{ + "data": [ + { + "id": "1", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/1" + }, + "attributes": { + "name": "box of chocolates" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/1/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/1/imageable" + }, + "data": { + "type": "products", + "id": "3" + } + } + } + }, + { + "id": "2", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/2" + }, + "attributes": { + "name": "10 foot candy cane" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/2/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/2/imageable" + }, + "data": { + "type": "products", + "id": "2" + } + } + } + }, + { + "id": "3", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/3" + }, + "attributes": { + "name": "Hot apple fritter" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/3/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/3/imageable" + }, + "data": { + "type": "products", + "id": "1" + } + } + } + }, + { + "id": "4", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/4" + }, + "attributes": { + "name": "Boston Creme" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/4/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/4/imageable" + }, + "data": { + "type": "products", + "id": "1" + } + } + } + }, + { + "id": "5", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/5" + }, + "attributes": { + "name": "Bill at EmberConf" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/5/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/5/imageable" + }, + "data": { + "type": "employees", + "id": "1" + } + } + } + } + ], + "included": [ + { + "id": "3", + "type": "products", + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/3" + }, + "attributes": { + "name": "Chocolates" + }, + "relationships": { + "pictures": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/3/relationships/pictures", + "related": "http://api.pixelhandler.com/api/v1/products/3/pictures" + } + } + } + }, + { + "id": "2", + "type": "products", + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/2" + }, + "attributes": { + "name": "Candy Canes" + }, + "relationships": { + "pictures": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/2/relationships/pictures", + "related": "http://api.pixelhandler.com/api/v1/products/2/pictures" + } + } + } + }, + { + "id": "1", + "type": "products", + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/1" + }, + "attributes": { + "name": "Donuts" + }, + "relationships": { + "pictures": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/1/relationships/pictures", + "related": "http://api.pixelhandler.com/api/v1/products/1/pictures" + } + } + } + }, + { + "id": "1", + "type": "employees", + "links": { + "self": "http://api.pixelhandler.com/api/v1/employees/1" + }, + "attributes": { + "name": "Bill Heaton" + }, + "relationships": { + "pictures": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/employees/1/relationships/pictures", + "related": "http://api.pixelhandler.com/api/v1/employees/1/pictures" + } + } + } + } + ], + "meta": { + "page": { + "total": 7, + "sort": [ + { + "field": "id", + "direction": "asc" + } + ], + "offset": 0, + "limit": 5 + } + }, + "links": { + "first": "http://api.pixelhandler.com/api/v1/pictures?include=imageable&page%5Blimit%5D=5&page%5Boffset%5D=0", + "next": "http://api.pixelhandler.com/api/v1/pictures?include=imageable&page%5Blimit%5D=5&page%5Boffset%5D=5", + "last": "http://api.pixelhandler.com/api/v1/pictures?include=imageable&page%5Blimit%5D=5&page%5Boffset%5D=2" + } +} diff --git a/fixtures/api/pictures/1.json b/fixtures/api/pictures/1.json new file mode 100644 index 0000000..05520f7 --- /dev/null +++ b/fixtures/api/pictures/1.json @@ -0,0 +1,20 @@ +{ + "data": { + "id": "1", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/1" + }, + "attributes": { + "name": "box of chocolates" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/1/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/1/imageable" + } + } + } + } +} diff --git a/fixtures/api/pictures/1/imageable.json b/fixtures/api/pictures/1/imageable.json new file mode 100644 index 0000000..7d3c6f3 --- /dev/null +++ b/fixtures/api/pictures/1/imageable.json @@ -0,0 +1,20 @@ +{ + "data": { + "id": "3", + "type": "products", + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/3" + }, + "attributes": { + "name": "Chocolates" + }, + "relationships": { + "pictures": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/products/3/relationships/pictures", + "related": "http://api.pixelhandler.com/api/v1/products/3/pictures" + } + } + } + } +} diff --git a/fixtures/api/pictures/5.json b/fixtures/api/pictures/5.json new file mode 100644 index 0000000..6987972 --- /dev/null +++ b/fixtures/api/pictures/5.json @@ -0,0 +1,20 @@ +{ + "data": { + "id": "5", + "type": "pictures", + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/5" + }, + "attributes": { + "name": "Bill at EmberConf" + }, + "relationships": { + "imageable": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/pictures/5/relationships/imageable", + "related": "http://api.pixelhandler.com/api/v1/pictures/5/imageable" + } + } + } + } +} diff --git a/fixtures/api/pictures/5/imageable.json b/fixtures/api/pictures/5/imageable.json new file mode 100644 index 0000000..5a4e21d --- /dev/null +++ b/fixtures/api/pictures/5/imageable.json @@ -0,0 +1,20 @@ +{ + "data": { + "id": "1", + "type": "employees", + "links": { + "self": "http://api.pixelhandler.com/api/v1/employees/1" + }, + "attributes": { + "name": "Bill Heaton" + }, + "relationships": { + "pictures": { + "links": { + "self": "http://api.pixelhandler.com/api/v1/employees/1/relationships/pictures", + "related": "http://api.pixelhandler.com/api/v1/employees/1/pictures" + } + } + } + } +} diff --git a/tests/acceptance/polymorphic-test.js b/tests/acceptance/polymorphic-test.js new file mode 100644 index 0000000..24167ef --- /dev/null +++ b/tests/acceptance/polymorphic-test.js @@ -0,0 +1,139 @@ +import Ember from 'ember'; +import { module, test } from 'qunit'; +import startApp from '../../tests/helpers/start-app'; + +import picturesMock from 'fixtures/api/pictures'; +import pictures1Mock from 'fixtures/api/pictures/1'; +import pictures1ImageableMock from 'fixtures/api/pictures/1/imageable'; +import pictures5Mock from 'fixtures/api/pictures/5'; +import pictures5ImageableMock from 'fixtures/api/pictures/5/imageable'; + +module('Acceptance | polymorphic', { + beforeEach: function() { + this.sandbox = window.sinon.sandbox.create(); + this.application = startApp(); + }, + + afterEach: function() { + Ember.run(this.application, 'destroy'); + this.sandbox.restore(); + } +}); + +test('visiting /pictures', function(assert) { + assert.expect(6); + setupFetchResonses(this.sandbox); + + visit('/pictures'); + andThen(function() { + assert.equal(currentURL(), '/pictures', 'Pictures route rendered'); + let listItems = document.querySelector('#ember-testing ul').querySelectorAll('li'); + let name; + for (let i = 0; i < picturesMock.data.length; i++) { + name = picturesMock.data[i].attributes.name; + assert.equal(listItems[i].innerText, name, 'item rendered: ' + name); + } + }); +}); + +test('visiting /pictures/1, picture with an (imageable) product relation', function(assert) { + assert.expect(3); + setupFetchResonses(this.sandbox); + + visit('/pictures/1'); + andThen(function() { + assert.equal(currentURL(), '/pictures/1', 'Pictures #1 route rendered'); + let content = document.querySelectorAll('#ember-testing p'); + let name = pictures1Mock.data.attributes.name; + assert.ok(content[0].innerText.trim().match(name) !== null, name + ' rendered in outlet'); + let imageableName = pictures1ImageableMock.data.attributes.name; + assert.ok(content[1].innerText.trim().match(imageableName) !== null, imageableName + ' rendered in outlet'); + }); +}); + +test('visiting /pictures/5, picture with an (imageable) employee relation', function(assert) { + assert.expect(3); + setupFetchResonses(this.sandbox); + + visit('/pictures/5'); + andThen(function() { + assert.equal(currentURL(), '/pictures/5', 'Pictures #5 route rendered'); + let content = document.querySelectorAll('#ember-testing p'); + let name = pictures5Mock.data.attributes.name; + assert.ok(content[0].innerText.trim().match(name) !== null, name + ' rendered in outlet'); + let imageableName = pictures5ImageableMock.data.attributes.name; + assert.ok(content[1].innerText.trim().match(imageableName) !== null, imageableName + ' rendered in outlet'); + }); +}); + + +function setupFetchResonses(sandbox) { + sandbox.stub(window, 'fetch', function (url) { + let resp; + switch(url) { + case 'api/v1/pictures?sort=id&include=imageable': + resp = picturesMockResponse(); + break; + case 'api/v1/pictures/1': + resp = pictures1MockResponse(); + break; + case '/api/v1/pictures/1/imageable': + resp = pictures1ImageableMockResponse(); + break; + case 'api/v1/pictures/5': + resp = pictures5MockResponse(); + break; + case '/api/v1/pictures/5/imageable': + resp = pictures5ImageableMockResponse(); + break; + default: + throw('no mocked fetch reponse for request'); + } + return resp; + }); +} + +function picturesMockResponse() { + return Ember.RSVP.Promise.resolve({ + "status": 200, + "json": function() { + return Ember.RSVP.Promise.resolve(picturesMock); + } + }); +} + +function pictures1MockResponse() { + return Ember.RSVP.Promise.resolve({ + "status": 200, + "json": function() { + return Ember.RSVP.Promise.resolve(pictures1Mock); + } + }); +} + +function pictures1ImageableMockResponse() { + return Ember.RSVP.Promise.resolve({ + "status": 200, + "json": function() { + return Ember.RSVP.Promise.resolve(pictures1ImageableMock); + } + }); +} + +function pictures5MockResponse() { + return Ember.RSVP.Promise.resolve({ + "status": 200, + "json": function() { + return Ember.RSVP.Promise.resolve(pictures5Mock); + } + }); +} + +function pictures5ImageableMockResponse() { + return Ember.RSVP.Promise.resolve({ + "status": 200, + "json": function() { + return Ember.RSVP.Promise.resolve(pictures5ImageableMock); + } + }); +} diff --git a/tests/dummy/app/adapters/employee.js b/tests/dummy/app/adapters/employee.js new file mode 100644 index 0000000..3231dd9 --- /dev/null +++ b/tests/dummy/app/adapters/employee.js @@ -0,0 +1,17 @@ +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; + } +}); diff --git a/tests/dummy/app/adapters/imageable.js b/tests/dummy/app/adapters/imageable.js new file mode 100644 index 0000000..710697d --- /dev/null +++ b/tests/dummy/app/adapters/imageable.js @@ -0,0 +1,25 @@ +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() {}, + cacheUpdate() {}, + cacheResource() {}, + initEvents() {} +}); diff --git a/tests/dummy/app/adapters/picture.js b/tests/dummy/app/adapters/picture.js new file mode 100644 index 0000000..b803c8d --- /dev/null +++ b/tests/dummy/app/adapters/picture.js @@ -0,0 +1,17 @@ +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; + } +}); diff --git a/tests/dummy/app/adapters/product.js b/tests/dummy/app/adapters/product.js new file mode 100644 index 0000000..2c5d5b9 --- /dev/null +++ b/tests/dummy/app/adapters/product.js @@ -0,0 +1,17 @@ +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; + } +}); diff --git a/tests/dummy/app/initializers/employee.js b/tests/dummy/app/initializers/employee.js new file mode 100644 index 0000000..2bef900 --- /dev/null +++ b/tests/dummy/app/initializers/employee.js @@ -0,0 +1,20 @@ +import Service from '../services/employees'; +import Model from '../models/employee'; +import Adapter from '../adapters/employee'; +import Serializer from '../serializers/employee'; + +export function initialize(container, application) { + application.register('model:employees', Model, { instantiate: false, singleton: false }); + application.register('service:employees', Service); + application.register('adapter:employees', Adapter); + application.register('serializer:employees', Serializer); + + application.inject('service:store', 'employees', 'service:employees'); + application.inject('service:employees', 'serializer', 'serializer:employees'); +} + +export default { + name: 'employees-service', + after: 'store', + initialize: initialize +}; diff --git a/tests/dummy/app/initializers/imageable.js b/tests/dummy/app/initializers/imageable.js new file mode 100644 index 0000000..a733e1e --- /dev/null +++ b/tests/dummy/app/initializers/imageable.js @@ -0,0 +1,18 @@ +import Service from '../services/imageables'; +import Adapter from '../adapters/imageable'; +import Serializer from '../serializers/application'; + +export function initialize(container, application) { + application.register('service:imageables', Service); + application.register('adapter:imageables', Adapter); + application.register('serializer:imageables', Serializer); + + application.inject('service:store', 'imageables', 'service:imageables'); + application.inject('service:imageables', 'serializer', 'serializer:imageables'); +} + +export default { + name: 'imageables-service', + after: 'store', + initialize: initialize +}; diff --git a/tests/dummy/app/initializers/picture.js b/tests/dummy/app/initializers/picture.js new file mode 100644 index 0000000..3db1e79 --- /dev/null +++ b/tests/dummy/app/initializers/picture.js @@ -0,0 +1,20 @@ +import Service from '../services/pictures'; +import Model from '../models/picture'; +import Adapter from '../adapters/picture'; +import Serializer from '../serializers/picture'; + +export function initialize(container, application) { + application.register('model:pictures', Model, { instantiate: false, singleton: false }); + application.register('service:pictures', Service); + application.register('adapter:pictures', Adapter); + application.register('serializer:pictures', Serializer); + + application.inject('service:store', 'pictures', 'service:pictures'); + application.inject('service:pictures', 'serializer', 'serializer:pictures'); +} + +export default { + name: 'pictures-service', + after: 'store', + initialize: initialize +}; diff --git a/tests/dummy/app/initializers/product.js b/tests/dummy/app/initializers/product.js new file mode 100644 index 0000000..5fcef6d --- /dev/null +++ b/tests/dummy/app/initializers/product.js @@ -0,0 +1,20 @@ +import Service from '../services/products'; +import Model from '../models/product'; +import Adapter from '../adapters/product'; +import Serializer from '../serializers/product'; + +export function initialize(container, application) { + application.register('model:products', Model, { instantiate: false, singleton: false }); + application.register('service:products', Service); + application.register('adapter:products', Adapter); + application.register('serializer:products', Serializer); + + application.inject('service:store', 'products', 'service:products'); + application.inject('service:products', 'serializer', 'serializer:products'); +} + +export default { + name: 'products-service', + after: 'store', + initialize: initialize +}; diff --git a/tests/dummy/app/models/employee.js b/tests/dummy/app/models/employee.js new file mode 100644 index 0000000..248b5b4 --- /dev/null +++ b/tests/dummy/app/models/employee.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import Resource from './resource'; +import { attr, hasMany } from 'ember-jsonapi-resources/models/resource'; + +export default Resource.extend({ + type: 'employees', + service: Ember.inject.service('employees'), + + name: attr(), + + pictures: hasMany('pictures') +}); diff --git a/tests/dummy/app/models/picture.js b/tests/dummy/app/models/picture.js new file mode 100644 index 0000000..5e20e34 --- /dev/null +++ b/tests/dummy/app/models/picture.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import Resource from './resource'; +import { attr, hasOne } from 'ember-jsonapi-resources/models/resource'; + +export default Resource.extend({ + type: 'pictures', + service: Ember.inject.service('pictures'), + + name: attr(), + + imageable: hasOne('imageable') // polymorphic +}); diff --git a/tests/dummy/app/models/product.js b/tests/dummy/app/models/product.js new file mode 100644 index 0000000..36d97eb --- /dev/null +++ b/tests/dummy/app/models/product.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import Resource from './resource'; +import { attr, hasMany } from 'ember-jsonapi-resources/models/resource'; + +export default Resource.extend({ + type: 'products', + service: Ember.inject.service('products'), + + name: attr(), + + pictures: hasMany('pictures') +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index bd04c02..9aea8c2 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -16,6 +16,15 @@ Router.map(function() { this.route('create'); this.route('edit', { path: ':edit_id' }); }); + this.route('products', { path: '/products' }, function () { + this.route('detail', { path: '/:product_id' }); + }); + this.route('employees', { path: '/employees' }, function () { + this.route('detail', { path: '/:employee_id' }); + }); + this.route('pictures', { path: '/pictures' }, function () { + this.route('detail', { path: '/:picture_id' }); + }); }); export default Router; diff --git a/tests/dummy/app/routes/employees.js b/tests/dummy/app/routes/employees.js new file mode 100644 index 0000000..e2afa6f --- /dev/null +++ b/tests/dummy/app/routes/employees.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + const options = { + query: { + sort: 'name', + include: 'pictures' + } + }; + return this.store.find('employees', options); + }, + + actions: { + error(error) { + Ember.Logger.error(error); + } + } +}); diff --git a/tests/dummy/app/routes/pictures.js b/tests/dummy/app/routes/pictures.js new file mode 100644 index 0000000..cd6db68 --- /dev/null +++ b/tests/dummy/app/routes/pictures.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + const options = { + query: { + sort: 'id', + include: 'imageable' + } + }; + return this.store.find('pictures', options); + }, + + actions: { + error(error) { + Ember.Logger.error(error); + } + } +}); diff --git a/tests/dummy/app/routes/products.js b/tests/dummy/app/routes/products.js new file mode 100644 index 0000000..437dde1 --- /dev/null +++ b/tests/dummy/app/routes/products.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + const options = { + query: { + sort: '-id', + include: 'pictures' + } + }; + return this.store.find('products', options); + }, + + actions: { + error(error) { + Ember.Logger.error(error); + } + } +}); diff --git a/tests/dummy/app/serializers/employee.js b/tests/dummy/app/serializers/employee.js new file mode 100644 index 0000000..3a37af5 --- /dev/null +++ b/tests/dummy/app/serializers/employee.js @@ -0,0 +1,3 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend(); diff --git a/tests/dummy/app/serializers/imageable.js b/tests/dummy/app/serializers/imageable.js new file mode 100644 index 0000000..3a37af5 --- /dev/null +++ b/tests/dummy/app/serializers/imageable.js @@ -0,0 +1,3 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend(); diff --git a/tests/dummy/app/serializers/picture.js b/tests/dummy/app/serializers/picture.js new file mode 100644 index 0000000..3a37af5 --- /dev/null +++ b/tests/dummy/app/serializers/picture.js @@ -0,0 +1,3 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend(); diff --git a/tests/dummy/app/serializers/product.js b/tests/dummy/app/serializers/product.js new file mode 100644 index 0000000..3a37af5 --- /dev/null +++ b/tests/dummy/app/serializers/product.js @@ -0,0 +1,3 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend(); diff --git a/tests/dummy/app/services/employees.js b/tests/dummy/app/services/employees.js new file mode 100644 index 0000000..c315cd5 --- /dev/null +++ b/tests/dummy/app/services/employees.js @@ -0,0 +1,6 @@ +import Adapter from '../adapters/employee'; +import ServiceCache from '../mixins/service-cache'; + +Adapter.reopenClass({ isServiceFactory: true }); + +export default Adapter.extend(ServiceCache); diff --git a/tests/dummy/app/services/imageables.js b/tests/dummy/app/services/imageables.js new file mode 100644 index 0000000..e98aa61 --- /dev/null +++ b/tests/dummy/app/services/imageables.js @@ -0,0 +1,6 @@ +import Adapter from '../adapters/imageable'; +import ServiceCache from '../mixins/service-cache'; + +Adapter.reopenClass({ isServiceFactory: true }); + +export default Adapter.extend(ServiceCache); diff --git a/tests/dummy/app/services/pictures.js b/tests/dummy/app/services/pictures.js new file mode 100644 index 0000000..8e75117 --- /dev/null +++ b/tests/dummy/app/services/pictures.js @@ -0,0 +1,6 @@ +import Adapter from '../adapters/picture'; +import ServiceCache from '../mixins/service-cache'; + +Adapter.reopenClass({ isServiceFactory: true }); + +export default Adapter.extend(ServiceCache); diff --git a/tests/dummy/app/services/products.js b/tests/dummy/app/services/products.js new file mode 100644 index 0000000..2a88107 --- /dev/null +++ b/tests/dummy/app/services/products.js @@ -0,0 +1,6 @@ +import Adapter from '../adapters/product'; +import ServiceCache from '../mixins/service-cache'; + +Adapter.reopenClass({ isServiceFactory: true }); + +export default Adapter.extend(ServiceCache); diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index 19253d7..80e9eb9 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -7,4 +7,7 @@
  • {{#link-to 'index'}}Home{{/link-to}}
  • {{#link-to 'admin.index'}}Edit{{/link-to}}
  • {{#link-to 'admin.create'}}Write{{/link-to}}
  • +
  • {{#link-to 'products'}}Products{{/link-to}}
  • +
  • {{#link-to 'employees'}}Employees{{/link-to}}
  • +
  • {{#link-to 'pictures'}}Pictures{{/link-to}}
  • diff --git a/tests/dummy/app/templates/employees.hbs b/tests/dummy/app/templates/employees.hbs new file mode 100644 index 0000000..6270fe6 --- /dev/null +++ b/tests/dummy/app/templates/employees.hbs @@ -0,0 +1,7 @@ + + +{{outlet}} diff --git a/tests/dummy/app/templates/employees/detail.hbs b/tests/dummy/app/templates/employees/detail.hbs new file mode 100644 index 0000000..d127536 --- /dev/null +++ b/tests/dummy/app/templates/employees/detail.hbs @@ -0,0 +1,3 @@ +

    Name: {{model.name}}

    + +{{outlet}} diff --git a/tests/dummy/app/templates/pictures.hbs b/tests/dummy/app/templates/pictures.hbs new file mode 100644 index 0000000..70b6f70 --- /dev/null +++ b/tests/dummy/app/templates/pictures.hbs @@ -0,0 +1,7 @@ + + +{{outlet}} diff --git a/tests/dummy/app/templates/pictures/detail.hbs b/tests/dummy/app/templates/pictures/detail.hbs new file mode 100644 index 0000000..c66f3be --- /dev/null +++ b/tests/dummy/app/templates/pictures/detail.hbs @@ -0,0 +1,4 @@ +

    Name: {{model.name}}

    +

    Imageable: {{model.imageable.name}}

    + +{{outlet}} diff --git a/tests/dummy/app/templates/products.hbs b/tests/dummy/app/templates/products.hbs new file mode 100644 index 0000000..cd815d1 --- /dev/null +++ b/tests/dummy/app/templates/products.hbs @@ -0,0 +1,7 @@ + + +{{outlet}} diff --git a/tests/dummy/app/templates/products/detail.hbs b/tests/dummy/app/templates/products/detail.hbs new file mode 100644 index 0000000..d127536 --- /dev/null +++ b/tests/dummy/app/templates/products/detail.hbs @@ -0,0 +1,3 @@ +

    Name: {{model.name}}

    + +{{outlet}}