├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── adapters │ └── application.js ├── initializers │ ├── model-setup.js │ └── store.js ├── mixins │ ├── adapter-api-host-proxy.js │ ├── authorization.js │ ├── resource-operations.js │ ├── service-cache.js │ └── transforms.js ├── models │ └── resource.js ├── serializers │ └── application.js ├── services │ └── store.js └── utils │ ├── attr.js │ ├── is.js │ ├── related-proxy.js │ ├── to-many.js │ ├── to-one.js │ ├── transform-map.js │ └── transforms.js ├── app ├── .gitkeep ├── adapters │ └── application.js ├── initializers │ ├── model-setup.js │ └── store.js ├── mixins │ ├── authorization.js │ ├── service-cache.js │ └── transforms.js ├── models │ └── resource.js ├── serializers │ └── application.js └── services │ └── store.js ├── blueprints ├── .jshintrc ├── addon-import │ ├── files │ │ └── __root__ │ │ │ └── __path__ │ │ │ └── __name__.js │ └── index.js ├── ember-jsonapi-resources │ └── index.js ├── jsonapi-adapter-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── __path__ │ │ │ └── __test__.js │ └── index.js ├── jsonapi-adapter │ ├── files │ │ └── __root__ │ │ │ └── __path__ │ │ │ └── __name__.js │ └── index.js ├── jsonapi-blueprint │ ├── files │ │ └── blueprints │ │ │ ├── .jshintrc │ │ │ └── __name__ │ │ │ └── index.js │ └── index.js ├── jsonapi-dictionary-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── utils │ │ │ └── dictionaries │ │ │ └── __name__-test.js │ └── index.js ├── jsonapi-dictionary │ ├── files │ │ └── __root__ │ │ │ └── utils │ │ │ └── dictionaries │ │ │ └── __name__.js │ └── index.js ├── jsonapi-initializer-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── initializers │ │ │ └── __name__-test.js │ └── index.js ├── jsonapi-initializer │ ├── files │ │ └── __root__ │ │ │ └── initializers │ │ │ └── __name__.js │ └── index.js ├── jsonapi-model-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── __path__ │ │ │ └── __test__.js │ └── index.js ├── jsonapi-model │ ├── files │ │ └── __root__ │ │ │ └── __path__ │ │ │ └── __name__.js │ └── index.js ├── jsonapi-resource │ └── index.js ├── jsonapi-serializer-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── __path__ │ │ │ └── __test__.js │ └── index.js ├── jsonapi-serializer │ ├── files │ │ └── __root__ │ │ │ └── __path__ │ │ │ └── __name__.js │ └── index.js ├── jsonapi-service-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── __path__ │ │ │ └── __test__.js │ └── index.js ├── jsonapi-service │ ├── files │ │ └── __root__ │ │ │ └── __path__ │ │ │ └── __resource__.js │ └── index.js ├── jsonapi-transform-mixin │ ├── files │ │ └── __root__ │ │ │ └── mixins │ │ │ └── __name__.js │ └── index.js ├── jsonapi-transform-test │ ├── files │ │ └── tests │ │ │ └── unit │ │ │ └── __path__ │ │ │ └── __test__.js │ └── index.js └── jsonapi-transform │ ├── files │ └── __root__ │ │ └── __path__ │ │ └── __name__.js │ └── index.js ├── bower.json ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── fixtures └── api │ ├── authors │ └── 1.json │ ├── employees.json │ ├── employees │ └── 1.json │ ├── pictures.json │ ├── pictures │ ├── 1 │ │ └── imageable.json │ ├── 5 │ │ └── imageable.json │ ├── 1.json │ └── 5.json │ ├── posts.json │ ├── posts │ └── 1.json │ └── supervisors │ └── 2.json ├── index.js ├── lib └── json-to-module.js ├── node-tests └── blueprints │ └── jsonapi-resource-test.js ├── package.json ├── server ├── .jshintrc ├── index.js └── proxies │ └── api.js ├── testem.js ├── tests ├── .jshintrc ├── acceptance │ └── polymorphic-test.js ├── blanket-options.js ├── dummy │ ├── app │ │ ├── adapters │ │ │ ├── author.js │ │ │ ├── comment.js │ │ │ ├── commenter.js │ │ │ ├── employee.js │ │ │ ├── imageable.js │ │ │ ├── picture.js │ │ │ ├── post.js │ │ │ └── product.js │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── form-post.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── admin │ │ │ │ └── edit.js │ │ │ └── post │ │ │ │ └── comments.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── initializers │ │ │ ├── author.js │ │ │ ├── comment.js │ │ │ ├── commenter.js │ │ │ ├── employee.js │ │ │ ├── imageable.js │ │ │ ├── picture.js │ │ │ ├── post.js │ │ │ └── product.js │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── author.js │ │ │ ├── comment.js │ │ │ ├── commenter.js │ │ │ ├── employee.js │ │ │ ├── person.js │ │ │ ├── picture.js │ │ │ ├── post.js │ │ │ ├── product.js │ │ │ └── supervisor.js │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ ├── admin │ │ │ │ ├── create.js │ │ │ │ ├── edit.js │ │ │ │ └── index.js │ │ │ ├── employees.js │ │ │ ├── index.js │ │ │ ├── pictures.js │ │ │ ├── post.js │ │ │ ├── post │ │ │ │ └── detail.js │ │ │ └── products.js │ │ ├── serializers │ │ │ ├── author.js │ │ │ ├── comment.js │ │ │ ├── commenter.js │ │ │ ├── employee.js │ │ │ ├── imageable.js │ │ │ ├── picture.js │ │ │ ├── post.js │ │ │ └── product.js │ │ ├── services │ │ │ ├── authors.js │ │ │ ├── commenters.js │ │ │ ├── comments.js │ │ │ ├── employees.js │ │ │ ├── imageables.js │ │ │ ├── pictures.js │ │ │ ├── posts.js │ │ │ └── products.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── admin │ │ │ ├── create.hbs │ │ │ ├── edit.hbs │ │ │ └── index.hbs │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── form-post.hbs │ │ │ ├── employees.hbs │ │ │ ├── employees │ │ │ └── detail.hbs │ │ │ ├── index.hbs │ │ │ ├── pictures.hbs │ │ │ ├── pictures │ │ │ └── detail.hbs │ │ │ ├── post.hbs │ │ │ ├── post │ │ │ ├── comments.hbs │ │ │ └── detail.hbs │ │ │ ├── products.hbs │ │ │ └── products │ │ │ └── detail.hbs │ ├── config │ │ └── environment.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ ├── resources.js │ └── start-app.js ├── index.html ├── test-helper.js └── unit │ ├── .gitkeep │ ├── adapters │ └── application-test.js │ ├── initializers │ ├── model-setup-test.js │ └── store-test.js │ ├── mixins │ ├── authorization-test.js │ ├── resource-operations-test.js │ ├── service-cache-test.js │ └── transforms-test.js │ ├── models │ └── resource-test.js │ ├── serializers │ └── application-test.js │ ├── services │ └── store-test.js │ └── utils │ ├── attr-test.js │ ├── is-test.js │ ├── to-many-test.js │ ├── to-one-test.js │ ├── transform-map-test.js │ └── transforms-test.js ├── vendor └── .gitkeep ├── yarn.lock └── yuidoc.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log* 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esversion": 6, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /node-tests 6 | /tmp 7 | **/.gitkeep 8 | .bowerrc 9 | .editorconfig 10 | .ember-cli 11 | .gitignore 12 | .jshintrc 13 | .watchmanconfig 14 | .travis.yml 15 | bower.json 16 | ember-cli-build.js 17 | testem.js 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "4" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - $HOME/.npm 11 | - $HOME/.cache # includes bowers cache 12 | 13 | env: 14 | # we recommend testing LTS's and latest stable release (bonus points to beta/canary) 15 | - EMBER_TRY_SCENARIO=ember-lts-2.4 16 | - EMBER_TRY_SCENARIO=ember-lts-2.8 17 | - EMBER_TRY_SCENARIO=ember-release 18 | - EMBER_TRY_SCENARIO=ember-beta 19 | - EMBER_TRY_SCENARIO=ember-canary 20 | 21 | matrix: 22 | fast_finish: true 23 | allow_failures: 24 | - env: EMBER_TRY_SCENARIO=ember-canary 25 | 26 | before_install: 27 | - npm config set spin false 28 | - npm install -g bower 29 | - bower --version 30 | - npm install phantomjs-prebuilt 31 | - node_modules/phantomjs-prebuilt/bin/phantomjs --version 32 | 33 | install: 34 | - npm install 35 | - bower install 36 | 37 | script: 38 | # Usually, it's ok to finish the test scenario without reverting 39 | # to the addon's original dependency state, skipping "cleanup". 40 | - ember try:one $EMBER_TRY_SCENARIO test --skip-cleanup 41 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember JSON API Resources 2 | 3 | A stand-alone data persistence solution as an addon for [Ember.js] applications 4 | built using [Ember CLI]. It follows the [JSON API] 1.0 specification (your 5 | anti-bikeshedding weapon for API development). 6 | 7 | A thin data layer, a 1:1 solution using the JSON API spec, which does not 8 | attempt to solve "all the things". 9 | 10 | By considering this equation **e = mc2** 11 | 12 | > “Errors = (More Code)2” 13 | 14 | …The "EJR" addon is a lightweight library that simply focuses on one solid 15 | specification, and follows common patterns for data persistence in Ember apps. 16 | 17 | * [ember-jsonapi-resources] website 18 | * [The Guide](https://pixelhandler.gitbooks.io/ember-jsonapi-resources-guide/content/) 19 | * [API Docs][generated docs] 20 | * [Example App] 21 | * [Cookbooks / Wiki][Wiki Guide] 22 | 23 | [![Build Status](https://travis-ci.org/pixelhandler/ember-jsonapi-resources.svg?branch=master)](https://travis-ci.org/pixelhandler/ember-jsonapi-resources) 24 | [![Ember Observer Score](http://emberobserver.com/badges/ember-jsonapi-resources.svg)](http://emberobserver.com/addons/ember-jsonapi-resources) 25 | [![npm](https://img.shields.io/npm/dm/ember-jsonapi-resources.svg)](https://www.npmjs.com/package/ember-jsonapi-resources) 26 | [![npm](https://img.shields.io/npm/v/ember-jsonapi-resources.svg)](https://www.npmjs.com/package/ember-jsonapi-resources) 27 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pixelhandler/ember-jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 28 | 29 | 30 | ## Contributing / Development 31 | 32 | Clone the repo, install the dependencies: 33 | 34 | * `git clone` this repository 35 | * `npm install` 36 | * `bower install` 37 | 38 | ### Running 39 | 40 | To run the app in /tests/dummy use a proxy url for a live API 41 | 42 | * `ember server`, an http-proxy is setup to use (dev) localhost:3000 or (prod) api.pixelhandler.com 43 | * Visit . 44 | 45 | ### Running Tests 46 | 47 | * `ember test` 48 | * `ember test --server` 49 | * `ember test --server -m 'Unit | Mixin | service cache'` 50 | * `ember test --server --filter 'cacheUpdate'` 51 | * `npm run nodetest` tests for blueprint, e.g. `jsonapi-resource` 52 | 53 | A good way to get to know more about how this addon works is to review the tests, 54 | see source code for the unit tests: [tests/unit](tests/unit). 55 | 56 | ### Building 57 | 58 | * `ember build` 59 | 60 | For more information on using ember-cli, visit [https://www.ember-cli.com/][Ember CLI] 61 | 62 | ## Documentation 63 | 64 | Online documentation, build from source: [generated docs] 65 | 66 | Docs are generated from source using [yuidoc]. 67 | 68 | To view the docs during development: 69 | 70 | * `yuidoc ./addon/* -c yuidoc.json --server 3333` (you can append a port number e.g. `--server 8888`, the default port is 3000) 71 | 72 | To generate docs for the gh-pages branch: 73 | 74 | * `yuidoc ./addon/* -c yuidoc.json` 75 | 76 | [Ember CLI]: https://www.ember-cli.com/ 77 | [Ember.js]: http://emberjs.com 78 | [ember-jsonapi-resources]: https://pixelhandler.github.io/ember-jsonapi-resources/ 79 | [Example App]: https://github.com/pixelhandler/jr-test 80 | [generated docs]: http://pixelhandler.github.io/ember-jsonapi-resources/docs 81 | [JSON API]: http://jsonapi.org 82 | [Wiki Guide]: https://github.com/pixelhandler/ember-jsonapi-resources/wiki 83 | [yuidoc]: https://github.com/yui/yuidoc 84 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/addon/.gitkeep -------------------------------------------------------------------------------- /addon/initializers/model-setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule initializers 4 | **/ 5 | 6 | /** 7 | Initializer for the model factories, registers option to not initialize 8 | 9 | @method initialize 10 | @for Resource 11 | */ 12 | export function initialize(application) { 13 | if (typeof application.registerOptionsForType === 'function') { 14 | let options = { instantiate: false, singleton: false }; 15 | application.registerOptionsForType('model', options); 16 | } 17 | } 18 | 19 | export default { 20 | name: 'model-setup', 21 | initialize 22 | }; 23 | -------------------------------------------------------------------------------- /addon/initializers/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule initializers 4 | **/ 5 | 6 | import StoreService from 'ember-jsonapi-resources/services/store'; 7 | 8 | /** 9 | Initializer for the store service, injects into the route and controller 10 | 11 | @method initialize 12 | @for StoreService 13 | @requires StoreService 14 | */ 15 | export function initialize() { 16 | // see http://emberjs.com/deprecations/v2.x/#toc_initializer-arity 17 | let application = arguments[1] || arguments[0]; 18 | const store = 'service:store'; 19 | application.register(store, StoreService); 20 | application.inject('route', 'store', store); 21 | application.inject('controller', 'store', store); 22 | } 23 | 24 | export default { 25 | name: 'store', 26 | initialize: initialize 27 | }; 28 | -------------------------------------------------------------------------------- /addon/mixins/adapter-api-host-proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule adapter-api-host-proxy-mixin 4 | **/ 5 | import Ember from 'ember'; 6 | 7 | const { getOwner } = Ember; 8 | 9 | /** 10 | Mixin to provide url rewrite for proxied api. Mostly used as example. 11 | 12 | @class AdapterApiHostProxyMixin 13 | @static 14 | */ 15 | export default Ember.Mixin.create({ 16 | fetchUrl: function(url) { 17 | const config = getOwner(this).resolveRegistration('config:environment'); 18 | const proxy = config.APP.API_HOST_PROXY; 19 | const host = config.APP.API_HOST; 20 | if (proxy && host) { 21 | url = url.replace(host, proxy); 22 | } 23 | return url; 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /addon/mixins/authorization.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule authorization 4 | **/ 5 | 6 | import Ember from 'ember'; 7 | 8 | /** 9 | A Mixin class for storage of credential/token uses with a HTTP Authorization request-header 10 | 11 | The default solution is to use localStorage['AuthorizationHeader'] for the credential 12 | 13 | @class AuthorizationMixin 14 | @static 15 | */ 16 | export default Ember.Mixin.create({ 17 | 18 | /** 19 | The name of the Authorization request-header field 20 | 21 | @property authorizationHeaderField 22 | @type String 23 | @required 24 | */ 25 | authorizationHeaderField: 'Authorization', 26 | 27 | /** 28 | The name key, stored locally, that references the Authorization request-header credential/token 29 | 30 | @property authorizationHeaderStorageKey 31 | @type String 32 | @required 33 | */ 34 | authorizationHeaderStorageKey: 'AuthorizationHeader', 35 | 36 | /** 37 | Authentication credentials/token used with HTTP authentication 38 | 39 | @property authorizationCredential 40 | @type String 41 | @required 42 | */ 43 | authorizationCredential: Ember.computed({ 44 | get(key) { 45 | key = this.get('authorizationHeaderStorageKey'); 46 | return window[this._storage].getItem(key); 47 | }, 48 | set(key, value) { 49 | key = this.get('authorizationHeaderStorageKey'); 50 | window[this._storage].setItem(key, value); 51 | return value; 52 | } 53 | }), 54 | 55 | /** 56 | When using the FetchMixin and using ajax instead of fetch, setup XHR 57 | beforeSend with Authorization Header 58 | 59 | @method ajaxPrefilter 60 | */ 61 | ajaxPrefilter: Ember.on('init', function () { 62 | if (!this.get('useAjax') || this.get('useFetch')) { return; } 63 | Ember.$.ajaxPrefilter(function(options) { 64 | let key = this.get('authorizationHeaderStorageKey'); 65 | let field = this.get('authorizationHeaderField'); 66 | let token = window[this._storage].getItem(key); 67 | options.xhrFields = { withCredentials: true }; 68 | options.beforeSend = function (xhr) { 69 | xhr.setRequestHeader(field, token); 70 | }; 71 | }.bind(this)); 72 | }), 73 | 74 | /** 75 | Storage type localStorage or sessionStorage 76 | 77 | @property _storage 78 | @type String 79 | @private 80 | */ 81 | _storage: ['localStorage', 'sessionStorage'][0] 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /addon/mixins/resource-operations.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule resource-operations 4 | **/ 5 | import Ember from 'ember'; 6 | 7 | /** 8 | Mixin to provide interations between a Resource instance and service/adapter. 9 | 10 | @class ResourceOperationsMixin 11 | @static 12 | */ 13 | export default Ember.Mixin.create({ 14 | /** 15 | The service object for the entity (adapter with cache and serializer) 16 | 17 | @property service 18 | @type Object 19 | @required 20 | */ 21 | service: Ember.required, 22 | 23 | /** 24 | Create a new resource, calls service to persist new model 25 | 26 | - See: 27 | 28 | Calling `this.createResource` will call the service to persist the new model, 29 | via it's `createResource` method. 30 | 31 | @method createResource 32 | @return {Promise} 33 | */ 34 | createResource() { 35 | return this.get('service').createResource(this); 36 | }, 37 | 38 | /** 39 | Update a persistend resource, calls service to persist changes 40 | 41 | - See: 42 | 43 | Calling `this.updateResource` will call the service to persist the changes, 44 | via it's `updateResource` method. 45 | 46 | @method updateResource 47 | @return {Promise} 48 | */ 49 | updateResource() { 50 | return this.get('service').updateResource(this); 51 | }, 52 | 53 | /** 54 | Delete a persistend resource, calls service to DELETE via API request 55 | 56 | - See: 57 | 58 | Calling `this.deleteResource` will call the service to remove the destroy, 59 | via it's `deleteResource` method. 60 | 61 | @method deleteResource 62 | @return {Promise} 63 | */ 64 | deleteResource() { 65 | return this.get('service').deleteResource(this); 66 | }, 67 | 68 | /** 69 | Create a relationship for a `to-many` relation, calls service to persist. 70 | 71 | See: 72 | 73 | Calling `this.createRelationship` will call the service to persist the changes, 74 | via it's `createRelationship` method. Since the default `catch` for this 75 | method is to rollback the relationships, an optional `errorCallback` function 76 | can be used to handle the error response. 77 | 78 | @method createRelationship 79 | @param {String} relationship name (plural) to find the url 80 | @param {String} id of the related resource 81 | @param {Function} errorCallback `function (error) {}` 82 | @return {Promise} 83 | */ 84 | createRelationship(relationship, id, errorCallback) { 85 | this.addRelationship(relationship, id); 86 | return this.get('service').createRelationship(this, relationship, id) 87 | .catch(function (error) { 88 | this.removeRelationship(relationship, id); 89 | if (typeof errorCallback === 'function') { 90 | errorCallback(error); 91 | } else { 92 | Ember.Logger.error(error); 93 | } 94 | }.bind(this)); 95 | }, 96 | 97 | /** 98 | Update a relationship, works with both `to-many` and `to-one`. Primarily use 99 | with `to-one` and `to-many` is for a full replacement only. 100 | 101 | For a `to-one` relationship, add, replace or remove, and persist the change 102 | using the service. With an id the relation will be added or changed, with 103 | `null` a relationship will be removed. 104 | 105 | See: 106 | 107 | For `to-many` relationships the backend will need to support editing as a set, 108 | full replacement (most often that may be disabled). The list of all related 109 | resource identifiers will be sent to the server as a replace operation. 110 | 111 | Update a relationship by adding or removing using a list, id, or null. When 112 | adding an id for a to-many relationship send one or more ids, include the 113 | existing ids as well. When removing from a to-many relationship pass the ids 114 | that should remain, missing ids will be removed, or remove all with an empty 115 | array. 116 | 117 | Calling `this.updateRelationship` will call the service to persist the changes, 118 | via it's `patchRelationship` method. Since the default `catch` for this 119 | method is to rollback the relationships, an optional `errorCallback` function 120 | can be used to handle the error response. 121 | 122 | @method updateRelationship 123 | @param {String} relationship name 124 | @param {Array|String|null} ids can be only one id or null to remove 125 | @param {Function} errorCallback `function (error) {}` 126 | @return {Promise} 127 | */ 128 | updateRelationship(relationship, ids, errorCallback) { 129 | let related = this.get(relationship); 130 | let rollback; 131 | if (related.kind === 'toOne') { 132 | rollback = related.get('id'); 133 | } else if (related.kind === 'toMany') { 134 | rollback = related.mapBy('id'); 135 | } 136 | this._replaceRelationshipsData(relationship, ids); 137 | return this.get('service').patchRelationship(this, relationship) 138 | .catch(function (error) { 139 | this._updateRelationshipsData(relationship, rollback); 140 | if (typeof errorCallback === 'function') { 141 | errorCallback(error); 142 | } else { 143 | Ember.Logger.error(error); 144 | } 145 | }.bind(this)); 146 | }, 147 | 148 | /** 149 | Deletes a relationship for `to-many` relation, calls service to persist. 150 | 151 | See: 152 | 153 | Calling `this.deleteRelationship` will call the service to persist the changes, 154 | via it's `deleteRelationship` method. Since the default `catch` for this 155 | method is to rollback the relationships, an optional `errorCallback` function 156 | can be used to handle the error response. 157 | 158 | @method deleteRelationship 159 | @param {String} relationship name (plural) to find the url 160 | @param {String} id of the related resource 161 | @param {Function} errorCallback `function (error) {}` 162 | @return {Promise} 163 | */ 164 | deleteRelationship(relationship, id, errorCallback) { 165 | this.removeRelationship(relationship, id); 166 | return this.get('service').deleteRelationship(this, relationship, id) 167 | .catch(function (error) { 168 | this.addRelationship(relationship, id); 169 | if (typeof errorCallback === 'function') { 170 | errorCallback(error); 171 | } else { 172 | Ember.Logger.error(error); 173 | } 174 | }.bind(this)); 175 | }, 176 | 177 | _updateRelationshipsData: Ember.required 178 | }); 179 | -------------------------------------------------------------------------------- /addon/mixins/service-cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule cache 4 | **/ 5 | 6 | import Ember from 'ember'; 7 | 8 | /** 9 | A Mixin class for caching JSON API resource objects 10 | 11 | @class ServiceCacheMixin 12 | @requires Ember.Inflector 13 | @static 14 | */ 15 | export default Ember.Mixin.create({ 16 | 17 | /** 18 | Cache object 19 | 20 | @property cache 21 | @type Object 22 | */ 23 | cache: null, 24 | 25 | /** 26 | Initialize a cache object 27 | 28 | @method initCache 29 | */ 30 | initCache: Ember.on('init', function () { 31 | this.cache = { 32 | meta: null, 33 | data: Ember.ArrayProxy.create({ content: Ember.A([]) }) 34 | }; 35 | }), 36 | 37 | /** 38 | Store response object(s) in the cache 39 | 40 | @method cacheResource 41 | @param {Object} resp w/ props: {Object} meta, {Array|Object} data, & {Object} headers 42 | */ 43 | cacheResource(resp) { 44 | this.cacheMeta(resp); 45 | this.cacheData(resp); 46 | }, 47 | 48 | /** 49 | Store meta data in the cache 50 | 51 | @method cacheMeta 52 | @param {Object} resp w/ props: {Object} meta, {Array|Object} data, & {Object} headers 53 | */ 54 | cacheMeta(resp) { 55 | if (resp.meta) { 56 | this.set('cache.meta', resp.meta); 57 | } 58 | }, 59 | 60 | /** 61 | Store resource objects in the `data` array of the cache 62 | 63 | @method cacheData 64 | @param {Object} resp w/ props: {Object} meta, {Array|Object} data, & {Object} headers 65 | */ 66 | cacheData(resp) { 67 | const ids = this.cache.data.mapBy('id'); 68 | if (Array.isArray(resp.data)) { 69 | if (ids.length === 0) { 70 | for (let i = 0; i < resp.data.length; i++) { 71 | this.cacheControl(resp.data[i], resp.headers); 72 | } 73 | this.cache.data.pushObjects(resp.data); 74 | } else { 75 | this.cacheUpdate(resp); 76 | } 77 | } else if (typeof resp === 'object') { 78 | if (ids.length === 0) { 79 | this.cache.data.pushObject(resp.data); 80 | this.cacheControl(resp.data, resp.headers); 81 | } else { 82 | if (ids.indexOf(resp.data.get('id')) === -1) { 83 | this.cache.data.pushObject(resp.data); 84 | } else { 85 | this.cacheUpdate(resp); 86 | } 87 | } 88 | } 89 | }, 90 | 91 | /** 92 | Add or update cache data 93 | 94 | @method cacheUpdate 95 | @param {Object} resp w/ props: {Object} meta, {Array|Object} data, & {Object} headers 96 | */ 97 | cacheUpdate(resp) { 98 | const ids = this.cache.data.mapBy('id'); 99 | if (!Array.isArray(resp.data) && typeof resp.data === 'object') { 100 | resp.data = [ resp.data ]; 101 | } 102 | 103 | for (let i = 0; i < resp.data.length; i++) { 104 | let data = resp.data[i]; 105 | let id = data.id || data.get('id'); 106 | let index = ids.indexOf(id); 107 | let isCached = index !== -1; 108 | let isResourceType = data.toString().indexOf('JSONAPIResource') > -1; 109 | let resource = isResourceType ? data : null; 110 | 111 | // Given Resources we can add or replace, 112 | // otherwise we can only update cache by id. 113 | if (isCached && isResourceType) { // cached resource -> replace 114 | this.cache.data.replaceContent(index, 1, resource); 115 | } else if (isResourceType) { // non-cached resource -> add 116 | this.cache.data.pushObject(resource); 117 | } else if (isCached) { // id found in cache -> update 118 | resource = this.cache.data.findBy('id', id); 119 | resource.didUpdateResource(data); 120 | } 121 | 122 | if (resource) { 123 | this.cacheControl(resource, resp.headers); 124 | } 125 | } 126 | }, 127 | 128 | /** 129 | Store meta from headers on resource meta, window.fetch includes 130 | a headers object in the response use `headers.get` to lookup data 131 | from the headers for cache-control, date, and etag. 132 | 133 | @method cacheControl 134 | @param {Resource} resource 135 | @param {Object} headers 136 | */ 137 | cacheControl(resource, headers) { 138 | resource.set('meta.timeStamps', { local: Date.now() }); 139 | if (headers && typeof headers.get === 'function') { 140 | let date = headers.get('date'); 141 | if (date) { 142 | resource.set('meta.timeStamps.server', date); 143 | } 144 | let cacheControl = headers.get('cache-control'); 145 | if (cacheControl) { 146 | resource.set('meta.cacheControl', cacheControl); 147 | } 148 | let etag = headers.get('etag'); 149 | if (etag) { 150 | resource.set('meta.etag', etag); 151 | } 152 | } 153 | }, 154 | 155 | /** 156 | Lookup a resource from cached data 157 | 158 | @method cacheLookup 159 | @param {String} id 160 | @return {Resource|undefined} 161 | */ 162 | cacheLookup(id) { 163 | return this.cache.data.find(function(resource) { 164 | const isExpired = resource.get('isCacheExpired'); 165 | if (isExpired) { 166 | Ember.run.next(this.cache.data, 'removeObject', resource); 167 | } 168 | return resource.get('id') === id && !isExpired; 169 | }.bind(this)); 170 | }, 171 | 172 | /** 173 | Remove a resource from cached data 174 | 175 | @method cacheRemove 176 | @param {Resource} resource 177 | */ 178 | cacheRemove(resource) { 179 | this.cache.data.removeObject(resource); 180 | } 181 | }); 182 | -------------------------------------------------------------------------------- /addon/mixins/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule transforms 4 | **/ 5 | 6 | import Ember from 'ember'; 7 | import { dateTransform } from 'ember-jsonapi-resources/utils/transforms'; 8 | 9 | /** 10 | A Mixin class for methods to transform resource attributes, includes date 11 | attribute methods to serialize and deserialize the date(time) to/from 12 | ISO Format for use with `attr('date')` 13 | 14 | Any valid attribute type (string, boolean, number, object, array, date) can 15 | be added to your app, just generate a transforms mixin and define other 16 | types if needed, and use the type when defining a resource attribute, 17 | e.g. attr('array') 18 | 19 | @class TransformsMixin 20 | @static 21 | */ 22 | export default Ember.Mixin.create({ 23 | /** 24 | @method serializeDateAttribute 25 | @param {Date|String} date 26 | @return {String|Null} date value as ISO String for JSON payload, or null 27 | */ 28 | serializeDateAttribute(date) { 29 | return dateTransform.serialize(date); 30 | }, 31 | 32 | /** 33 | @method deserializeDateAttribute 34 | @param {String} date usually in ISO format, must be a valid argument for Date 35 | @return {Date|Null} date value from JSON payload, or null 36 | */ 37 | deserializeDateAttribute(date) { 38 | return dateTransform.deserialize(date); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /addon/services/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule store 4 | **/ 5 | 6 | import Ember from 'ember'; 7 | import { pluralize } from 'ember-inflector'; 8 | 9 | /** 10 | Service for a JSON API endpoint a facade to your resource adapter 11 | 12 | @class StoreService 13 | @requires Ember.Inflector 14 | @static 15 | */ 16 | export default Ember.Service.extend({ 17 | 18 | /** 19 | Find resource(s) using an id or a using a query `{id: '', query: {}}` 20 | 21 | @method find 22 | @param {String} type the entity or resource name will be pluralized unless a `{singleton: true}` option is passed 23 | @param {Object|String} options object or (string) id 24 | @return {Promise} 25 | */ 26 | find(type, options) { 27 | let service = this._service(type, options); 28 | return service.find(options); 29 | }, 30 | 31 | /** 32 | Access to the cached object 33 | 34 | @method all 35 | @param {String} type the entity or resource name will be pluralized 36 | @return {Ember.Array} 37 | */ 38 | all(type) { 39 | let service = this._service(type); 40 | return (service.cache && service.cache.data) ? service.cache.data : Ember.A([]); 41 | }, 42 | 43 | /** 44 | Create a new resource, sends a POST request 45 | 46 | @method createResource 47 | @param {String} type the entity or resource name will be pluralized 48 | @param {Resource} resource instance to serialize 49 | @return {Promise} 50 | */ 51 | createResource(type, resource) { 52 | let service = this._service(type); 53 | return service.createResource(resource); 54 | }, 55 | 56 | /** 57 | Patch an existing resource, sends a PATCH request. 58 | 59 | @method updateResource 60 | @param {String} type the entity or resource name will be pluralized 61 | @param {Resource} resource instance to serialize the changed attributes 62 | @param {Array} includeRelationships (optional) list of {String} relationships 63 | to opt-into an update 64 | @return {Promise} 65 | */ 66 | updateResource(type, resource, includeRelationships = false) { 67 | let service = this._service(type); 68 | return service.updateResource(resource, includeRelationships); 69 | }, 70 | 71 | /** 72 | Delete an existing resource, sends a DELETE request 73 | 74 | @method deleteResource 75 | @param {String} type the entity or resource name will be pluralized 76 | @param {String|Resource} resource name (plural) or instance with self link 77 | @return {Promise} 78 | */ 79 | deleteResource(type, resource) { 80 | let service = this._service(type); 81 | return service.deleteResource(resource); 82 | }, 83 | 84 | /** 85 | Create (add) a relationship for `to-many` relation, sends a POST request. 86 | 87 | See: 88 | 89 | Adds a relation using a payload with a resource identifier object: 90 | 91 | ``` 92 | { 93 | "data": [ 94 | { "type": "comments", "id": "12" } 95 | ] 96 | } 97 | ``` 98 | 99 | @method createRelationship 100 | @param {String} type the entity or resource name will be pluralized 101 | @param {Resource} resource instance, has URLs via it's relationships property 102 | @param {String} relationship name (plural) to find the url from the resource instance 103 | @param {String} id of the related resource 104 | @return {Promise} 105 | */ 106 | createRelationship(type, resource, relationship, id) { 107 | let service = this._service(type); 108 | return service.createRelationship(resource, relationship, id); 109 | }, 110 | 111 | /** 112 | Patch a relationship, either adds or removes everyting, sends a PATCH request. 113 | 114 | See: 115 | 116 | For `to-one` relation: 117 | 118 | - Remove (delete) with payload: `{ "data": null }` 119 | - Create/Update with payload: 120 | ``` 121 | { 122 | "data": { "type": "comments", "id": "1" } 123 | } 124 | ``` 125 | 126 | For `to-many` relation: 127 | 128 | - Remove (delete) all with payload: `{ "data": [] }` 129 | - Replace all with payload: 130 | ``` 131 | { 132 | "data": [ 133 | { "type": "comments", "id": "1" }, 134 | { "type": "comments", "id": "2" } 135 | ] 136 | } 137 | ``` 138 | 139 | @method patchRelationship 140 | @param {String} type the entity or resource name will be pluralized 141 | @param {Resource} resource instance, has URLs via it's relationships property 142 | @param {String} relationship name (plural) to find the url from the resource instance 143 | @return {Promise} 144 | */ 145 | patchRelationship(type, resource, relationship) { 146 | let service = this._service(type); 147 | return service.patchRelationship(resource, relationship); 148 | }, 149 | 150 | /** 151 | Deletes a relationship for `to-many` relation, sends a DELETE request. 152 | 153 | See: 154 | 155 | Remove using a payload with the resource identifier object: 156 | 157 | For `to-many`: 158 | 159 | ``` 160 | { 161 | "data": [ 162 | { "type": "comments", "id": "1" } 163 | ] 164 | } 165 | ``` 166 | 167 | @method deleteRelationship 168 | @param {String} type the entity or resource name will be pluralized 169 | @param {Resource} resource instance, has URLs via it's relationships property 170 | @param {String} relationship name (plural) to find the url from the resource instance 171 | @param {String} id of the related resource 172 | @return {Promise} 173 | */ 174 | deleteRelationship(type, resource, relationship, id) { 175 | let service = this._service(type); 176 | return service.deleteRelationship(resource, relationship, id); 177 | }, 178 | 179 | /** 180 | Lookup the injected service for a resource, pluralize type arg. 181 | 182 | @private 183 | @method _service 184 | @param {String} type - the entity or resource name will be pluralized unless a `{singleton: true}` option is passed 185 | @param {Object} options (object) 186 | */ 187 | _service(type, options = {}) { 188 | if (!options.singleton) { 189 | type = pluralize(type); 190 | } 191 | if (!this[type]) { 192 | throw new Error(type + ' service not initialized'); 193 | } 194 | return this[type]; 195 | } 196 | }); 197 | -------------------------------------------------------------------------------- /addon/utils/attr.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | @main attr 5 | **/ 6 | 7 | import Ember from 'ember'; 8 | import { isBlank, isDasherized, isType } from 'ember-jsonapi-resources/utils/is'; 9 | 10 | /** 11 | Utility helper to setup a computed property for a resource attribute, imported and 12 | exported with the resource submodule. 13 | 14 | An `attr` of the resource is a computed property to the actual attribute in an 15 | `attributes` hash on the `resource` (model) instance. Using `attr()` supports 16 | any type, and an optional `type` (String) argument can be used to enforce 17 | setting and getting with a specific type. `'string'`, `'number'`, `'boolean'`, 18 | `'date'`, `'object'`, and `'array'` are all valid types for attributes. 19 | 20 | Use `attr()`, with optional type argument, to compose your model attributes, e.g: 21 | 22 | ```js 23 | import Ember from 'ember'; 24 | import Resource from 'ember-jsonapi-resources/models/resource'; 25 | import { attr, toOne, toMany } from 'ember-jsonapi-resources/models/resource'; 26 | 27 | export default Resource.extend({ 28 | type: 'articles', 29 | service: Ember.inject.service('articles'), 30 | 31 | title: attr('string'), 32 | published: attr('date'), 33 | tags: attr('array'), 34 | footnotes: attr('object'), 35 | revisions: attr() 36 | version: attr('number'), 37 | "is-approved": attr('boolean') 38 | }); 39 | ``` 40 | 41 | @method attr 42 | @for Resource 43 | @final 44 | @param {String} [type] an optional param for the type of property, i.e. `string`, 45 | `number`, `boolean`, `date`, `object`, or `array` 46 | @param {Boolean} [mutable=true] optional param, defaults to `true` if not passed 47 | @return {Object} computed property 48 | */ 49 | export default function attr(type = 'any', mutable = true) { 50 | const _mutable = mutable; 51 | if (type !== 'any' && !isBlank(type)) { 52 | assertValidTypeOption(type); 53 | } 54 | return Ember.computed('attributes', { 55 | get: function (key) { 56 | assertDasherizedAttr(key); 57 | let value = this.get('attributes.' + key); 58 | if (!isBlank(value)) { 59 | assertType.call(this, key, value); 60 | } 61 | return value; 62 | }, 63 | 64 | set: function (key, value) { 65 | const lastValue = this.get('attributes.' + key); 66 | if (!_mutable) { 67 | return immutableValue(key, value, lastValue); 68 | } 69 | if (value === lastValue) { return value; } 70 | assertType.call(this, key, value); 71 | this.set('attributes.' + key, value); 72 | if (!this.get('isNew')) { 73 | this._attributes[key] = this._attributes[key] || {}; 74 | if (this._attributes[key].previous === undefined) { 75 | this._attributes[key].previous = Ember.copy(lastValue, true); 76 | } 77 | this._attributes[key].changed = Ember.copy(value, true); 78 | let service = this.get('service'); 79 | if (service) { 80 | service.trigger('attributeChanged', this); 81 | } 82 | } 83 | return this.get('attributes.' + key); 84 | } 85 | }).meta({type: type, mutable: mutable}); 86 | } 87 | 88 | function assertValidTypeOption(type) { 89 | if (type === 'any') { return; } 90 | let allowed = 'string number boolean date object array'; 91 | let msg = 'Allowed types are: ' + allowed + ' however ' + type + ' was given instead.'; 92 | Ember.assert(msg, allowed.split(' ').indexOf(type) > -1); 93 | } 94 | 95 | function assertDasherizedAttr(name) { 96 | try { 97 | let attrName = Ember.String.dasherize(name); 98 | let msg = "Attributes are recommended to use dasherized names, e.g `'"+ attrName +"': attr()`"; 99 | msg += ", instead of `"+ name +": attr()`"; 100 | Ember.assert(msg, isDasherized(name)); 101 | } catch(e) { 102 | Ember.Logger.warn(e.message); 103 | } 104 | } 105 | 106 | function assertType(key, value) { 107 | let meta = this.constructor.metaForProperty(key); 108 | if (meta && meta.type && meta.type !== 'any') { 109 | let msg = this.toString() + '#' + key + ' is expected to be a ' + meta.type; 110 | Ember.assert(msg, isType(meta.type, value)); 111 | } 112 | } 113 | 114 | function immutableValue(key, value, lastValue) { 115 | let msg = [ 116 | this.toString(), '#', key, ' is not mutable set was called with ', 117 | '`', value, '`', ' but is previous set to `', lastValue, '`' 118 | ]; 119 | Ember.Logger.warn(msg.join('')); 120 | return lastValue; 121 | } 122 | -------------------------------------------------------------------------------- /addon/utils/is.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | **/ 5 | 6 | import Ember from 'ember'; 7 | 8 | /** 9 | Abstract class to transform mapped data structures 10 | 11 | @class Utils 12 | @static 13 | **/ 14 | export default class Utils { 15 | isBlank() { return isBlank(); } 16 | isDasherized() { return isDasherized(); } 17 | isType() { return isType(); } 18 | } 19 | 20 | /** 21 | @method isBlank 22 | @param {String} value 23 | @return {Boolean} 24 | */ 25 | export function isBlank(value) { 26 | return value === null || typeof value === 'undefined'; 27 | } 28 | 29 | /** 30 | @method isDasherized 31 | @param {String} name 32 | @return {Boolean} 33 | */ 34 | export function isDasherized(name) { 35 | return (Ember.String.dasherize(name) === name); 36 | } 37 | 38 | /** 39 | @method isType 40 | @param {String} type - e.g. string, date, error, boolean, etc. 41 | @param {Object} value - object to test the type 42 | @return {Boolean} 43 | */ 44 | export function isType(type, value) { 45 | type = Ember.String.capitalize(type); 46 | type = `[object ${type}]`; 47 | return Object.prototype.toString.call(value) === type; 48 | } 49 | -------------------------------------------------------------------------------- /addon/utils/related-proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | @main RelatedProxyUtil 5 | **/ 6 | 7 | import Ember from 'ember'; 8 | import RSVP from 'rsvp'; 9 | import { pluralize } from 'ember-inflector'; 10 | 11 | /** 12 | Utility for creating promise proxy objects for related resources 13 | 14 | @class RelatedProxyUtil 15 | @static 16 | @final 17 | */ 18 | const RelatedProxyUtil = Ember.Object.extend({ 19 | 20 | /** 21 | Checks for required `relationship` property 22 | 23 | @method init 24 | */ 25 | init: function () { 26 | this._super(); 27 | if (typeof this.get('relationship') !== 'string') { 28 | throw new Error('RelatedProxyUtil#init expects `relationship` property to exist.'); 29 | } 30 | return this; 31 | }, 32 | 33 | /** 34 | The name of the relationship 35 | 36 | @property resource 37 | @type String 38 | @required 39 | */ 40 | relationship: null, 41 | 42 | /** 43 | The name of the type of resource 44 | 45 | @property type 46 | @type String 47 | @required 48 | */ 49 | type: null, 50 | 51 | /** 52 | Proxy for the requested relation, resolves w/ content from fulfilled promise 53 | 54 | @method createProxy 55 | @param {Resource} resource 56 | @param {String} kind 'toMany' or 'toOne' 57 | @return {PromiseProxy|ObjectProxy|ArrayProxy} proxy instance, new resource uses mock relations 58 | */ 59 | createProxy(resource, kind) { 60 | let proxyFactory, newContent; 61 | if (kind === 'toMany') { 62 | proxyFactory = Ember.ArrayProxy; 63 | newContent = Ember.A([]); 64 | } else if (kind === 'toOne') { 65 | proxyFactory = Ember.ObjectProxy; 66 | newContent = Ember.Object.create(); 67 | } 68 | if (resource.get('isNew')) { 69 | return proxyFactory.create({ content: newContent }); 70 | } else { 71 | let proxy = this.proxySetup(resource, kind, proxyFactory); 72 | return this.proxyResolution(resource, proxy); 73 | } 74 | }, 75 | 76 | /** 77 | @method proxySetup 78 | @param {Resource} resource 79 | @param {String} kind 'toMany' or 'toOne' 80 | @param {Ember.ObjectProxy|Ember.ArrayProxy} proxyFactory 81 | @return {PromiseProxy} proxy 82 | */ 83 | proxySetup(resource, kind, proxyFactory) { 84 | let relation = this.get('relationship'); 85 | let type = this.get('type'); 86 | let url = this.proxyUrl(resource, relation); 87 | let owner = Ember.getOwner(resource); 88 | let service = owner.lookup('service:' + pluralize(type)); 89 | let promise = this.promiseFromCache(resource, relation, service); 90 | promise = promise || service.findRelated({'relation': relation, 'type': type}, url); 91 | let proxyProto = proxyFactory.extend(Ember.PromiseProxyMixin, { 92 | 'promise': promise, 'type': relation, 'kind': kind 93 | }); 94 | return proxyProto.create({ 95 | content: (kind === 'toOne') ? Ember.Object.create() : Ember.A([]) 96 | }); 97 | }, 98 | 99 | /** 100 | @method proxyResolution 101 | @param {proxy} resource 102 | @return {PromiseProxy} proxy 103 | */ 104 | proxyResolution(resource, proxy) { 105 | proxy.then( 106 | function (resources) { 107 | proxy.set('content', resources); 108 | let relation = proxy.get('type'); 109 | let kind = proxy.get('kind'); 110 | resource.didResolveProxyRelation(relation, kind, resources); 111 | return resources; 112 | }, 113 | function (error) { 114 | Ember.Logger.error(error); 115 | throw error; 116 | } 117 | ); 118 | return proxy; 119 | }, 120 | 121 | /** 122 | Proxy url to fetch for the resource's relation 123 | 124 | @method proxyUrl 125 | @param {Resource} resource 126 | @param {String} relation 127 | @return {PromiseProxy} proxy 128 | */ 129 | proxyUrl(resource, relation) { 130 | let related = linksPath(relation); 131 | let url = resource.get(related); 132 | if (typeof url !== 'string') { 133 | throw new Error('RelatedProxyUtil#_proxyUrl expects `model.'+ related +'` property to exist.'); 134 | } 135 | return url; 136 | }, 137 | 138 | /** 139 | Lookup relation from service cache and pomisify result 140 | 141 | @method promiseFromCache 142 | @param {Resource} resource 143 | @param {String} relation 144 | @param {Object} service 145 | @return {Promise|null} 146 | */ 147 | promiseFromCache(resource, relation, service) { 148 | let data = resource.get('relationships.' + relation + '.data'); 149 | if (!data) { return; } 150 | let content, found; 151 | if (Array.isArray(data)) { 152 | content = Ember.A([]); 153 | for (let i = 0; i < data.length; i++) { 154 | found = this.serviceCacheLookup(service, data[i]); 155 | if (found) { 156 | content.push(found); 157 | } 158 | } 159 | content = (data.length && data.length === content.length) ? content : null; 160 | } else { 161 | content = this.serviceCacheLookup(service, data); 162 | } 163 | return (content) ? RSVP.Promise.resolve(content) : null; 164 | }, 165 | 166 | /** 167 | Lookup data in service cache 168 | 169 | @method serviceCacheLookup 170 | @param {Object} service 171 | @param {Object} data 172 | @return {Resource|undefined} 173 | */ 174 | serviceCacheLookup(service, data) { 175 | return (typeof data === 'object' && data.id) ? service.cacheLookup(data.id) : undefined; 176 | } 177 | }); 178 | 179 | export default RelatedProxyUtil; 180 | 181 | /** 182 | @method linksPath 183 | @param {String} relation 184 | @return {String} path to the related link 185 | */ 186 | export function linksPath(relation) { 187 | return ['relationships', relation, 'links', 'related'].join('.'); 188 | } 189 | -------------------------------------------------------------------------------- /addon/utils/to-many.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | @main toMany 5 | **/ 6 | 7 | import Ember from 'ember'; 8 | import RelatedProxyUtil from 'ember-jsonapi-resources/utils/related-proxy'; 9 | import { linksPath } from 'ember-jsonapi-resources/utils/related-proxy'; 10 | import { isDasherized } from 'ember-jsonapi-resources/utils/is'; 11 | 12 | /** 13 | Helper to setup a has many relationship to another resource 14 | 15 | ```js 16 | let Author = Resource.extend({ 17 | type: 'authors', 18 | name: attr(), 19 | posts: toMany('posts') 20 | }); 21 | ``` 22 | 23 | Or, with an optional type to use instead of the resource's service 24 | 25 | ```js 26 | let Person = Resource.extend({ 27 | type: 'people', 28 | name: attr() 29 | }); 30 | 31 | let Supervisor = Person.extend({ 32 | type: 'supervisors', 33 | directReports: toMany({ resource: 'employees', type: 'people' }) 34 | }); 35 | ``` 36 | 37 | @method toMany 38 | @for Resource 39 | @final 40 | @param {String|Object} relation the name of the relationship 41 | @param {String} relation.resource the name of the relationship 42 | @param {String} relation.type the name of the type or service to use 43 | @return {Object} computed property 44 | */ 45 | export function toMany(relation) { 46 | let type = relation; 47 | if (typeof type === 'object') { 48 | assertResourceAndTypeProps(relation); 49 | type = relation.type; 50 | relation = relation.resource; 51 | } 52 | assertDasherizedHasManyRelation(relation); 53 | let kind = 'toMany'; 54 | let util = RelatedProxyUtil.create({'relationship': relation, 'type': type, kind: kind}); 55 | let path = linksPath(relation); 56 | return Ember.computed(path, function () { 57 | return util.createProxy(this, kind); 58 | }).meta({relation: relation, type: type, kind: kind}); 59 | } 60 | 61 | export let hasMany = toMany; 62 | 63 | function assertResourceAndTypeProps(relation) { 64 | try { 65 | let msg = 'Options must include properties: resource, type'; 66 | Ember.assert(msg, relation && relation.resource && relation.type); 67 | } catch(e) { 68 | Ember.Logger.warn(e.message); 69 | } 70 | } 71 | 72 | function assertDasherizedHasManyRelation(name) { 73 | try { 74 | let relationName = Ember.String.dasherize(name); 75 | let msg = " are recommended to use dasherized names, e.g `toMany('"+ relationName +"')`"; 76 | msg += ", instead of `toMany('"+ name +"')`"; 77 | Ember.assert(msg, isDasherized(name)); 78 | } catch(e) { 79 | Ember.Logger.warn(e.message); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /addon/utils/to-one.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | @main toOne 5 | **/ 6 | 7 | import Ember from 'ember'; 8 | import RelatedProxyUtil from 'ember-jsonapi-resources/utils/related-proxy'; 9 | import { linksPath } from 'ember-jsonapi-resources/utils/related-proxy'; 10 | import { isDasherized } from 'ember-jsonapi-resources/utils/is'; 11 | 12 | /** 13 | Helper to setup a has one relationship to another resource 14 | 15 | ```js 16 | let Employee = Person.extend({ 17 | type: 'employees', 18 | supervisor: toOne('supervisor') 19 | }); 20 | ``` 21 | 22 | Or, with an optional type to use instead of the resource's service 23 | 24 | ```js 25 | let Person = Resource.extend({ 26 | type: 'people', 27 | name: attr() 28 | }); 29 | 30 | let Employee = Person.extend({ 31 | type: 'employees', 32 | supervisor: toOne({ resource: 'supervisor', type: 'people' }) 33 | }); 34 | ``` 35 | 36 | @method toOne 37 | @for Resource 38 | @final 39 | @param {String|Object} relation the name of the relationship 40 | @param {String} relation.resource the name of the relationship 41 | @param {String} relation.type the name of the type or service to use 42 | @return {Object} computed property 43 | */ 44 | export function toOne(relation) { 45 | let type = relation; 46 | if (typeof type === 'object') { 47 | assertResourceAndTypeProps(relation); 48 | type = relation.type; 49 | relation = relation.resource; 50 | } 51 | assertDasherizedHasOneRelation(type); 52 | let kind = 'toOne'; 53 | let util = RelatedProxyUtil.create({relationship: relation, type: type, kind: kind}); 54 | let path = linksPath(relation); 55 | return Ember.computed(path, function () { 56 | return util.createProxy(this, kind); 57 | }).meta({relation: relation, type: type, kind: kind}); 58 | } 59 | 60 | export let hasOne = toOne; 61 | 62 | function assertResourceAndTypeProps(relation) { 63 | try { 64 | let msg = 'Options must include properties: resource, type'; 65 | Ember.assert(msg, relation && relation.resource && relation.type); 66 | } catch(e) { 67 | Ember.Logger.warn(e.message); 68 | } 69 | } 70 | 71 | function assertDasherizedHasOneRelation(name) { 72 | try { 73 | let relationName = Ember.String.dasherize(name); 74 | let msg = " are recommended to use dasherized names, e.g `toOne('"+ relationName +"')`"; 75 | msg += ", instead of `toOne('"+ name +"')`"; 76 | Ember.assert(msg, isDasherized(name)); 77 | } catch(e) { 78 | Ember.Logger.warn(e.message); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /addon/utils/transform-map.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | @main TransformMap 5 | **/ 6 | 7 | import { isBlank, isType } from './is'; 8 | 9 | /** 10 | Abstract class to transform mapped data structures 11 | 12 | @class TransformMap 13 | @constructor 14 | **/ 15 | export default class TransformMap { 16 | 17 | /** 18 | @method constructor 19 | @param {Object} [map] created with `null` as prototype 20 | **/ 21 | constructor(map) { 22 | this.map = map; 23 | this.keys = Object.keys(map); 24 | let inverse = Object.create(null); 25 | let values = []; 26 | let entries = []; 27 | let pair; 28 | for (let key in map) { 29 | values.push(map[key]); 30 | inverse[map[key]] = key; 31 | pair = Object.create(null); 32 | entries.push([key, map[key]]); 33 | } 34 | Object.freeze(inverse); 35 | this.values = values; 36 | this.inverse = inverse; 37 | this.entries = entries; 38 | } 39 | 40 | /** 41 | @method lookup 42 | @param {String} [value] 43 | @parm {String} [use='keys'] keys or values 44 | @return {String|Null} [value] name or null 45 | */ 46 | lookup(value, use = 'keys') { 47 | if (isBlank(value) || value === '') { 48 | value = null; 49 | } else if (isType('string', value)) { 50 | if (this[use].indexOf(value) > -1) { 51 | if (use === 'keys') { 52 | value = this.map[value]; 53 | } else if (use === 'values') { 54 | value = this.inverse[value]; 55 | } 56 | } 57 | } 58 | return (value) ? value : null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /addon/utils/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | @module ember-jsonapi-resources 3 | @submodule utils 4 | @main dateTransform 5 | **/ 6 | 7 | import { isBlank, isType } from 'ember-jsonapi-resources/utils/is'; 8 | 9 | /** 10 | @class TransformDateAttribute 11 | @constructor 12 | */ 13 | class TransformDateAttribute { 14 | 15 | /** 16 | @method serialize 17 | @param {Date|String} date 18 | @return {String|Null} date value as ISO String for JSON payload, or null 19 | */ 20 | serialize(date) { 21 | if (isBlank(date) || date === '') { 22 | date = null; 23 | } else if (isType('date', date)) { 24 | date = date.toISOString(); 25 | } else if (isType('string', date)) { 26 | date = new Date(date); 27 | } 28 | return (date) ? date : null; 29 | } 30 | 31 | /** 32 | @method deserialize 33 | @param {String} date usually in ISO format, must be a valid argument for Date 34 | @return {Date|Null} date value from JSON payload, or null 35 | */ 36 | deserialize(date) { 37 | if (isBlank(date)) { 38 | date = null; 39 | } else if (isType('string', date) || isType('number', date)) { 40 | date = new Date(date); 41 | } 42 | return (date) ? date : null; 43 | } 44 | 45 | } 46 | 47 | /** 48 | @final 49 | */ 50 | export let dateTransform = new TransformDateAttribute(); 51 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/app/.gitkeep -------------------------------------------------------------------------------- /app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import AuthorizationMixin from 'ember-jsonapi-resources/mixins/authorization'; 2 | import ApplicationAdapter from 'ember-jsonapi-resources/adapters/application'; 3 | 4 | /** 5 | Adapter for a JSON API endpoint, combines the addon ApplicationAdapter and AuthorizationMixin 6 | 7 | @class ApplicationAdapter 8 | @uses AuthorizationMixin 9 | */ 10 | export default ApplicationAdapter.extend(AuthorizationMixin); 11 | -------------------------------------------------------------------------------- /app/initializers/model-setup.js: -------------------------------------------------------------------------------- 1 | export { default, initialize } from 'ember-jsonapi-resources/initializers/model-setup'; 2 | -------------------------------------------------------------------------------- /app/initializers/store.js: -------------------------------------------------------------------------------- 1 | export { default, initialize } from 'ember-jsonapi-resources/initializers/store'; 2 | -------------------------------------------------------------------------------- /app/mixins/authorization.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-jsonapi-resources/mixins/authorization'; 2 | -------------------------------------------------------------------------------- /app/mixins/service-cache.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-jsonapi-resources/mixins/service-cache'; 2 | -------------------------------------------------------------------------------- /app/mixins/transforms.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-jsonapi-resources/mixins/transforms'; 2 | -------------------------------------------------------------------------------- /app/models/resource.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-jsonapi-resources/models/resource'; -------------------------------------------------------------------------------- /app/serializers/application.js: -------------------------------------------------------------------------------- 1 | import TransformsMixin from '../mixins/transforms'; 2 | import ApplicationSerializer from 'ember-jsonapi-resources/serializers/application'; 3 | 4 | /** 5 | Serializer for a JSON API resource, combines the addon ApplicationSerializer and TransformsMixin 6 | 7 | @class ApplicationSerializer 8 | @uses TransformsMixin 9 | */ 10 | export default ApplicationSerializer.extend(TransformsMixin); 11 | -------------------------------------------------------------------------------- /app/services/store.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-jsonapi-resources/services/store'; -------------------------------------------------------------------------------- /blueprints/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "console" 4 | ], 5 | "strict": false 6 | } 7 | -------------------------------------------------------------------------------- /blueprints/addon-import/files/__root__/__path__/__name__.js: -------------------------------------------------------------------------------- 1 | export { default } from '<%= modulePath %>'; 2 | -------------------------------------------------------------------------------- /blueprints/addon-import/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var stringUtil = require('ember-cli-string-utils'); 4 | var path = require('path'); 5 | var inflector = require('inflection'); 6 | 7 | module.exports = { 8 | description: 'Generates an import wrapper, (edited to strip out "jsonapi-" prefix for use with ember-jsonapi-resources)', 9 | 10 | fileMapTokens: function() { 11 | return { 12 | __name__: function(options) { 13 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 14 | if (options.pod && options.hasPathToken) { 15 | return blueprintName; 16 | } 17 | var moduleName; 18 | if (blueprintName.match(/transform-mixin/) !== null) { 19 | moduleName = options.dasherizedModuleName + '-transforms'; 20 | } else { 21 | moduleName = options.dasherizedModuleName; 22 | } 23 | return moduleName; 24 | }, 25 | __path__: function(options) { 26 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 27 | if (options.pod && options.hasPathToken) { 28 | return path.join(options.podPath, moduleName); 29 | } 30 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 31 | if (blueprintName.match(/dictionary/) !== null) { 32 | return 'utils/dictionaries'; 33 | } else if (blueprintName.match(/transform-mixin/) !== null) { 34 | return 'mixins'; 35 | } else { 36 | return inflector.pluralize(blueprintName); 37 | } 38 | }, 39 | __root__: function(options) { 40 | if (options.inRepoAddon) { 41 | return path.join('lib', options.inRepoAddon, 'app'); 42 | } 43 | return 'app'; 44 | } 45 | }; 46 | }, 47 | locals: function(options) { 48 | var addonRawName = options.inRepoAddon ? options.inRepoAddon : options.project.name(); 49 | var addonName = stringUtil.dasherize(addonRawName); 50 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 51 | var fileName = stringUtil.dasherize(options.entity.name); 52 | var modulePath = [addonName, inflector.pluralize(blueprintName), fileName].join('/'); 53 | 54 | if (options.pod) { 55 | modulePath = [addonName, fileName, blueprintName].join('/'); 56 | } 57 | if (blueprintName.match(/dictionary/) !== null) { 58 | modulePath = [addonName, 'utils/dictionaries', fileName].join('/'); 59 | } else if (blueprintName.match(/transform-mixin/) !== null) { 60 | modulePath = [addonName, 'mixins', fileName + '-transforms'].join('/'); 61 | } 62 | return { 63 | modulePath: modulePath 64 | }; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /blueprints/ember-jsonapi-resources/index.js: -------------------------------------------------------------------------------- 1 | var Promise = require('ember-cli/lib/ext/promise'); 2 | 3 | module.exports = { 4 | description: 'ember-jsonapi-resources', 5 | 6 | normalizeEntityName: function () {}, 7 | 8 | afterInstall: function () { 9 | return Promise.all([ 10 | this.addBowerPackagesToProject([ 11 | { name: 'fetch' }, 12 | { name: 'es6-promise', target: '^4.0.5' } 13 | ]), 14 | this.addPackagesToProject([ 15 | { name: 'inflection', target: '~1.7.1' }, 16 | { name: 'lodash', target: '~3.10.1' }, 17 | { name: 'ember-inflector',target: '^1.6.2' }, 18 | { name: 'ember-cli-string-utils', target: '^1.0.0' }, 19 | { name: 'ember-cli-path-utils', target: '^1.0.0' }, 20 | { name: 'ember-cli-test-info', target: '^1.0.0' }, 21 | { name: 'ember-cli-get-dependency-depth', target: '^1.0.0' }, 22 | { name: 'silent-error', target: '^1.0.0' } 23 | ]) 24 | ]); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /blueprints/jsonapi-adapter-test/files/tests/unit/__path__/__test__.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:<%= dasherizedModuleName %>', '<%= friendlyTestDescription %>', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /blueprints/jsonapi-adapter-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflector = require('inflection'); 3 | var testInfo = require('ember-cli-test-info'); 4 | var AdapterBlueprint = require('../jsonapi-adapter'); 5 | 6 | module.exports = { 7 | description: 'Generates an (ember-jsonapi-resource) adapter unit test', 8 | 9 | locals: function(options) { 10 | return { 11 | friendlyTestDescription: testInfo.description(options.entity.name, "Unit", "Adapter") 12 | }; 13 | }, 14 | 15 | fileMapTokens: function() { 16 | var tokens = AdapterBlueprint.fileMapTokens.apply(this, arguments); 17 | 18 | tokens['__test__'] = function (options) { 19 | if (options.pod) { 20 | return 'adapter-test'; 21 | } 22 | var moduleName = options.dasherizedModuleName; 23 | return moduleName + '-test'; 24 | }; 25 | 26 | return tokens; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /blueprints/jsonapi-adapter/files/__root__/__path__/__name__.js: -------------------------------------------------------------------------------- 1 | <%= importStatement %> 2 | import config from '../config/environment'; 3 | 4 | export default <%= baseClass %>.extend({ 5 | type: '<%= entity %>', 6 | 7 | /** 8 | Full url/path to API endpoint for this resource. 9 | 10 | This property is optional, as the application adapter provides a sane 11 | default combining config.APP.API_HOST, config.APP.API_PATH and type 12 | through Ember computed properties. This is faster however, and provides 13 | easy customization per adapter. 14 | 15 | Url is always fetched through #fetchUrl method to provide runtime 16 | manipulation of the url, either per adapter or application-wide on the 17 | application adapter. See AdapterApiHostProxyMixin for an example. 18 | 19 | @property url 20 | @type String 21 | */ 22 | url: [config.APP.API_HOST, config.APP.API_PATH, '<%= resource %>'].join('/') 23 | }); 24 | -------------------------------------------------------------------------------- /blueprints/jsonapi-adapter/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflector = require('inflection'); 3 | var stringUtil = require('ember-cli-string-utils'); 4 | var SilentError = require('silent-error'); 5 | var pathUtil = require('ember-cli-path-utils'); 6 | var path = require('path'); 7 | 8 | module.exports = { 9 | description: 'Generates an (ember-jsonapi-resource) adapter following the JSON API 1.0 spec.', 10 | 11 | availableOptions: [ 12 | { name: 'base-class', type: String } 13 | ], 14 | 15 | locals: function(options) { 16 | var resource = options.entity.name || options.args[1]; 17 | var relativePath = pathUtil.getRelativePath(resource); 18 | options.baseClass = options.baseClass || 'application'; 19 | 20 | var baseClass = stringUtil.classify(options.baseClass.replace('\/', '-')) + 'Adapter'; 21 | var importStatement = 'import ' + baseClass + ' from \'' + relativePath + options.baseClass + '\';'; 22 | if (resource === 'application') { 23 | importStatement = 'import ' + baseClass + ' from \'ember-jsonapi-resources/adapters/application\';'; 24 | } else if (options.baseClass === resource) { 25 | throw new SilentError('Adapters cannot extend from themself. To resolve this, remove the `--base-class` option or change to a different base-class.'); 26 | } else if (options.pod) { 27 | relativePath = pathUtil.getRelativeParentPath(resource); 28 | importStatement = 'import ' + baseClass + ' from \'' + relativePath + ['adapters', options.baseClass].join('/') + '\';'; 29 | } 30 | 31 | // TOOD use isAddon to set path for importing confing from dasherizedPackageName or dummy app 32 | // var isAddon = options.inRepoAddon || options.project.isEmberCLIAddon(); 33 | 34 | return { 35 | importStatement: importStatement, 36 | baseClass: baseClass, 37 | entity: stringUtil.dasherize(inflector.singularize(resource)), 38 | resource: stringUtil.dasherize(inflector.pluralize(resource)) 39 | }; 40 | }, 41 | 42 | fileMapTokens: function() { 43 | return { 44 | __name__: function (options) { 45 | if (options.pod) { 46 | return 'adapter'; 47 | } 48 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 49 | return moduleName; 50 | }, 51 | __path__: function(options) { 52 | if (options.pod && options.hasPathToken) { 53 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 54 | return path.join(options.podPath, moduleName); 55 | } 56 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 57 | if (blueprintName === 'resource') { 58 | blueprintName = 'adapter'; 59 | } 60 | return inflector.pluralize(blueprintName); 61 | }, 62 | __root__: function(options) { 63 | if (options.inRepoAddon) { 64 | return path.join('lib', options.inRepoAddon, 'app'); 65 | } else if (options.inAddon) { 66 | return 'app'; 67 | } 68 | return 'app'; 69 | } 70 | }; 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /blueprints/jsonapi-blueprint/files/blueprints/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "console" 4 | ], 5 | "strict": false 6 | } 7 | -------------------------------------------------------------------------------- /blueprints/jsonapi-blueprint/files/blueprints/__name__/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | description: '<%= name %>', 4 | 5 | normalizeEntityName: function () {}, 6 | 7 | afterInstall: function(options) { 8 | return this.addPackagesToProject([{ 9 | name: 'ember-jsonapi-resources', 10 | target: '~1.1.2' 11 | }]); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /blueprints/jsonapi-blueprint/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | description: 'jsonapi-blueprint, generates addon blueprint (use addon name)', 4 | 5 | anonymousOptions: ['name'], 6 | 7 | locals: function(options) { 8 | return { name: options.args[1] }; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /blueprints/jsonapi-dictionary-test/files/tests/unit/utils/dictionaries/__name__-test.js: -------------------------------------------------------------------------------- 1 | import <%= camelizedModuleName %> from '../../../../utils/dictionaries/<%= dasherizedModuleName %>'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('<%= friendlyTestName %>'); 5 | 6 | // Replace this with your real tests. 7 | test('<%= camelizedModuleName %> dictionary exists', function(assert) { 8 | assert.ok(<%= camelizedModuleName %>, '<%= camelizedModuleName %> ok'); 9 | 10 | let dictionary = { 11 | <%= pairs %> 12 | }; 13 | let keys = Object.keys(dictionary); 14 | let values = keys.map(function(key) { return dictionary[key]; }); 15 | 16 | for (let i = 0; i < keys.length; i++) { 17 | let key = keys[i]; 18 | let value = values[i]; 19 | assert.ok(!!<%= camelizedModuleName %>[key], `${key} is a key`); 20 | assert.equal(<%= camelizedModuleName %>[key], value, `<%= camelizedModuleName %>[${key}] is ${value}`); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /blueprints/jsonapi-dictionary-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var EOL = require('os').EOL; 3 | var testInfo = require('ember-cli-test-info'); 4 | 5 | module.exports = { 6 | description: 'Generates a dictionary util unit test.', 7 | locals: function(options) { 8 | var name = options.args[1]; 9 | var entries = options.entity.options; 10 | var key, value, entry; 11 | var pairs = []; 12 | 13 | for (key in entries) { 14 | if (entries.hasOwnProperty(key)) { 15 | value = entries[key]; 16 | pairs.push(assignment(key, value)); 17 | } 18 | } 19 | return { 20 | friendlyTestName: testInfo.name('dictionary/' + options.entity.name, "Unit", "Utility"), 21 | pairs: pairs.join(',' + EOL) 22 | }; 23 | } 24 | }; 25 | 26 | function assignment(key, value) { 27 | return ' "' + key + '": "' + value + '"'; 28 | } 29 | -------------------------------------------------------------------------------- /blueprints/jsonapi-dictionary/files/__root__/utils/dictionaries/__name__.js: -------------------------------------------------------------------------------- 1 | const dictionary = Object.create(null); 2 | 3 | <%= pairs %> 4 | 5 | export default Object.freeze(dictionary); 6 | -------------------------------------------------------------------------------- /blueprints/jsonapi-dictionary/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var EOL = require('os').EOL; 3 | var path = require('path'); 4 | 5 | module.exports = { 6 | description: 'Generates an dictionary util, use with ember-jsonapi-resources transfrom objects.', 7 | 8 | anonymousOptions: [ 9 | 'name', 10 | 'key:value' 11 | ], 12 | 13 | locals: function(options) { 14 | var name = options.args[1]; 15 | var entries = options.entity.options; 16 | var key, value, entry; 17 | var pairs = []; 18 | 19 | for (key in entries) { 20 | if (entries.hasOwnProperty(key)) { 21 | value = entries[key]; 22 | pairs.push(assignment(key, value)); 23 | } 24 | } 25 | 26 | return { 27 | name: name, 28 | pairs: pairs.join(EOL) 29 | }; 30 | }, 31 | 32 | fileMapTokens: function() { 33 | return { 34 | __name__: function (options) { 35 | var moduleName = options.dasherizedModuleName; 36 | return moduleName; 37 | }, 38 | __path__: function(options) { 39 | return path.join('utils', 'dictionaries'); 40 | } 41 | }; 42 | } 43 | }; 44 | 45 | function assignment(key, value) { 46 | return 'dictionary["' + key + '"] = "' + value + '";'; 47 | } 48 | -------------------------------------------------------------------------------- /blueprints/jsonapi-initializer-test/files/tests/unit/initializers/__name__-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import <%= classifiedModuleName %>Initializer from '<%= dependencyDepth %>/initializers/<%= dasherizedModuleName %>'; 3 | import { module, test } from 'qunit'; 4 | 5 | let registry, application, factories, injections; 6 | 7 | module('<%= friendlyTestName %>', { 8 | beforeEach: function() { 9 | Ember.run(function() { 10 | application = Ember.Application.create(); 11 | registry = application.registry; 12 | application.deferReadiness(); 13 | }); 14 | application = stub(application); 15 | }, 16 | afterEach: function() { 17 | factories = null; 18 | injections = null; 19 | application = null; 20 | registry = null; 21 | } 22 | }); 23 | 24 | test('it registers <%= resource %> factory: model, injects into: service, serializer', function(assert) { 25 | <%= classifiedModuleName %>Initializer.initialize(registry, application); 26 | 27 | let registered = Ember.A(factories.mapBy('name')); 28 | assert.ok(registered.includes('model:<%= entity %>'), 'model:<%= entity %> registered'); 29 | let msg = '<%= resource %> injected into service:store'; 30 | assert.equal(injections.findBy('factory', 'service:store').property, '<%= resource %>', msg); 31 | msg = 'serializer injected into service:<%= resource %>'; 32 | assert.equal(injections.findBy('factory', 'service:<%= resource %>').property, 'serializer', msg); 33 | }); 34 | 35 | function stub(app) { 36 | factories = Ember.A([]); 37 | injections = Ember.A([]); 38 | app.register = function(name, factory) { 39 | factories.push({name: name, factory: factory}); 40 | }; 41 | app.inject = function(factory, property, injection) { 42 | injections.push({ 43 | factory: factory, 44 | property: property, 45 | injection: injection 46 | }); 47 | }; 48 | return app; 49 | } 50 | -------------------------------------------------------------------------------- /blueprints/jsonapi-initializer-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var inflector = require('inflection'); 4 | var stringUtil = require('ember-cli-string-utils'); 5 | var getDependencyDepth = require('ember-cli-get-dependency-depth'); 6 | var testInfo = require('ember-cli-test-info'); 7 | 8 | module.exports = { 9 | description: 'Generates an (ember-jsonapi-resource) initializer unit test.', 10 | 11 | locals: function(options) { 12 | var resource = options.entity.name || options.args[1]; 13 | 14 | return { 15 | friendlyTestName: testInfo.name(resource, "Unit", "Initializer"), 16 | dependencyDepth: getDependencyDepth(resource), 17 | entity: stringUtil.dasherize(inflector.singularize(resource)), 18 | resource: stringUtil.dasherize(inflector.pluralize(resource)) 19 | }; 20 | }, 21 | 22 | fileMapTokens: function() { 23 | return { 24 | __name__: function (options) { 25 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 26 | return moduleName; 27 | }, 28 | __path__: function(options) { 29 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 30 | return inflector.pluralize(blueprintName); 31 | } 32 | }; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /blueprints/jsonapi-initializer/files/__root__/initializers/__name__.js: -------------------------------------------------------------------------------- 1 | import Resource from '<%= modelPath %>'; 2 | 3 | export function initialize() { 4 | // see http://emberjs.com/deprecations/v2.x/#toc_initializer-arity 5 | let application = arguments[1] || arguments[0]; 6 | application.register('model:<%= entity %>', Resource, { instantiate: false, singleton: false }); 7 | application.inject('service:store', '<%= resource %>', 'service:<%= resource %>'); 8 | application.inject('service:<%= resource %>', 'serializer', 'serializer:<%= entity %>'); 9 | } 10 | 11 | export default { 12 | name: '<%= resource %>-service', 13 | after: 'store', 14 | initialize: initialize 15 | }; 16 | -------------------------------------------------------------------------------- /blueprints/jsonapi-initializer/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflector = require('inflection'); 3 | var stringUtil = require('ember-cli-string-utils'); 4 | var pathUtil = require('ember-cli-path-utils'); 5 | var path = require('path'); 6 | 7 | module.exports = { 8 | description: 'Generates an (ember-jsonapi-resource) initializer for a service using the JSON API 1.0 spec.', 9 | 10 | locals: function(options) { 11 | var resource = options.entity.name || options.args[1]; 12 | var resourceSingular = inflector.singularize(resource); 13 | var resourcePlural = inflector.pluralize(resource); 14 | var relativePath = pathUtil.getRelativeParentPath(resource); 15 | 16 | var servicePath = relativePath + [ 'services', resourcePlural ].join('/'); 17 | var modelPath = relativePath + [ 'models', resourceSingular ].join('/'); 18 | var adapterPath = relativePath + [ 'adapters', resourceSingular ].join('/'); 19 | var serializerPath = relativePath + [ 'serializers', resourceSingular ].join('/'); 20 | 21 | if (options.pod) { 22 | servicePath = relativePath + [ resourceSingular, 'service' ].join('/'); 23 | modelPath = relativePath + [ resourceSingular, 'model' ].join('/'); 24 | adapterPath = relativePath + [ resourceSingular, 'adapter' ].join('/'); 25 | serializerPath = relativePath + [ resourceSingular, 'serializer' ].join('/'); 26 | } 27 | 28 | return { 29 | entity: stringUtil.dasherize(resourceSingular), 30 | resource: stringUtil.dasherize(resourcePlural), 31 | servicePath: servicePath, 32 | modelPath: modelPath, 33 | adapterPath: adapterPath, 34 | serializerPath: serializerPath 35 | }; 36 | }, 37 | 38 | fileMapTokens: function() { 39 | return { 40 | __name__: function (options) { 41 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 42 | return moduleName; 43 | }, 44 | __path__: function(options) { 45 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 46 | if (options.pod && options.hasPathToken) { 47 | return path.join(options.podPath, moduleName); 48 | } 49 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 50 | return inflector.pluralize(blueprintName); 51 | }, 52 | __root__: function(options) { 53 | if (options.inRepoAddon) { 54 | return path.join('lib', options.inRepoAddon, 'app'); 55 | } else if (options.inAddon) { 56 | return 'app'; 57 | } 58 | return 'app'; 59 | } 60 | }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /blueprints/jsonapi-model-test/files/tests/unit/__path__/__test__.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | import Model from '<%= modelPath %>'; 3 | import Ember from 'ember'; 4 | 5 | moduleFor('model:<%= dasherizedModuleName %>', '<%= friendlyDescription %>', { 6 | // Specify the other units that are required for this test. 7 | <%= typeof needs !== 'undefined' ? needs : '' %> 8 | beforeEach() { 9 | let opts = { instantiate: false, singleton: false }; 10 | this.registry.register('model:<%= entity %>', Model, opts); 11 | }, 12 | afterEach() { 13 | this.registry.unregister('model:<%= entity %>'); 14 | } 15 | }); 16 | 17 | test('<%= resource %> has "type" property set to: <%= resource %>', function(assert) { 18 | let owner = Ember.getOwner(this); 19 | let model = owner._lookupFactory('model:<%= entity %>').create(); 20 | assert.equal(model.get('type'), '<%= resource %>', 'resource has expected type'); 21 | }); 22 | -------------------------------------------------------------------------------- /blueprints/jsonapi-model-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var ModelBlueprint = require('../jsonapi-model'); 4 | var testInfo = require('ember-cli-test-info'); 5 | var inflector = require('inflection'); 6 | var stringUtil = require('ember-cli-string-utils'); 7 | var pathUtil = require('ember-cli-path-utils'); 8 | var getDependencyDepth = require('ember-cli-get-dependency-depth'); 9 | 10 | module.exports = { 11 | description: 'Generates a (ember-jsonapi-resource) model unit test.', 12 | 13 | locals: function(options) { 14 | var resource = options.entity.name || options.args[1]; 15 | var resourceSingular = inflector.singularize(resource); 16 | var resourcePlural = inflector.pluralize(resource); 17 | var result = ModelBlueprint.locals.apply(this, arguments); 18 | 19 | result.friendlyDescription = testInfo.description(resource, "Unit", "Model"); 20 | result.entity = stringUtil.dasherize(resourceSingular); 21 | result.resource = stringUtil.dasherize(resourcePlural); 22 | 23 | var relativePath = getDependencyDepth(resource); 24 | var modelPath = [ relativePath, 'models', resourceSingular ].join('/'); 25 | if (options.pod) { 26 | modelPath = [ relativePath, resourceSingular, 'model' ].join('/'); 27 | } 28 | result.modelPath = modelPath; 29 | 30 | return result; 31 | }, 32 | 33 | fileMapTokens: function() { 34 | var tokens = ModelBlueprint.fileMapTokens.apply(this, arguments); 35 | 36 | tokens['__test__'] = function (options) { 37 | if (options.pod) { 38 | return 'model-test'; 39 | } 40 | var moduleName = options.dasherizedModuleName; 41 | return moduleName + '-test'; 42 | }; 43 | 44 | return tokens; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /blueprints/jsonapi-model/files/__root__/__path__/__name__.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from 'ember-jsonapi-resources/models/resource'; 3 | import { attr, toOne, toMany } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | let <%= classifiedModuleName %>Model = Resource.extend({ 6 | type: '<%= resource %>', 7 | service: Ember.inject.service('<%= resource %>'), 8 | 9 | <%= attrs %> 10 | }); 11 | 12 | <%= classifiedModuleName %>Model.reopenClass({ 13 | 14 | getDefaults() { 15 | return { 16 | attributes: {} 17 | }; 18 | } 19 | }); 20 | 21 | export default <%= classifiedModuleName %>Model; 22 | -------------------------------------------------------------------------------- /blueprints/jsonapi-model/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflection = require('inflection'); 3 | var stringUtils = require('ember-cli-string-utils'); 4 | var pathUtil = require('ember-cli-path-utils'); 5 | var EOL = require('os').EOL; 6 | 7 | module.exports = { 8 | description: 'Generates an (ember-jsonapi-resource) model following the JSON API 1.0 spec.', 9 | 10 | anonymousOptions: [ 11 | 'name', 12 | 'attr:type' 13 | ], 14 | 15 | locals: function(options) { 16 | var resource = options.entity.name || options.args[1]; 17 | var attrs = []; 18 | var needs = []; 19 | var entityOptions = options.entity.options; 20 | 21 | for (var name in entityOptions) { 22 | var type = entityOptions[name] || ''; 23 | var foreignModel = name; 24 | if (type.indexOf(':') > -1) { 25 | foreignModel = type.split(':')[1]; 26 | type = type.split(':')[0]; 27 | } 28 | var dasherizedName = stringUtils.dasherize(name); 29 | var dasherizedType = stringUtils.dasherize(type); 30 | var dasherizedForeignModel = stringUtils.dasherize(foreignModel); 31 | var dasherizedForeignModelSingular = inflection.singularize(dasherizedForeignModel); 32 | var dasherizedForeignModelPlural = inflection.pluralize(dasherizedForeignModel); 33 | 34 | var attr; 35 | if (/has-many/.test(dasherizedType)) { 36 | var dasherizedNamePlural = inflection.pluralize(dasherizedName); 37 | attr = resourceAttr(dasherizedForeignModelPlural, dasherizedType); 38 | attrs.push('"' + dasherizedNamePlural + '": ' + attr); 39 | } else if (/has-one/.test(dasherizedType)) { 40 | attr = resourceAttr(dasherizedForeignModelSingular, dasherizedType); 41 | attrs.push('"' + dasherizedName + '": ' + attr); 42 | } else { 43 | attr = resourceAttr(dasherizedName, dasherizedType); 44 | attrs.push('"' + dasherizedName + '": ' + attr); 45 | } 46 | 47 | if (/has-many|has-one/.test(dasherizedType)) { 48 | needs.push("'model:" + dasherizedForeignModelSingular + "'"); 49 | } 50 | } 51 | var needsDeduplicated = needs.filter(function(need, i) { 52 | return needs.indexOf(need) === i; 53 | }); 54 | 55 | attrs = attrs.join(',' + EOL + ' '); 56 | needs = ' needs: [' + needsDeduplicated.join(', ') + '],'; 57 | 58 | var relativePath = pathUtil.getRelativePath(resource); 59 | if (options.pod) { 60 | relativePath = pathUtil.getRelativeParentPath(resource); 61 | } 62 | 63 | return { 64 | attrs: attrs, 65 | needs: needs, 66 | entity: stringUtils.dasherize(inflection.singularize(resource)), 67 | resource: stringUtils.dasherize(inflection.pluralize(resource)) 68 | }; 69 | }, 70 | 71 | fileMapTokens: function() { 72 | return { 73 | __name__: function (options) { 74 | if (options.pod) { 75 | return 'model'; 76 | } 77 | var moduleName = options.dasherizedModuleName; 78 | return moduleName; 79 | }, 80 | __path__: function(options) { 81 | if (options.pod && options.hasPathToken) { 82 | var moduleName = options.dasherizedModuleName; 83 | return [ options.podPath, moduleName ].join('/'); 84 | } 85 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 86 | if (blueprintName === 'resource') { 87 | blueprintName = 'model'; 88 | } 89 | return inflection.pluralize(blueprintName); 90 | } 91 | }; 92 | } 93 | }; 94 | 95 | function resourceAttr(name, type) { 96 | switch (type) { 97 | case 'to-one': 98 | return 'toOne(\'' + name + '\')'; 99 | case 'to-many': 100 | return 'toMany(\'' + name + '\')'; 101 | case '': 102 | return 'attr()'; 103 | default: 104 | return 'attr(\'' + type + '\')'; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /blueprints/jsonapi-resource/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var Blueprint = require('ember-cli/lib/models/blueprint'); 4 | var Promise = require('ember-cli/lib/ext/promise'); 5 | var merge = require('lodash/object/merge'); 6 | var inflection = require('inflection'); 7 | var path = require('path'); 8 | 9 | module.exports = { 10 | description: 'Generates (ember-jsonapi-resource) resources: a model, service, adapter, serializer, and initializer.', 11 | 12 | install: function(options) { 13 | return this._process('install', options); 14 | }, 15 | 16 | uninstall: function(options) { 17 | return this._process('uninstall', options); 18 | }, 19 | 20 | _processBlueprint: function(type, name, options) { 21 | var mainBlueprint = Blueprint.lookup(name, { 22 | ui: this.ui, 23 | analytics: this.analytics, 24 | project: this.project, 25 | // need to check blueprint paths from addons 26 | paths: options.paths || [ path.resolve(__dirname, '..', '..', 'blueprints') ] 27 | }); 28 | 29 | return Promise.resolve() 30 | .then(function() { 31 | return mainBlueprint[type](options); 32 | }) 33 | .then(function() { 34 | if (name === 'addon-import') { return; } 35 | var testBlueprint = mainBlueprint.lookupBlueprint(name + '-test', { 36 | ui: this.ui, 37 | analytics: this.analytics, 38 | project: this.project, 39 | ignoreMissing: true 40 | }); 41 | 42 | if (!testBlueprint) { return; } 43 | 44 | if (testBlueprint.locals === Blueprint.prototype.locals) { 45 | testBlueprint.locals = function(options) { 46 | return mainBlueprint.locals(options); 47 | }; 48 | } 49 | 50 | return testBlueprint[type](options); 51 | }); 52 | }, 53 | 54 | _process: function(type, options) { 55 | var entityName = options.entity.name; 56 | 57 | var modelOptions = merge({}, options, { 58 | entity: { 59 | name: entityName ? inflection.singularize(entityName) : '' 60 | }, 61 | originBlueprintName: 'jsonapi-model' 62 | }); 63 | 64 | var otherOptions = merge({}, options); 65 | var promises = [ 66 | this._processBlueprint(type, 'jsonapi-model', modelOptions), 67 | this._processBlueprint(type, 'jsonapi-adapter', otherOptions), 68 | this._processBlueprint(type, 'jsonapi-serializer', otherOptions), 69 | this._processBlueprint(type, 'jsonapi-service', otherOptions), 70 | this._processBlueprint(type, 'jsonapi-initializer', otherOptions) 71 | ]; 72 | if (!!options.project.pkg['ember-addon']) { 73 | promies.push( this._processBlueprint(type, 'addon-import', modelOptions) ); 74 | } 75 | return Promise.all(promises); 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /blueprints/jsonapi-serializer-test/files/tests/unit/__path__/__test__.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | import Resource from '<%= modelPath %>'; 3 | import Ember from 'ember'; 4 | 5 | moduleFor('serializer:<%= entity %>', '<%= friendlyTestDescription %>', { 6 | beforeEach() { 7 | Ember.setOwner(Resource.prototype, Ember.getOwner(this)); 8 | let opts = { instantiate: false, singleton: false }; 9 | this.registry.register('model:<%= entity %>', Resource, opts); 10 | } 11 | }); 12 | 13 | // Replace this with your real tests. 14 | test('it serializes resources', function(assert) { 15 | let owner = Ember.getOwner(this); 16 | let resource = owner._lookupFactory('model:<%= entity %>').create(); 17 | let serializer = this.subject(); 18 | var serializedResource = serializer.serialize(resource); 19 | assert.equal(serializedResource.data.type, '<%= resource %>', 'serializes a <%= entity %> resource'); 20 | }); 21 | -------------------------------------------------------------------------------- /blueprints/jsonapi-serializer-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var inflector = require('inflection'); 4 | var stringUtil = require('ember-cli-string-utils'); 5 | var pathUtil = require('ember-cli-path-utils'); 6 | var testInfo = require('ember-cli-test-info'); 7 | var SerializerBlueprint = require('../jsonapi-serializer'); 8 | var getDependencyDepth = require('ember-cli-get-dependency-depth'); 9 | 10 | module.exports = { 11 | description: 'Generates an (ember-jsonapi-resource) serializer unit test.', 12 | 13 | locals: function(options) { 14 | var resource = options.entity.name || options.args[1]; 15 | var resourceSingular = inflector.singularize(resource); 16 | var resourcePlural = inflector.pluralize(resource); 17 | 18 | var relativePath = getDependencyDepth(resource); 19 | var modelPath = [ relativePath, 'models', resourceSingular ].join('/'); 20 | if (options.pod) { 21 | modelPath = [ relativePath, resourceSingular, 'model' ].join('/'); 22 | } 23 | 24 | return { 25 | friendlyTestDescription: testInfo.description(resource, "Unit", "Serializer"), 26 | entity: stringUtil.dasherize(resourceSingular), 27 | resource: stringUtil.dasherize(resourcePlural), 28 | modelPath: modelPath 29 | }; 30 | }, 31 | 32 | fileMapTokens: function() { 33 | var tokens = SerializerBlueprint.fileMapTokens.apply(this, arguments); 34 | 35 | tokens['__test__'] = function (options) { 36 | if (options.pod) { 37 | return 'serializer-test'; 38 | } 39 | var moduleName = options.dasherizedModuleName; 40 | return moduleName + '-test'; 41 | }; 42 | 43 | return tokens; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /blueprints/jsonapi-serializer/files/__root__/__path__/__name__.js: -------------------------------------------------------------------------------- 1 | <%= importStatement %> 2 | 3 | export default ApplicationSerializer.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /blueprints/jsonapi-serializer/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var inflector = require('inflection'); 4 | var stringUtil = require('ember-cli-string-utils'); 5 | var pathUtil = require('ember-cli-path-utils'); 6 | var path = require('path'); 7 | 8 | module.exports = { 9 | description: 'Generates an (ember-jsonapi-resource) serializer following the JSON API 1.0 spec.', 10 | 11 | locals: function(options) { 12 | var resource = options.entity.name || options.args[1]; 13 | var relativePath = pathUtil.getRelativePath(resource); 14 | var importStatement = "import ApplicationSerializer from '" + relativePath + "application';"; 15 | if (options.pod) { 16 | var relativePath = pathUtil.getRelativeParentPath(resource); 17 | importStatement = "import ApplicationSerializer from '" + relativePath + ['serializers', 'application'].join('/') + ";"; 18 | } 19 | 20 | return { 21 | importStatement: importStatement 22 | }; 23 | }, 24 | 25 | fileMapTokens: function() { 26 | return { 27 | __name__: function (options) { 28 | if (options.pod) { 29 | return 'serializer'; 30 | } 31 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 32 | return moduleName; 33 | }, 34 | __path__: function(options) { 35 | if (options.pod && options.hasPathToken) { 36 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 37 | return path.join(options.podPath, moduleName); 38 | } 39 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 40 | if (blueprintName === 'resource') { 41 | blueprintName = 'serializer'; 42 | } 43 | return inflector.pluralize(blueprintName); 44 | }, 45 | __root__: function(options) { 46 | if (options.inRepoAddon) { 47 | return path.join('lib', options.inRepoAddon, 'app'); 48 | } else if (options.inAddon) { 49 | return 'app'; 50 | } 51 | return 'app'; 52 | } 53 | }; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /blueprints/jsonapi-service-test/files/tests/unit/__path__/__test__.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('service:<%= resource %>', '<%= friendlyTestDescription %>', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['service:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let service = this.subject(); 11 | assert.ok(service); 12 | }); 13 | -------------------------------------------------------------------------------- /blueprints/jsonapi-service-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | var inflector = require('inflection'); 4 | var stringUtil = require('ember-cli-string-utils'); 5 | var testInfo = require('ember-cli-test-info'); 6 | var ServiceBlueprint = require('../jsonapi-service'); 7 | 8 | module.exports = { 9 | description: 'Generates an (ember-jsonapi-resource) service unit test.', 10 | 11 | locals: function(options) { 12 | var resource = options.entity.name || options.args[1]; 13 | 14 | return { 15 | friendlyTestDescription: testInfo.description(resource, "Unit", "Service"), 16 | resource: stringUtil.dasherize(inflector.pluralize(resource)) 17 | }; 18 | }, 19 | 20 | fileMapTokens: function() { 21 | var tokens = ServiceBlueprint.fileMapTokens.apply(this, arguments); 22 | 23 | tokens['__test__'] = function (options) { 24 | if (options.pod) { 25 | return 'service-test'; 26 | } 27 | var moduleName = options.dasherizedModuleName; 28 | return inflector.pluralize(moduleName) + '-test'; 29 | }; 30 | 31 | return tokens; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /blueprints/jsonapi-service/files/__root__/__path__/__resource__.js: -------------------------------------------------------------------------------- 1 | import Adapter from '<%= adapterPath %>'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /blueprints/jsonapi-service/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflector = require('inflection'); 3 | var stringUtil = require('ember-cli-string-utils'); 4 | var pathUtil = require('ember-cli-path-utils'); 5 | var path = require('path'); 6 | 7 | module.exports = { 8 | description: 'Generates an (ember-jsonapi-resource) service for resources following the JSON API 1.0 spec.', 9 | 10 | locals: function(options) { 11 | var resource = options.entity.name || options.args[1]; 12 | var resourceSingular = inflector.singularize(resource); 13 | var relativePath = pathUtil.getRelativeParentPath(resource); 14 | 15 | var adapterPath = relativePath + [ 'adapters', resourceSingular ].join('/'); 16 | if (options.pod) { 17 | relativePath = pathUtil.getRelativePath(resource); 18 | adapterPath = relativePath + 'adapter'; 19 | } 20 | 21 | return { 22 | adapterPath: adapterPath, 23 | entity: stringUtil.dasherize(inflector.singularize(resource)), 24 | resource: stringUtil.dasherize(inflector.pluralize(resource)) 25 | }; 26 | }, 27 | 28 | fileMapTokens: function() { 29 | return { 30 | __resource__: function(options) { 31 | if (options.pod) { 32 | return 'service'; 33 | } 34 | return inflector.pluralize(options.locals.resource); 35 | }, 36 | __path__: function(options) { 37 | if (options.pod && options.hasPathToken) { 38 | var moduleName = options.dasherizedModuleName.replace('jsonapi-', ''); 39 | return path.join(options.podPath, moduleName); 40 | } 41 | var blueprintName = options.originBlueprintName.replace('jsonapi-', ''); 42 | if (blueprintName === 'resource') { 43 | blueprintName = 'service'; 44 | } 45 | return inflector.pluralize(blueprintName); 46 | }, 47 | __root__: function(options) { 48 | if (options.inRepoAddon) { 49 | return path.join('lib', options.inRepoAddon, 'app'); 50 | } else if (options.inAddon) { 51 | return 'app'; 52 | } 53 | return 'app'; 54 | } 55 | }; 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /blueprints/jsonapi-transform-mixin/files/__root__/mixins/__name__.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | <%= imports %> 3 | 4 | export default Ember.Mixin.create({ 5 | 6 | <%= methods %> 7 | 8 | }); 9 | -------------------------------------------------------------------------------- /blueprints/jsonapi-transform-mixin/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflector = require('inflection'); 3 | var stringUtil = require('ember-cli-string-utils'); 4 | var EOL = require('os').EOL; 5 | 6 | module.exports = { 7 | description: 'Generates an mixin for using value transforms w/ ember-jsonapi-resources serializers.', 8 | 9 | anonymousOptions: ['name', 'attr-name'], 10 | 11 | locals: function(options) { 12 | var entity = options.args[1]; 13 | var attrNames = Object.keys(options.entity.options); 14 | var attrName, imports = [], methods = []; 15 | 16 | for (var i = 0; i < attrNames.length; i++) { 17 | attrName = attrNames[i]; 18 | imports.push( makeImport(attrName) ); 19 | methods.push( makeTransformMethods(attrName) ); 20 | } 21 | 22 | return { 23 | imports: imports.join(EOL), 24 | methods: methods.join(',' + EOL + EOL) 25 | }; 26 | }, 27 | 28 | fileMapTokens: function() { 29 | return { 30 | __name__: function (options) { 31 | return options.dasherizedModuleName + '-transforms'; 32 | }, 33 | __path__: function(options) { 34 | return inflector.pluralize(options.originBlueprintName.replace('jsonapi-transform-', '')); 35 | } 36 | }; 37 | } 38 | }; 39 | 40 | function makeImport(attrName) { 41 | var transformName = makeTransformName(attrName); 42 | var dasherized = stringUtil.dasherize(attrName); 43 | return "import " + transformName + " from '../transforms/" + dasherized + "';"; 44 | } 45 | 46 | function makeTransformMethods(attrName) { 47 | return [ makeDeserializeMethod(attrName), makeSerializeMethod(attrName) ].join(',' + EOL + EOL); 48 | } 49 | 50 | function makeSerializeMethod(attrName) { 51 | var camelized = stringUtil.camelize(attrName); 52 | var methodName = 'serialize'+ stringUtil.classify(attrName) + 'Attribute'; 53 | var transformName = makeTransformName(attrName); 54 | var loc = []; 55 | loc.push(' ' + methodName + '(deserialized) {'); 56 | loc.push(' ' + 'return ' + transformName + '.serialize(deserialized);'); 57 | loc.push(' }'); 58 | return loc.join(EOL); 59 | } 60 | 61 | function makeDeserializeMethod(attrName) { 62 | var camelized = stringUtil.camelize(attrName); 63 | var methodName = 'deserialize'+ stringUtil.classify(attrName) + 'Attribute'; 64 | var transformName = makeTransformName(attrName); 65 | var loc = []; 66 | loc.push(' ' + methodName + '(serialized) {'); 67 | loc.push(' ' + 'return ' + transformName + '.deserialize(serialized);'); 68 | loc.push(' }'); 69 | return loc.join(EOL); 70 | } 71 | 72 | function makeTransformName(attrName) { 73 | return stringUtil.camelize(attrName) + 'Transform'; 74 | } 75 | -------------------------------------------------------------------------------- /blueprints/jsonapi-transform-test/files/tests/unit/__path__/__test__.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import <%= transformName %> from '<%= transformPath %>'; 3 | import dictionary from '<%= dictionaryPath %>'; 4 | 5 | module('<%= friendlyTestDescription %>'); 6 | 7 | test('<%= transformName %>#serialize', function(assert) { 8 | let value; 9 | for (let key in dictionary) { 10 | value = <%= transformName %>.serialize(dictionary[key]); 11 | assert.equal(value, key, 'serialize("'+ dictionary[key] +'") is `' + key + '`'); 12 | } 13 | }); 14 | 15 | test('<%= transformName %>#deserialize', function(assert) { 16 | let value; 17 | for (let key in dictionary) { 18 | value = <%= transformName %>.deserialize(key); 19 | assert.equal(value, dictionary[key], 'deserialize("'+ key +'") is `' + dictionary[key] +'`'); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /blueprints/jsonapi-transform-test/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var testInfo = require('ember-cli-test-info'); 3 | var inflector = require('inflection'); 4 | var stringUtil = require('ember-cli-string-utils'); 5 | var pathUtil = require('ember-cli-path-utils'); 6 | 7 | module.exports = { 8 | description: 'Generates a value transform unit test for use with ember-jsonapi-resources.', 9 | 10 | locals: function(options) { 11 | var transformName = options.entity.name || options.args[1]; 12 | var dasherized = stringUtil.dasherize(transformName); 13 | var relativePath = pathUtil.getRelativeParentPath('../../'); 14 | var dictionaryPath = relativePath + [ 'utils', 'dictionaries', dasherized ].join('/'); 15 | var transformPath = relativePath + [ 'transforms', dasherized ].join('/'); 16 | 17 | return { 18 | friendlyTestDescription: testInfo.description(options.entity.name, "Unit", "Transform"), 19 | dictionaryPath: dictionaryPath, 20 | transformPath: transformPath, 21 | transformName: stringUtil.camelize(transformName) 22 | }; 23 | }, 24 | 25 | fileMapTokens: function() { 26 | return { 27 | __name__: function (options) { 28 | return options.dasherizedModuleName.replace('jsonapi-', ''); 29 | }, 30 | __path__: function(options) { 31 | return inflector.pluralize(options.originBlueprintName.replace('jsonapi-', '')); 32 | } 33 | }; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /blueprints/jsonapi-transform/files/__root__/__path__/__name__.js: -------------------------------------------------------------------------------- 1 | import TransformMap from 'ember-jsonapi-resources/utils/transform-map'; 2 | import dictionary from '<%= dictionaryPath %>'; 3 | 4 | class <%= className %> extends TransformMap { 5 | 6 | deserialize(serialized) { 7 | return this.lookup(serialized); 8 | } 9 | 10 | serialize(deserialized) { 11 | return this.lookup(deserialized, 'values'); 12 | } 13 | 14 | } 15 | 16 | export default new <%= className %>(dictionary); 17 | -------------------------------------------------------------------------------- /blueprints/jsonapi-transform/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var inflector = require('inflection'); 3 | var stringUtil = require('ember-cli-string-utils'); 4 | var pathUtil = require('ember-cli-path-utils'); 5 | var path = require('path'); 6 | 7 | module.exports = { 8 | description: 'Generates an value transform for use with ember-jsonapi-resources.', 9 | 10 | locals: function(options) { 11 | var transformName = options.entity.name || options.args[1]; 12 | var relativePath = pathUtil.getRelativeParentPath('.'); 13 | var dictionaryPath = relativePath + [ 'utils', 'dictionaries', stringUtil.dasherize(transformName) ].join('/'); 14 | 15 | return { 16 | dictionaryPath: dictionaryPath, 17 | className: 'Transform' + stringUtil.classify(transformName) + 'Attribute' 18 | }; 19 | }, 20 | 21 | fileMapTokens: function() { 22 | return { 23 | __name__: function (options) { 24 | return options.dasherizedModuleName.replace('jsonapi-', ''); 25 | }, 26 | __path__: function(options) { 27 | return inflector.pluralize(options.originBlueprintName.replace('jsonapi-', '')); 28 | } 29 | }; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-jsonapi-resources", 3 | "dependencies": { 4 | "ember": "~2.10.0", 5 | "ember-cli-shims": "0.1.3", 6 | "fetch": "~0.10.1", 7 | "es6-promise": "^4.0.5" 8 | }, 9 | "devDependencies": { 10 | "sinon": "http://sinonjs.org/releases/sinon-1.15.0.js", 11 | "es5-shim": "^4.5.9" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | scenarios: [ 4 | { 5 | name: 'ember-lts-2.4', 6 | bower: { 7 | dependencies: { 8 | 'ember': 'components/ember#lts-2-4' 9 | }, 10 | resolutions: { 11 | 'ember': 'lts-2-4' 12 | } 13 | } 14 | }, 15 | { 16 | name: 'ember-lts-2.8', 17 | bower: { 18 | dependencies: { 19 | 'ember': 'components/ember#lts-2-8' 20 | }, 21 | resolutions: { 22 | 'ember': 'lts-2-8' 23 | } 24 | } 25 | }, 26 | { 27 | name: 'ember-release', 28 | bower: { 29 | dependencies: { 30 | 'ember': 'components/ember#release' 31 | }, 32 | resolutions: { 33 | 'ember': 'release' 34 | } 35 | } 36 | }, 37 | { 38 | name: 'ember-beta', 39 | bower: { 40 | dependencies: { 41 | 'ember': 'components/ember#beta' 42 | }, 43 | resolutions: { 44 | 'ember': 'beta' 45 | } 46 | } 47 | }, 48 | { 49 | name: 'ember-canary', 50 | bower: { 51 | dependencies: { 52 | 'ember': 'components/ember#canary' 53 | }, 54 | resolutions: { 55 | 'ember': 'canary' 56 | } 57 | } 58 | } 59 | ] 60 | }; 61 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | module.exports = function(/* environment, appConfig */) { 5 | return { }; 6 | }; 7 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | /* global require, module */ 3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | var pickFiles = require('broccoli-static-compiler'); 5 | var compileES6 = require('broccoli-es6-concatenator'); 6 | var jsonToModule = require('./lib/json-to-module'); 7 | 8 | module.exports = function(defaults) { 9 | var app = new EmberAddon(defaults); 10 | 11 | /* 12 | This build file specifies the options for the dummy test app of this 13 | addon, located in `/tests/dummy` 14 | This build file does *not* influence how the addon or the app using it 15 | behave. You most likely want to be modifying `./index.js` or app's build file 16 | */ 17 | 18 | var buildTrees = []; 19 | 20 | if (app.tests) { 21 | 22 | app.import({ 23 | development: app.bowerDirectory + '/es5-shim/es5-shim.js', 24 | production: app.bowerDirectory + '/es5-shim/es5-shim.min.js' 25 | }); 26 | 27 | var sinon = pickFiles(app.bowerDirectory + '/sinon', { 28 | srcDir: '/', 29 | files: ['index.js'], 30 | destDir: '/assets/sinon' 31 | }); 32 | 33 | buildTrees.push(sinon); 34 | 35 | var mocks = pickFiles('fixtures', { 36 | srcDir: '/', 37 | files: ['**/*.json'], 38 | destDir: '/fixtures' 39 | }); 40 | 41 | var mocksJs = compileES6(jsonToModule(mocks), { 42 | inputFiles: [ 43 | '**/*.js' 44 | ], 45 | wrapInEval: false, 46 | outputFile: '/assets/fixtures.js' 47 | }); 48 | 49 | buildTrees.push(mocksJs); 50 | } 51 | 52 | return app.toTree(buildTrees); 53 | }; 54 | -------------------------------------------------------------------------------- /fixtures/api/authors/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "1", 4 | "type": "authors", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/authors/1" 7 | }, 8 | "attributes": { 9 | "name": "pixelhandler", 10 | "email": "pixelhandler@gmail.com", 11 | "full-name": "Bill Heaton" 12 | }, 13 | "relationships": { 14 | "posts": { 15 | "links": { 16 | "self": "http://api.pixelhandler.com/api/v1/authors/1/relationships/posts", 17 | "related": "http://api.pixelhandler.com/api/v1/authors/1/posts" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fixtures/api/employees.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "employees", 5 | "id": "1", 6 | "links": { 7 | "self": "http://api.pixelhandler.com/api/v1/employees/1" 8 | }, 9 | "attributes": { 10 | "name": "The Special" 11 | }, 12 | "relationships": { 13 | "supervisor": { 14 | "links": { 15 | "self": "http://api.pixelhandler.com/api/v1/employees/1/relationships/supervisor", 16 | "related": "http://api.pixelhandler.com/api/v1/employees/1/supervisor" 17 | } 18 | } 19 | } 20 | }, 21 | { 22 | "type": "supervisors", 23 | "id": "2", 24 | "links": { 25 | "self": "http://api.pixelhandler.com/api/v1/supervisors/2" 26 | }, 27 | "attributes": { 28 | "name": "The Boss" 29 | }, 30 | "relationships": { 31 | "direct-reports": { 32 | "links": { 33 | "self": "http://api.pixelhandler.com/api/v1/supervisors/2/relationships/direct-reports", 34 | "related": "http://api.pixelhandler.com/api/v1/supervisors/2/direct-reports" 35 | } 36 | } 37 | } 38 | } 39 | ], 40 | "meta": { 41 | "page": { 42 | "sort": "-date", 43 | "total": 2, 44 | "limit": "5", 45 | "offset": "0" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /fixtures/api/employees/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "employees", 4 | "id": "1", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/employees/1" 7 | }, 8 | "attributes": { 9 | "name": "The Special" 10 | }, 11 | "relationships": { 12 | "supervisor": { 13 | "links": { 14 | "related": "http://api.pixelhandler.com/api/v1/employees/1/supervisor" 15 | } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fixtures/api/pictures.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "1", 5 | "type": "pictures", 6 | "links": { 7 | "self": "http://api.pixelhandler.com/api/v1/pictures/1" 8 | }, 9 | "attributes": { 10 | "name": "box of chocolates" 11 | }, 12 | "relationships": { 13 | "imageable": { 14 | "links": { 15 | "self": "http://api.pixelhandler.com/api/v1/pictures/1/relationships/imageable", 16 | "related": "http://api.pixelhandler.com/api/v1/pictures/1/imageable" 17 | }, 18 | "data": { 19 | "type": "products", 20 | "id": "3" 21 | } 22 | } 23 | } 24 | }, 25 | { 26 | "id": "2", 27 | "type": "pictures", 28 | "links": { 29 | "self": "http://api.pixelhandler.com/api/v1/pictures/2" 30 | }, 31 | "attributes": { 32 | "name": "10 foot candy cane" 33 | }, 34 | "relationships": { 35 | "imageable": { 36 | "links": { 37 | "self": "http://api.pixelhandler.com/api/v1/pictures/2/relationships/imageable", 38 | "related": "http://api.pixelhandler.com/api/v1/pictures/2/imageable" 39 | }, 40 | "data": { 41 | "type": "products", 42 | "id": "2" 43 | } 44 | } 45 | } 46 | }, 47 | { 48 | "id": "3", 49 | "type": "pictures", 50 | "links": { 51 | "self": "http://api.pixelhandler.com/api/v1/pictures/3" 52 | }, 53 | "attributes": { 54 | "name": "Hot apple fritter" 55 | }, 56 | "relationships": { 57 | "imageable": { 58 | "links": { 59 | "self": "http://api.pixelhandler.com/api/v1/pictures/3/relationships/imageable", 60 | "related": "http://api.pixelhandler.com/api/v1/pictures/3/imageable" 61 | }, 62 | "data": { 63 | "type": "products", 64 | "id": "1" 65 | } 66 | } 67 | } 68 | }, 69 | { 70 | "id": "4", 71 | "type": "pictures", 72 | "links": { 73 | "self": "http://api.pixelhandler.com/api/v1/pictures/4" 74 | }, 75 | "attributes": { 76 | "name": "Boston Creme" 77 | }, 78 | "relationships": { 79 | "imageable": { 80 | "links": { 81 | "self": "http://api.pixelhandler.com/api/v1/pictures/4/relationships/imageable", 82 | "related": "http://api.pixelhandler.com/api/v1/pictures/4/imageable" 83 | }, 84 | "data": { 85 | "type": "products", 86 | "id": "1" 87 | } 88 | } 89 | } 90 | }, 91 | { 92 | "id": "5", 93 | "type": "pictures", 94 | "links": { 95 | "self": "http://api.pixelhandler.com/api/v1/pictures/5" 96 | }, 97 | "attributes": { 98 | "name": "Bill at EmberConf" 99 | }, 100 | "relationships": { 101 | "imageable": { 102 | "links": { 103 | "self": "http://api.pixelhandler.com/api/v1/pictures/5/relationships/imageable", 104 | "related": "http://api.pixelhandler.com/api/v1/pictures/5/imageable" 105 | }, 106 | "data": { 107 | "type": "employees", 108 | "id": "1" 109 | } 110 | } 111 | } 112 | } 113 | ], 114 | "included": [ 115 | { 116 | "id": "3", 117 | "type": "products", 118 | "links": { 119 | "self": "http://api.pixelhandler.com/api/v1/products/3" 120 | }, 121 | "attributes": { 122 | "name": "Chocolates" 123 | }, 124 | "relationships": { 125 | "pictures": { 126 | "links": { 127 | "self": "http://api.pixelhandler.com/api/v1/products/3/relationships/pictures", 128 | "related": "http://api.pixelhandler.com/api/v1/products/3/pictures" 129 | } 130 | } 131 | } 132 | }, 133 | { 134 | "id": "2", 135 | "type": "products", 136 | "links": { 137 | "self": "http://api.pixelhandler.com/api/v1/products/2" 138 | }, 139 | "attributes": { 140 | "name": "Candy Canes" 141 | }, 142 | "relationships": { 143 | "pictures": { 144 | "links": { 145 | "self": "http://api.pixelhandler.com/api/v1/products/2/relationships/pictures", 146 | "related": "http://api.pixelhandler.com/api/v1/products/2/pictures" 147 | } 148 | } 149 | } 150 | }, 151 | { 152 | "id": "1", 153 | "type": "products", 154 | "links": { 155 | "self": "http://api.pixelhandler.com/api/v1/products/1" 156 | }, 157 | "attributes": { 158 | "name": "Donuts" 159 | }, 160 | "relationships": { 161 | "pictures": { 162 | "links": { 163 | "self": "http://api.pixelhandler.com/api/v1/products/1/relationships/pictures", 164 | "related": "http://api.pixelhandler.com/api/v1/products/1/pictures" 165 | } 166 | } 167 | } 168 | }, 169 | { 170 | "id": "1", 171 | "type": "employees", 172 | "links": { 173 | "self": "http://api.pixelhandler.com/api/v1/employees/1" 174 | }, 175 | "attributes": { 176 | "name": "Bill Heaton" 177 | }, 178 | "relationships": { 179 | "pictures": { 180 | "links": { 181 | "self": "http://api.pixelhandler.com/api/v1/employees/1/relationships/pictures", 182 | "related": "http://api.pixelhandler.com/api/v1/employees/1/pictures" 183 | } 184 | } 185 | } 186 | } 187 | ], 188 | "meta": { 189 | "page": { 190 | "total": 7, 191 | "sort": [ 192 | { 193 | "field": "id", 194 | "direction": "asc" 195 | } 196 | ], 197 | "offset": 0, 198 | "limit": 5 199 | } 200 | }, 201 | "links": { 202 | "first": "http://api.pixelhandler.com/api/v1/pictures?include=imageable&page%5Blimit%5D=5&page%5Boffset%5D=0", 203 | "next": "http://api.pixelhandler.com/api/v1/pictures?include=imageable&page%5Blimit%5D=5&page%5Boffset%5D=5", 204 | "last": "http://api.pixelhandler.com/api/v1/pictures?include=imageable&page%5Blimit%5D=5&page%5Boffset%5D=2" 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /fixtures/api/pictures/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "1", 4 | "type": "pictures", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/pictures/1" 7 | }, 8 | "attributes": { 9 | "name": "box of chocolates" 10 | }, 11 | "relationships": { 12 | "imageable": { 13 | "links": { 14 | "self": "http://api.pixelhandler.com/api/v1/pictures/1/relationships/imageable", 15 | "related": "http://api.pixelhandler.com/api/v1/pictures/1/imageable" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fixtures/api/pictures/1/imageable.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "3", 4 | "type": "products", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/products/3" 7 | }, 8 | "attributes": { 9 | "name": "Chocolates" 10 | }, 11 | "relationships": { 12 | "pictures": { 13 | "links": { 14 | "self": "http://api.pixelhandler.com/api/v1/products/3/relationships/pictures", 15 | "related": "http://api.pixelhandler.com/api/v1/products/3/pictures" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fixtures/api/pictures/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "5", 4 | "type": "pictures", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/pictures/5" 7 | }, 8 | "attributes": { 9 | "name": "Bill at EmberConf" 10 | }, 11 | "relationships": { 12 | "imageable": { 13 | "links": { 14 | "self": "http://api.pixelhandler.com/api/v1/pictures/5/relationships/imageable", 15 | "related": "http://api.pixelhandler.com/api/v1/pictures/5/imageable" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fixtures/api/pictures/5/imageable.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "1", 4 | "type": "employees", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/employees/1" 7 | }, 8 | "attributes": { 9 | "name": "Bill Heaton" 10 | }, 11 | "relationships": { 12 | "pictures": { 13 | "links": { 14 | "self": "http://api.pixelhandler.com/api/v1/employees/1/relationships/pictures", 15 | "related": "http://api.pixelhandler.com/api/v1/employees/1/pictures" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fixtures/api/posts.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "1", 5 | "type": "posts", 6 | "links": { 7 | "self": "http://api.pixelhandler.com/api/v1/posts/1" 8 | }, 9 | "attributes": { 10 | "title": "Practical: Deploy an Ember App with ember-cli-deploy", 11 | "date": "2015-04-25", 12 | "excerpt": "The notes below demonstrate how to setup a chat application, built with [Ember CLI], that uses a backend service from [Firebase]." 13 | } 14 | }, 15 | { 16 | "id": "2", 17 | "type": "posts", 18 | "links": { 19 | "self": "http://api.pixelhandler.com/api/v1/posts/2" 20 | }, 21 | "attributes": { 22 | "title": "How Much Faster is HTMLBars Than Handlebars?", 23 | "date": "2015-03-08", 24 | "excerpt": "Last week I posted an article about [Measuring Performance with User Timing API, in an Ember Application](/posts/measuring-performance-with-user-timing-api-in-an-ember-application) and my conclusion was… " 25 | } 26 | }, 27 | { 28 | "id": "3", 29 | "type": "posts", 30 | "links": { 31 | "self": "http://api.pixelhandler.com/api/v1/posts/3" 32 | }, 33 | "attributes": { 34 | "title": "EmberConf 2015 - March 4th (day 2)", 35 | "date": "2015-03-04", 36 | "excerpt": "_Notes were taken in real time, the last entries in each section may reflect beginning of the talk (so the last became the first and the first became the last, or not)…_" 37 | } 38 | }, 39 | { 40 | "id": "4", 41 | "type": "posts", 42 | "links": { 43 | "self": "http://api.pixelhandler.com/api/v1/posts/4" 44 | }, 45 | "attributes": { 46 | "title": "EmberConf 2015 - March 3rd (day 1)", 47 | "date": "2015-03-03", 48 | "excerpt": "_Notes were taken in real time, the last entries in each section may reflect beginning of the talk (so the last became the first and the first became the last, or not)…_" 49 | } 50 | }, 51 | { 52 | "id": "5", 53 | "type": "posts", 54 | "links": { 55 | "self": "http://api.pixelhandler.com/api/v1/posts/5" 56 | }, 57 | "attributes": { 58 | "title": "A Bet on Web Components and Ember.Component Synchronicity", 59 | "date": "2015-03-02", 60 | "excerpt": "What is a native [Web Component] and how does it differ from an [Ember.Component]. Can your use of Ember Components reflect the Web Components specification?" 61 | } 62 | } 63 | ], 64 | "meta": { 65 | "page": { 66 | "sort": "-date", 67 | "total": 66, 68 | "limit": "5", 69 | "offset": "0" 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /fixtures/api/posts/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "1", 4 | "type": "posts", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/posts/1" 7 | }, 8 | "attributes": { 9 | "title": "Practical: Deploy an Ember App with ember-cli-deploy", 10 | "slug": "practical-deploy-an-ember-app-with-ember-cli-deploy-on-digitalocean", 11 | "excerpt": "The notes below demonstrate how to setup a chat application, built with [Ember CLI], that uses a backend service from [Firebase].", 12 | "date": "2015-04-25", 13 | "body": "## Lightning-Approach Workflow\n\nThis approach is the default setup when using ember-cli-deploy and uses a Redis\nstore for the versions of your index.html file that you deploy.", 14 | "created-at": "2015-04-25T00:00:00.000Z", 15 | "updated-at": "2015-04-25T00:00:00.000Z" 16 | }, 17 | "relationships": { 18 | "author": { 19 | "links": { 20 | "self": "http://api.pixelhandler.com/api/v1/posts/1/relationships/author", 21 | "related": "http://api.pixelhandler.com/api/v1/posts/1/author" 22 | }, 23 | "data": { 24 | "type": "authors", 25 | "id": "1" 26 | } 27 | }, 28 | "comments": { 29 | "links": { 30 | "self": "http://api.pixelhandler.com/api/v1/posts/1/relationships/comments", 31 | "related": "http://api.pixelhandler.com/api/v1/posts/1/comments" 32 | } 33 | } 34 | } 35 | }, 36 | "included": [ 37 | { 38 | "id": "1", 39 | "type": "authors", 40 | "links": { 41 | "self": "http://api.pixelhandler.com/api/v1/authors/1" 42 | }, 43 | "attributes": { 44 | "name": "pixelhandler", 45 | "email": "pixelhandler@gmail.com" 46 | }, 47 | "relationships": { 48 | "posts": { 49 | "links": { 50 | "self": "http://api.pixelhandler.com/api/v1/authors/1/relationships/posts", 51 | "related": "http://api.pixelhandler.com/api/v1/authors/1/posts" 52 | } 53 | } 54 | } 55 | }, 56 | { 57 | "id": "1", 58 | "type": "comments", 59 | "links": { 60 | "self": "http://api.pixelhandler.com/api/v1/comments/1" 61 | }, 62 | "attributes": { 63 | "body": "We recorded a walkthrough of deploying an Ember app with ember-cli-deploy that follows this post here: https://www.youtube.com/watch?v=H-UDcJzlXis", 64 | "created-at": "2015-05-31T20:22:14.820Z" 65 | }, 66 | "relationships": { 67 | "commenter": { 68 | "links": { 69 | "self": "http://api.pixelhandler.com/api/v1/comments/1/relationships/commenter", 70 | "related": "http://api.pixelhandler.com/api/v1/comments/1/commenter" 71 | }, 72 | "data": { 73 | "type": "commenters", 74 | "id": "1" 75 | } 76 | }, 77 | "post": { 78 | "links": { 79 | "self": "http://api.pixelhandler.com/api/v1/comments/1/relationships/post", 80 | "related": "http://api.pixelhandler.com/api/v1/comments/1/post" 81 | }, 82 | "data": { 83 | "type": "posts", 84 | "id": "1" 85 | } 86 | } 87 | } 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /fixtures/api/supervisors/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "supervisors", 4 | "id": "2", 5 | "links": { 6 | "self": "http://api.pixelhandler.com/api/v1/supervisors/2" 7 | }, 8 | "attributes": { 9 | "name": "The Boss" 10 | }, 11 | "relationships": { 12 | "direct-reports": { 13 | "links": { 14 | "self": "http://api.pixelhandler.com/api/v1/supervisors/2/relationships/direct-reports", 15 | "related": "http://api.pixelhandler.com/api/v1/supervisors/2/direct-reports" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-jsonapi-resources', 6 | 7 | included: function(app) { 8 | this._super.included.apply(this, arguments); 9 | 10 | // addon passed as an app doesn't define app.import 11 | if (typeof app.import === 'function') { 12 | app.import({ 13 | development: app.bowerDirectory + '/es6-promise/es6-promise.js', 14 | production: app.bowerDirectory + '/es6-promise/es6-promise.min.js' 15 | }); 16 | 17 | app.import(app.bowerDirectory + '/fetch/fetch.js'); 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /lib/json-to-module.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | var Filter = require('broccoli-filter'); 3 | 4 | function JsonToModule (inputTree, options) { 5 | if (!(this instanceof JsonToModule)) { 6 | return new JsonToModule(inputTree); 7 | } 8 | Filter.call(this, inputTree, options); 9 | options = options || {}; 10 | } 11 | 12 | JsonToModule.prototype = Object.create(Filter.prototype); 13 | JsonToModule.prototype.constructor = JsonToModule; 14 | JsonToModule.prototype.extensions = ['json']; 15 | JsonToModule.prototype.targetExtension = 'js'; 16 | 17 | JsonToModule.prototype.processString = function (string) { 18 | return 'export default ' + string + ';'; 19 | }; 20 | 21 | module.exports = JsonToModule; 22 | -------------------------------------------------------------------------------- /node-tests/blueprints/jsonapi-resource-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EOL = require('os').EOL; 4 | var blueprintHelpers = require('ember-cli-blueprint-test-helpers/helpers'); 5 | var setupTestHooks = blueprintHelpers.setupTestHooks; 6 | var emberNew = blueprintHelpers.emberNew; 7 | var emberGenerateDestroy = blueprintHelpers.emberGenerateDestroy; 8 | var emberGenerate = blueprintHelpers.emberGenerate; 9 | 10 | var expect = require('ember-cli-blueprint-test-helpers/chai').expect; 11 | var sleep = require('sleep'); 12 | 13 | describe('Acceptance: ember generate and destroy jsonapi-resource', function() { 14 | setupTestHooks(this); 15 | 16 | it('generates jsonapi-resource files (model, serializer, adapter, service, tests)', function() { 17 | var args = ['jsonapi-resource', 'foo']; 18 | 19 | return emberNew() 20 | .then(() => emberGenerateDestroy(args, (file) => { 21 | // model & unit test 22 | expect(file('app/models/foo.js')) 23 | .to.contain("import Ember from 'ember';"+EOL) 24 | .to.contain("import Resource from 'ember-jsonapi-resources/models/resource';"+EOL) 25 | .to.contain("import { attr, hasOne, hasMany } from 'ember-jsonapi-resources/models/resource';"+EOL); 26 | expect(file('tests/unit/models/foo-test.js')) 27 | .to.contain("import Ember from 'ember';"+EOL) 28 | .to.contain("import Model from '../../../models/foo';"+EOL) 29 | .to.contain("import { moduleFor, test } from 'ember-qunit';"+EOL); 30 | 31 | // serializer and tests 32 | expect(file('app/serializers/foo.js')) 33 | .to.contain("import ApplicationSerializer from './application';"+EOL) 34 | .to.contain("export default ApplicationSerializer.extend({"+EOL) 35 | // we're not testing the body here 36 | .to.contain("});"+EOL); 37 | expect(file('tests/unit/serializers/foo-test.js')) 38 | .to.contain("import Ember from 'ember';"+EOL) 39 | .to.contain("import Resource from '../../../models/foo';"+EOL) 40 | .to.contain("import { moduleFor, test } from 'ember-qunit';"+EOL); 41 | 42 | // adapter and tests 43 | expect(file('app/adapters/foo.js')) 44 | .to.contain("import ApplicationAdapter from './application';"+EOL) 45 | .to.contain("import config from '../config/environment';"+EOL) 46 | .to.contain("export default ApplicationAdapter.extend({"+EOL) 47 | // we're not testing the body here, except for type 48 | .to.contain("type: 'foo',"+EOL) 49 | .to.contain("});"+EOL); 50 | expect(file('tests/unit/adapters/foo-test.js')) 51 | .to.contain("import { moduleFor, test } from 'ember-qunit';"+EOL); 52 | 53 | // service and tests 54 | expect(file('app/services/foos.js')) 55 | .to.contain("import Adapter from '../adapters/foo';"+EOL) 56 | .to.contain("import ServiceCache from '../mixins/service-cache';"+EOL) 57 | .to.contain("Adapter.reopenClass({ isServiceFactory: true });"+EOL) 58 | .to.contain("export default Adapter.extend(ServiceCache);"+EOL); 59 | expect(file('tests/unit/services/foos-test.js')) 60 | .to.contain("import { moduleFor, test } from 'ember-qunit';"+EOL); 61 | })); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-jsonapi-resources", 3 | "version": "2.0.3", 4 | "description": "Data Persistence for an Ember CLI app using the JSON API 1.0 specification.", 5 | "keywords": [ 6 | "ember-addon", 7 | "ember-jsonapi-resources", 8 | "jsonapi-resources", 9 | "jsonapi" 10 | ], 11 | "license": "MIT", 12 | "author": "Bill Heaton ", 13 | "directories": { 14 | "doc": "doc", 15 | "test": "tests" 16 | }, 17 | "repository": "git@github.com:pixelhandler/ember-jsonapi-resources.git", 18 | "scripts": { 19 | "build": "ember build", 20 | "start": "ember server", 21 | "test": "ember try:each", 22 | "nodetest": "mocha node-tests --recursive" 23 | }, 24 | "dependencies": { 25 | "ember-cli-babel": "^5.1.7", 26 | "ember-fetchjax": "^0.6.1", 27 | "ember-inflector": "^1.9.1", 28 | "ember-runtime-enumerable-includes-polyfill": "^1.0.2", 29 | "inflection": "~1.7.1", 30 | "lodash": "~3.10.1" 31 | }, 32 | "devDependencies": { 33 | "broccoli-asset-rev": "^2.4.5", 34 | "broccoli-es6-concatenator": "^0.1.11", 35 | "broccoli-filter": "^0.1.14", 36 | "broccoli-static-compiler": "^0.2.1", 37 | "ember-buffered-proxy": "^0.5.1", 38 | "ember-cli": "2.10.0", 39 | "ember-cli-app-version": "^2.0.0", 40 | "ember-cli-blueprint-test-helpers": "^0.13.0", 41 | "ember-cli-content-security-policy": "0.4.0", 42 | "ember-cli-dependency-checker": "^1.3.0", 43 | "ember-cli-get-dependency-depth": "^1.0.0", 44 | "ember-cli-htmlbars": "^1.0.10", 45 | "ember-cli-htmlbars-inline-precompile": "^0.3.3", 46 | "ember-cli-inject-live-reload": "^1.4.1", 47 | "ember-cli-jshint": "^2.0.1", 48 | "ember-cli-path-utils": "^1.0.0", 49 | "ember-cli-qunit": "^3.0.1", 50 | "ember-cli-release": "^0.2.9", 51 | "ember-cli-sri": "^2.1.0", 52 | "ember-cli-string-utils": "^1.0.0", 53 | "ember-cli-test-info": "^1.0.0", 54 | "ember-cli-test-loader": "^1.1.0", 55 | "ember-cli-uglify": "^1.2.0", 56 | "ember-disable-prototype-extensions": "^1.1.0", 57 | "ember-export-application-global": "^1.0.5", 58 | "ember-load-initializers": "^0.5.1", 59 | "ember-resolver": "^2.0.3", 60 | "glob": "^4.5.3", 61 | "http-proxy": "^1.12.0", 62 | "loader.js": "^4.0.10", 63 | "mocha": "^2.5.3", 64 | "morgan": "^1.6.1", 65 | "silent-error": "^1.0.0", 66 | "yuidocjs": "^0.7.0" 67 | }, 68 | "engines": { 69 | "node": ">= 0.12.0" 70 | }, 71 | "ember-addon": { 72 | "configPath": "tests/dummy/config" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true 3 | } 4 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 3 | // To use it create some files under `mocks/` 4 | // e.g. `server/mocks/ember-hamsters.js` 5 | // 6 | // module.exports = function(app) { 7 | // app.get('/ember-hamsters', function(req, res) { 8 | // res.send('hello'); 9 | // }); 10 | // }; 11 | 12 | module.exports = function(app) { 13 | var globSync = require('glob').sync; 14 | var mocks = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require); 15 | var proxies = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require); 16 | 17 | // Log proxy requests 18 | var morgan = require('morgan'); 19 | app.use(morgan('dev')); 20 | 21 | mocks.forEach(function(route) { route(app); }); 22 | proxies.forEach(function(route) { route(app); }); 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /server/proxies/api.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var proxyPath = '/api'; 3 | 4 | module.exports = function(app) { 5 | // For options, see: 6 | // https://github.com/nodejitsu/node-http-proxy 7 | var proxy = require('http-proxy').createProxyServer({}); 8 | 9 | proxy.on('error', function(err, req) { 10 | console.error(err, req.url); 11 | }); 12 | 13 | app.use(proxyPath, function(req, res, next){ 14 | // include root path in proxied request 15 | req.url = proxyPath + '/' + req.url; 16 | if (process.env.EMBER_ENV == 'development') { 17 | proxy.web(req, res, { target: 'http://localhost:3000' }); 18 | } else if (process.env.EMBER_ENV == 'production') { 19 | proxy.web(req, res, { target: 'http://api.pixelhandler.com' }); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | "framework": "qunit", 4 | "test_page": "tests/index.html?hidepassed", 5 | "disable_watching": true, 6 | "launch_in_ci": [ 7 | "PhantomJS" 8 | ], 9 | "launch_in_dev": [ 10 | "PhantomJS", 11 | "Chrome" 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "QUnit", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": true, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esversion": 6, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/acceptance/polymorphic-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import RSVP from 'rsvp'; 3 | import { module, test } from 'qunit'; 4 | import startApp from '../../tests/helpers/start-app'; 5 | 6 | import picturesMock from 'fixtures/api/pictures'; 7 | import pictures1Mock from 'fixtures/api/pictures/1'; 8 | import pictures1ImageableMock from 'fixtures/api/pictures/1/imageable'; 9 | import pictures5Mock from 'fixtures/api/pictures/5'; 10 | import pictures5ImageableMock from 'fixtures/api/pictures/5/imageable'; 11 | 12 | import config from '../../config/environment'; 13 | 14 | module('Acceptance | polymorphic', { 15 | beforeEach: function() { 16 | this.sandbox = window.sinon.sandbox.create(); 17 | this.application = startApp(); 18 | }, 19 | 20 | afterEach: function() { 21 | Ember.run(this.application, 'destroy'); 22 | this.sandbox.restore(); 23 | } 24 | }); 25 | 26 | test('visiting /pictures list', function(assert) { 27 | assert.expect(6); 28 | setupFetchResponses(this.sandbox); 29 | 30 | visit('/pictures'); 31 | andThen(function() { 32 | assert.equal(currentURL(), '/pictures', 'Pictures route rendered'); 33 | let listItems = document.querySelector('#ember-testing ul').querySelectorAll('li'); 34 | let name; 35 | for (let i = 0; i < picturesMock.data.length; i++) { 36 | name = picturesMock.data[i].attributes.name; 37 | assert.equal(listItems[i].innerText, name, 'item rendered: ' + name); 38 | } 39 | }); 40 | }); 41 | 42 | test('visiting /pictures/1, picture with an (imageable) product relation', function(assert) { 43 | assert.expect(3); 44 | setupFetchResponses(this.sandbox); 45 | 46 | visit('/pictures/1'); 47 | andThen(function() { 48 | assert.equal(currentURL(), '/pictures/1', 'Pictures #1 route rendered'); 49 | let content = document.querySelectorAll('#ember-testing p'); 50 | let name = pictures1Mock.data.attributes.name; 51 | assert.ok(content[0].innerText.trim().match(name) !== null, name + ' rendered in outlet'); 52 | let imageableName = pictures1ImageableMock.data.attributes.name; 53 | assert.ok(content[1].innerText.trim().match(imageableName) !== null, imageableName + ' rendered in outlet'); 54 | }); 55 | }); 56 | 57 | test('visiting /pictures/5, picture with an (imageable) employee relation', function(assert) { 58 | assert.expect(3); 59 | setupFetchResponses(this.sandbox); 60 | 61 | visit('/pictures/5'); 62 | andThen(function() { 63 | assert.equal(currentURL(), '/pictures/5', 'Pictures #5 route rendered'); 64 | let content = document.querySelectorAll('#ember-testing p'); 65 | let name = pictures5Mock.data.attributes.name; 66 | assert.ok(content[0].innerText.trim().match(name) !== null, name + ' rendered in outlet'); 67 | let imageableName = pictures5ImageableMock.data.attributes.name; 68 | assert.ok(content[1].innerText.trim().match(imageableName) !== null, imageableName + ' rendered in outlet'); 69 | }); 70 | }); 71 | 72 | 73 | function setupFetchResponses(sandbox) { 74 | const apiUrl = [config.APP.API_HOST, config.APP.API_PATH].join('/'); 75 | sandbox.stub(window, 'fetch', function (url) { 76 | let resp; 77 | switch(url) { 78 | case [apiUrl, 'pictures?sort=id&include=imageable'].join('/'): 79 | resp = picturesMockResponse(); 80 | break; 81 | case [apiUrl, 'pictures/1'].join('/'): 82 | resp = pictures1MockResponse(); 83 | break; 84 | case [apiUrl, 'pictures/1/imageable'].join('/'): 85 | case pictures1Mock.data.relationships.imageable.links.related: 86 | resp = pictures1ImageableMockResponse(); 87 | break; 88 | case [apiUrl, 'pictures/5'].join('/'): 89 | resp = pictures5MockResponse(); 90 | break; 91 | case [apiUrl, 'pictures/5/imageable'].join('/'): 92 | case pictures5Mock.data.relationships.imageable.links.related: 93 | resp = pictures5ImageableMockResponse(); 94 | break; 95 | default: 96 | throw('no mocked fetch reponse for request: ' + url); 97 | } 98 | return resp; 99 | }); 100 | } 101 | 102 | function picturesMockResponse() { 103 | return RSVP.Promise.resolve({ 104 | "status": 200, 105 | "json": function() { 106 | return RSVP.Promise.resolve(picturesMock); 107 | } 108 | }); 109 | } 110 | 111 | function pictures1MockResponse() { 112 | return RSVP.Promise.resolve({ 113 | "status": 200, 114 | "json": function() { 115 | return RSVP.Promise.resolve(pictures1Mock); 116 | } 117 | }); 118 | } 119 | 120 | function pictures1ImageableMockResponse() { 121 | return RSVP.Promise.resolve({ 122 | "status": 200, 123 | "json": function() { 124 | return RSVP.Promise.resolve(pictures1ImageableMock); 125 | } 126 | }); 127 | } 128 | 129 | function pictures5MockResponse() { 130 | return RSVP.Promise.resolve({ 131 | "status": 200, 132 | "json": function() { 133 | return RSVP.Promise.resolve(pictures5Mock); 134 | } 135 | }); 136 | } 137 | 138 | function pictures5ImageableMockResponse() { 139 | return RSVP.Promise.resolve({ 140 | "status": 200, 141 | "json": function() { 142 | return RSVP.Promise.resolve(pictures5ImageableMock); 143 | } 144 | }); 145 | } 146 | -------------------------------------------------------------------------------- /tests/blanket-options.js: -------------------------------------------------------------------------------- 1 | /* globals blanket, module */ 2 | 3 | var options = { 4 | modulePrefix: 'ember-jsonapi-resources', 5 | filter: '//.*ember-jsonapi-resources/.*/', 6 | antifilter: '//.*(tests|template).*/', 7 | loaderExclusions: [], 8 | enableCoverage: true, 9 | cliOptions: { 10 | reporters: ['lcov'], 11 | autostart: true 12 | } 13 | }; 14 | if (typeof exports === 'undefined') { 15 | blanket.options(options); 16 | } else { 17 | module.exports = options; 18 | } 19 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/author.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'author' 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/comment.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'comment' 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/commenter.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'commenter' 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/employee.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'employee' 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/imageable.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'imageable', 5 | 6 | url: null, 7 | 8 | find() {}, 9 | findOne() {}, 10 | findQuery() {}, 11 | cacheUpdate() {}, 12 | cacheResource() {}, 13 | initEvents() {} 14 | }); 15 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/picture.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'picture' 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/post.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | import AuthorizationMixin from '../mixins/authorization'; 3 | 4 | export default ApplicationAdapter.extend(AuthorizationMixin, { 5 | type: 'post' 6 | }); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/product.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | type: 'product' 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | let App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/components/form-post.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import BufferedProxy from 'ember-buffered-proxy/proxy'; 3 | 4 | export default Ember.Component.extend({ 5 | tagName: 'form', 6 | 7 | resource: Ember.computed('post', function() { 8 | return BufferedProxy.create({ content: this.get('post') }); 9 | }).readOnly(), 10 | 11 | isNew: null, 12 | isEditing: true, 13 | 14 | focusOut() { 15 | if (!this.get('isNew')) { 16 | this.get('resource').applyChanges(); 17 | this.set('isEditing', false); 18 | this.get('on-edit')(this.get('post')).finally(function() { 19 | this.set('isEditing', true); 20 | }.bind(this)); 21 | } 22 | }, 23 | 24 | actions: { 25 | edit() { 26 | this.set('isEditing', true); 27 | }, 28 | done() { 29 | this.set('isEditing', false); 30 | }, 31 | save() { 32 | this.set('isEditing', false); 33 | this.get('resource').applyChanges(); 34 | this.sendAction('on-save', this.get('post')); 35 | }, 36 | cancel() { 37 | this.set('isEditing', false); 38 | this.get('resource').discardChanges(); 39 | this.sendAction('on-cancel'); 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/admin/edit.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | actions: { 5 | update(model) { 6 | return this.store.updateResource('posts', model).catch(function(err) { 7 | Ember.Logger.error(err); 8 | model.rollback(); 9 | }); 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/post/comments.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/author.js: -------------------------------------------------------------------------------- 1 | import Author from '../models/author'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:author', Author, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'authors', 'service:authors'); 7 | application.inject('service:authors', 'serializer', 'serializer:author'); 8 | } 9 | 10 | export default { 11 | name: 'authors-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/comment.js: -------------------------------------------------------------------------------- 1 | import Comment from '../models/comment'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:comment', Comment, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'comments', 'service:comments'); 7 | application.inject('service:comments', 'serializer', 'serializer:comment'); 8 | } 9 | 10 | export default { 11 | name: 'comments-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/commenter.js: -------------------------------------------------------------------------------- 1 | import Commenter from '../models/commenter'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:commenter', Commenter, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'commenters', 'service:commenters'); 7 | application.inject('service:commenters', 'serializer', 'serializer:commenter'); 8 | } 9 | 10 | export default { 11 | name: 'commenters-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/employee.js: -------------------------------------------------------------------------------- 1 | import Employee from '../models/employee'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:employee', Employee, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'employees', 'service:employees'); 7 | application.inject('service:employees', 'serializer', 'serializer:employee'); 8 | } 9 | 10 | export default { 11 | name: 'employees-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/imageable.js: -------------------------------------------------------------------------------- 1 | export function initialize() { 2 | let application = arguments[1] || arguments[0]; 3 | application.inject('service:store', 'imageables', 'service:imageables'); 4 | application.inject('service:imageables', 'serializer', 'serializer:imageable'); 5 | } 6 | 7 | export default { 8 | name: 'imageables-service', 9 | after: 'store', 10 | initialize: initialize 11 | }; 12 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/picture.js: -------------------------------------------------------------------------------- 1 | import Picture from '../models/picture'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:picture', Picture, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'pictures', 'service:pictures'); 7 | application.inject('service:pictures', 'serializer', 'serializer:picture'); 8 | } 9 | 10 | export default { 11 | name: 'pictures-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/post.js: -------------------------------------------------------------------------------- 1 | import Post from '../models/post'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:post', Post, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'posts', 'service:posts'); 7 | application.inject('service:posts', 'serializer', 'serializer:post'); 8 | } 9 | 10 | export default { 11 | name: 'posts-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/initializers/product.js: -------------------------------------------------------------------------------- 1 | import Product from '../models/product'; 2 | 3 | export function initialize() { 4 | let application = arguments[1] || arguments[0]; 5 | application.register('model:product', Product, { instantiate: false, singleton: false }); 6 | application.inject('service:store', 'products', 'service:products'); 7 | application.inject('service:products', 'serializer', 'serializer:product'); 8 | } 9 | 10 | export default { 11 | name: 'products-service', 12 | after: 'store', 13 | initialize: initialize 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/models/author.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr, toMany } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'authors', 7 | service: Ember.inject.service('authors'), 8 | 9 | name: attr('string'), 10 | email: attr('string'), 11 | 12 | posts: toMany('posts') 13 | }); 14 | -------------------------------------------------------------------------------- /tests/dummy/app/models/comment.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr, toOne } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'comments', 7 | service: Ember.inject.service('comments'), 8 | 9 | body: attr('string'), 10 | 11 | date: Ember.computed('attributes', { 12 | get() { 13 | return this.get('attributes.created-at'); 14 | } 15 | }), 16 | 17 | commenter: toOne('commenter'), 18 | post: toOne('post') 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/models/commenter.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr, toMany } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'commenters', 7 | service: Ember.inject.service('commenters'), 8 | 9 | name: attr('string'), 10 | email: attr('string'), 11 | hash: attr(), 12 | 13 | comments: toMany('comments') 14 | }); 15 | -------------------------------------------------------------------------------- /tests/dummy/app/models/employee.js: -------------------------------------------------------------------------------- 1 | import PersonResource from './person'; 2 | import { toOne, toMany } from 'ember-jsonapi-resources/models/resource'; 3 | 4 | export default PersonResource.extend({ 5 | type: 'employees', 6 | pictures: toMany('pictures'), 7 | supervisor: toOne('supervisor') 8 | }); 9 | -------------------------------------------------------------------------------- /tests/dummy/app/models/person.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'people', 7 | service: Ember.inject.service('people'), 8 | name: attr() // can use any value type for an attribute 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/models/picture.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr, toOne } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'pictures', 7 | service: Ember.inject.service('pictures'), 8 | 9 | "name": attr('string'), 10 | "updated-at": attr('date'), 11 | "created-at": attr('date'), 12 | 13 | imageable: toOne('imageable') // polymorphic 14 | }); 15 | -------------------------------------------------------------------------------- /tests/dummy/app/models/post.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr, toOne, toMany } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'posts', 7 | service: Ember.inject.service('posts'), 8 | 9 | title: attr('string'), 10 | date: attr(), 11 | excerpt: attr('string'), 12 | 13 | author: toOne('author'), 14 | comments: toMany('comments') 15 | }); 16 | -------------------------------------------------------------------------------- /tests/dummy/app/models/product.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resource from './resource'; 3 | import { attr, toMany } from 'ember-jsonapi-resources/models/resource'; 4 | 5 | export default Resource.extend({ 6 | type: 'products', 7 | service: Ember.inject.service('products'), 8 | 9 | name: attr('string'), 10 | 11 | pictures: toMany('pictures') 12 | }); 13 | -------------------------------------------------------------------------------- /tests/dummy/app/models/supervisor.js: -------------------------------------------------------------------------------- 1 | import EmployeeResource from './person'; 2 | import { toMany } from 'ember-jsonapi-resources/models/resource'; 3 | 4 | export default EmployeeResource.extend({ 5 | type: 'supervisors', 6 | directReports: toMany({resource: 'direct-reports', type: 'employees'}) 7 | }); 8 | -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | const Router = Ember.Router.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | this.route('index', { path: '/' }); 11 | this.route('post', { path: '/:post_id' }, function () { 12 | this.route('detail', { path: '/' }); 13 | this.route('comments'); 14 | }); 15 | this.route('admin', function () { 16 | this.route('index'); 17 | this.route('create'); 18 | this.route('edit', { path: ':edit_id' }); 19 | }); 20 | this.route('products', { path: '/products' }, function () { 21 | this.route('detail', { path: '/:product_id' }); 22 | }); 23 | this.route('employees', { path: '/employees' }, function () { 24 | this.route('detail', { path: '/:employee_id' }); 25 | }); 26 | this.route('pictures', { path: '/pictures' }, function () { 27 | this.route('detail', { path: '/:picture_id' }); 28 | }); 29 | }); 30 | 31 | export default Router; 32 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/routes/admin/create.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | beforeModel() { 5 | return this.store.find('authors').then(function (authors) { 6 | this.set('authors', authors); 7 | }.bind(this)); 8 | }, 9 | 10 | model() { 11 | let owner = Ember.getOwner(this); 12 | return owner._lookupFactory('model:post').create({ 13 | isNew: true, 14 | attributes: { date: new Date() } 15 | }); 16 | }, 17 | 18 | afterModel(resource) { 19 | const author = this.get('authors.firstObject'); 20 | resource.addRelationship('author', author.get('id')); 21 | return resource; 22 | }, 23 | 24 | actions: { 25 | save(resource) { 26 | this.store.createResource('posts', resource).then(function(resp) { 27 | let collection = this.modelFor('admin.index'); 28 | if (collection) { collection.addObject(resp); } 29 | collection = this.modelFor('index'); 30 | if (collection) { collection.addObject(resp); } 31 | this.transitionTo('admin.index'); 32 | }.bind(this)); 33 | }, 34 | 35 | cancel() { 36 | this.transitionTo('admin.index'); 37 | } 38 | }, 39 | 40 | deactivate() { 41 | this.modelFor('admin.create').destroy(); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/admin/edit.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return this.store.find('posts', params.edit_id); 6 | }, 7 | 8 | setupController(controller, model) { 9 | this._super(controller, model); 10 | controller.set('isEditing', true); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/admin/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | serviceName: 'posts', 5 | 6 | model() { 7 | return this.store.find('posts', { query: { 'page[limit]': 10, 'sort': '-date' }}); 8 | }, 9 | 10 | actions: { 11 | destroy(model) { 12 | this.modelFor('admin.index').removeObject(model); 13 | return this.store.deleteResource('posts', model).then(function() { 14 | this.refresh(); 15 | }.bind(this)).catch(function(e) { 16 | console.error(e); 17 | }); 18 | } 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/employees.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model() { 5 | const options = { 6 | query: { 7 | sort: 'name', 8 | include: 'pictures' 9 | } 10 | }; 11 | return this.store.find('employees', options); 12 | }, 13 | 14 | actions: { 15 | error(error) { 16 | Ember.Logger.error(error); 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | 5 | model() { 6 | const options = { 7 | query: { 8 | sort: '-date', 9 | include: 'author', 10 | 'page[offset]': 0, 11 | 'page[limit]': 5 12 | } 13 | }; 14 | return this.store.find('posts', options); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/pictures.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model() { 5 | const options = { 6 | query: { 7 | sort: 'id', 8 | include: 'imageable' 9 | } 10 | }; 11 | return this.store.find('pictures', options); 12 | }, 13 | 14 | actions: { 15 | error(error) { 16 | Ember.Logger.error(error); 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/post.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import RSVP from 'rsvp'; 3 | 4 | export default Ember.Route.extend({ 5 | model(params) { 6 | return new RSVP.Promise(function (resolve, reject) { 7 | const found = this.store.all('posts').filter(function (post) { 8 | return post.get('id') === params.post_id; 9 | }); 10 | if (found.get('length') > 0) { 11 | resolve(found[0]); 12 | } else { 13 | const options = { 14 | id: params.post_id, 15 | query: { include: 'author,comments' } 16 | }; 17 | this.store.find('post', options).then( 18 | function (post) { 19 | resolve(post); 20 | }, 21 | function (error) { 22 | reject(error); 23 | } 24 | ); 25 | } 26 | }.bind(this)); 27 | }, 28 | 29 | actions: { 30 | error(error) { 31 | Ember.Logger.error(error); 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/post/detail.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | 5 | setupController(controller, model) { 6 | this._super(controller, model); 7 | this.controllerFor('post.comments').set('model', model.get('comments')); 8 | }, 9 | 10 | renderTemplate(controller, model) { 11 | this._super(controller, model); 12 | this.render('post.comments', { 13 | into: 'post.detail', 14 | outlet: 'comments', 15 | controller: this.controllerFor('post.comments') 16 | }); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/products.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model() { 5 | const options = { 6 | query: { 7 | sort: '-id', 8 | include: 'pictures' 9 | } 10 | }; 11 | return this.store.find('products', options); 12 | }, 13 | 14 | actions: { 15 | error(error) { 16 | Ember.Logger.error(error); 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/author.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/comment.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/commenter.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/employee.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/imageable.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/picture.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/post.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/product.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from './application'; 2 | 3 | export default ApplicationSerializer.extend(); 4 | -------------------------------------------------------------------------------- /tests/dummy/app/services/authors.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/author'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/commenters.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/commenter'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/comments.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/comment'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/employees.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/employee'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/imageables.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/imageable'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/pictures.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/picture'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/posts.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/post'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/services/products.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../adapters/product'; 2 | import ServiceCache from '../mixins/service-cache'; 3 | 4 | Adapter.reopenClass({ isServiceFactory: true }); 5 | 6 | export default Adapter.extend(ServiceCache); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/admin/create.hbs: -------------------------------------------------------------------------------- 1 |

2 | Create a Blog Post 3 |

4 | {{form-post post=model isNew=model.isNew on-save="save" on-cancel="cancel"}} 5 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/admin/edit.hbs: -------------------------------------------------------------------------------- 1 |

2 | Edit a Blog Post 3 |

4 | {{form-post post=model isNew=model.isNew on-edit=(action "update")}} 5 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/admin/index.hbs: -------------------------------------------------------------------------------- 1 |

Posts Admin

2 |
3 | {{#each model key="@identity" as |post|}} 4 |
{{post.date}}
5 |
6 | {{post.title}} 7 | {{#link-to "admin.edit" post class="u-button"}}Edit{{/link-to}} 8 | 9 |
10 | {{/each}} 11 |
12 | {{outlet}} 13 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Ember JSON API Resources Test App

2 | 3 | {{outlet}} 4 | 5 |

Nav

6 |
    7 |
  • {{#link-to 'index'}}Home{{/link-to}}
  • 8 |
  • {{#link-to 'admin.index'}}Edit{{/link-to}}
  • 9 |
  • {{#link-to 'admin.create'}}Write{{/link-to}}
  • 10 |
  • {{#link-to 'products'}}Products{{/link-to}}
  • 11 |
  • {{#link-to 'employees'}}Employees{{/link-to}}
  • 12 |
  • {{#link-to 'pictures'}}Pictures{{/link-to}}
  • 13 |
14 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/form-post.hbs: -------------------------------------------------------------------------------- 1 | {{#if isEditing}} 2 | {{#if isNew}} 3 | 4 | 5 | {{else}} 6 | 7 | {{/if}} 8 |
9 | {{! form for editing the resource attributes }} 10 |
11 |

12 | 13 |
14 | {{input type="text" value=resource.title name="title"}} 15 |

16 | {{#if isNew}} 17 |

18 | 19 |
20 | {{input type="date" value=resource.date name="date" placeholder="xx/xx/xxxx"}} 21 |

22 | {{/if}} 23 |

24 | 25 |
26 | {{textarea type="text" value=resource.excerpt name="excerpt"}} 27 |

28 |
29 | {{else}} 30 | {{#if isNew}} 31 | 32 | 33 | 34 | {{else}} 35 | 36 | {{/if}} 37 |
38 | {{! preview of the resource }} 39 |

{{resource.title}}

40 | 41 | ({{resource.date}}) 42 | 43 |
44 |
45 | {{resource.excerpt}} 46 |
47 |
48 | {{resource.body}} 49 |
50 | {{/if}} 51 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/employees.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each model key="@identity" as |employee|}} 3 |
  • {{#link-to "employees.detail" employee.id}}{{employee.name}}{{/link-to}}
  • 4 | {{/each}} 5 |
6 | 7 | {{outlet}} 8 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/employees/detail.hbs: -------------------------------------------------------------------------------- 1 |

Name: {{model.name}}

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each model key="@identity" as |post|}} 3 |
  • {{#link-to "post" post.id}}{{post.title}}{{/link-to}}
  • 4 | {{/each}} 5 |
6 | 7 | {{outlet}} 8 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/pictures.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each model key="@identity" as |picture|}} 3 |
  • {{#link-to "pictures.detail" picture.id}}{{picture.name}}{{/link-to}}
  • 4 | {{/each}} 5 |
6 | 7 | {{outlet}} 8 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/pictures/detail.hbs: -------------------------------------------------------------------------------- 1 |

2 | Name: {{model.name}}
3 | Updated At: {{model.updated-at}} 4 |

5 |

Imageable: {{model.imageable.name}}

6 | 7 | {{outlet}} 8 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/post.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/post/comments.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Comments

3 |
4 | {{#if model}} 5 |
    6 | {{#each model key="@identity" as |comment|}} 7 |
  • 8 | {{#with comment.commenter as |commenter|}} 9 | 10 | 11 | 12 | by {{commenter.name}} 13 | {{/with}} 14 | on {{comment.date}} 15 |

    {{comment.body}}

    16 |
  • 17 | {{/each}} 18 |
19 | {{else}} 20 |

No comments yet

21 | {{/if}} 22 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/post/detail.hbs: -------------------------------------------------------------------------------- 1 |

{{model.title}}

2 | 3 | {{#with model.author as |author|}} 4 | by {{author.name}}  5 | {{/with}} 6 | ({{model.date}}) 7 | 8 | 9 |
10 | 11 | {{#if model.excerpt}} 12 | {{model.excerpt}} 13 | {{/if}} 14 | 15 | {{outlet "comments"}} 16 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/products.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each model key="@identity" as |product|}} 3 |
  • {{#link-to "products.detail" product.id}}{{product.name}}{{/link-to}}
  • 4 | {{/each}} 5 |
6 | 7 | {{outlet}} 8 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/products/detail.hbs: -------------------------------------------------------------------------------- 1 |

Name: {{model.name}}

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | MODEL_FACTORY_INJECTIONS: true, 15 | EXTEND_PROTOTYPES: { 16 | // Prevent Ember Data from overriding Date.parse. 17 | Date: false 18 | } 19 | }, 20 | 21 | APP: { 22 | // Here you can pass flags/options to your application instance 23 | // when it is created 24 | API_HOST: '', 25 | API_PATH: 'api/v1', 26 | }, 27 | contentSecurityPolicy: { 28 | 'script-src': "'self' 'unsafe-inline' 'unsafe-eval' localhost:49152", 29 | 'connect-src': "'self' api.pixelhandler.com localhost:3000 0.0.0.0:3000", 30 | 'img-src': "'self' www.gravatar.com" 31 | } 32 | }; 33 | 34 | if (environment === 'development') { 35 | // ENV.APP.LOG_RESOLVER = true; 36 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 37 | // ENV.APP.LOG_TRANSITIONS = true; 38 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 39 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 40 | } 41 | 42 | if (environment === 'test') { 43 | // Testem prefers this... 44 | ENV.locationType = 'none'; 45 | 46 | // keep test console output quieter 47 | ENV.APP.LOG_ACTIVE_GENERATION = false; 48 | ENV.APP.LOG_VIEW_LOOKUPS = false; 49 | 50 | ENV.APP.rootElement = '#ember-testing'; 51 | } 52 | 53 | if (environment === 'production') { 54 | 55 | } 56 | 57 | return ENV; 58 | }; 59 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function destroyApp(application) { 4 | Ember.run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import Ember from 'ember'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | 6 | const { RSVP: { Promise } } = Ember; 7 | 8 | export default function(name, options = {}) { 9 | module(name, { 10 | beforeEach() { 11 | this.application = startApp(); 12 | 13 | if (options.beforeEach) { 14 | return options.beforeEach.apply(this, arguments); 15 | } 16 | }, 17 | 18 | afterEach() { 19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments); 20 | return Promise.resolve(afterEach).then(() => destroyApp(this.application)); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from '../../resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/resources.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import RSVP from 'rsvp'; 3 | 4 | import PostResource from 'dummy/models/post'; 5 | import AuthorResource from 'dummy/models/author'; 6 | import CommentResource from 'dummy/models/comment'; 7 | import CommenterResource from 'dummy/models/commenter'; 8 | import PersonResource from 'dummy/models/person'; 9 | import EmployeeResource from 'dummy/models/employee'; 10 | import SupervisorResource from 'dummy/models/supervisor'; 11 | // Even though unused, keep PictureResource here for clarity. (shut up jshint!) 12 | import PictureResource from 'dummy/models/picture'; // jshint ignore:line 13 | 14 | // Remove the service from resources. We're using mock services. 15 | export const Post = PostResource.extend({service: null}); 16 | export const Author = AuthorResource.extend({service: null}); 17 | export const Comment = CommentResource.extend({service: null}); 18 | export const Commenter = CommenterResource.extend({service: null}); 19 | export const Person = PersonResource.extend({service: null}); 20 | export const Employee = EmployeeResource.extend({service: null}); 21 | export const Supervisor = SupervisorResource.extend({service: null}); 22 | 23 | export function setup() { 24 | let opts = { instantiate: false, singleton: false }; 25 | this.registry.register('model:post', Post, opts); 26 | this.registry.register('model:author', Author, opts); 27 | this.registry.register('model:comment', Comment, opts); 28 | this.registry.register('model:commenter', Commenter, opts); 29 | this.registry.register('model:person', Person, opts); 30 | this.registry.register('model:employee', Employee, opts); 31 | this.registry.register('model:supervisor', Supervisor, opts); 32 | } 33 | 34 | export function mockServices() { 35 | let types = Ember.String.w('posts authors comments commenters people employees supervisors'); 36 | let mockService = Ember.Service.extend({ 37 | cacheLookup(/*id*/) { return undefined; }, 38 | findRelated() { return RSVP.resolve(null); } 39 | }); 40 | for (let i = 0; i < types.length; i++) { 41 | this.registry.register('service:' + types[i], mockService); 42 | } 43 | } 44 | 45 | export function teardown() {} 46 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let application, attributes; 7 | 8 | // use defaults, but you can override 9 | if (Ember.assign) { 10 | attributes = Ember.assign({}, config.APP, attrs); 11 | } else { 12 | attributes = Ember.merge({}, config.APP); 13 | attributes = Ember.merge(attributes, attrs); 14 | } 15 | 16 | Ember.run(() => { 17 | application = Application.create(attributes); 18 | application.setupForTesting(); 19 | application.injectTestHelpers(); 20 | }); 21 | 22 | return application; 23 | } 24 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{content-for "body-footer"}} 33 | {{content-for "test-body-footer"}} 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/initializers/model-setup-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import initializer from '../../../initializers/model-setup'; 3 | import { module, test } from 'qunit'; 4 | 5 | let application, registeredTypeOptions; 6 | 7 | module('Unit | Initializer | model-setup', { 8 | beforeEach() { 9 | Ember.run(function() { 10 | application = Ember.Application.create(); 11 | application.deferReadiness(); 12 | stub(application); 13 | }); 14 | }, 15 | afterEach() { 16 | application = null; 17 | registeredTypeOptions = null; 18 | } 19 | }); 20 | 21 | test('registers intantiate:false option for model factories', function(assert) { 22 | initializer.initialize(application); 23 | let option = registeredTypeOptions.findBy('name', 'model'); 24 | assert.equal(option.name, 'model', 'option for model registered'); 25 | assert.equal(option.options.instantiate, false, 'option set to "instantiate:false"'); 26 | }); 27 | 28 | function stub(app) { 29 | registeredTypeOptions = Ember.A([]); 30 | app.registerOptionsForType = function(name, options) { 31 | registeredTypeOptions.pushObject({name: name, options: options}); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /tests/unit/initializers/store-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { initialize } from '../../../initializers/store'; 3 | import { module, test } from 'qunit'; 4 | 5 | let registry, application, factories, injections; 6 | 7 | module('Unit | Initializer | store', { 8 | beforeEach: function() { 9 | Ember.run(function() { 10 | application = Ember.Application.create(); 11 | registry = application.registry; 12 | application.deferReadiness(); 13 | application = stub(application); 14 | }); 15 | }, 16 | afterEach: function() { 17 | factories = null; 18 | injections = null; 19 | application = null; 20 | registry = null; 21 | } 22 | }); 23 | 24 | test('it registers service and injects into route and controller', function(assert) { 25 | initialize(registry, application); 26 | 27 | let registered = Ember.A(factories.mapBy('name')); 28 | assert.ok(registered.includes('service:store'), 'service:briefs registered'); 29 | 30 | let injection = injections.findBy('factory', 'route'); 31 | assert.equal(injection.property, 'store', 'store injected into route factory'); 32 | assert.equal(injection.injection, 'service:store', 'route.store is service:store'); 33 | 34 | injection = injections.findBy('factory', 'controller'); 35 | assert.equal(injection.property, 'store', 'store injected into controller factory'); 36 | assert.equal(injection.injection, 'service:store', 'controller.store is service:store'); 37 | }); 38 | 39 | function stub(app) { 40 | factories = Ember.A([]); 41 | injections = Ember.A([]); 42 | app.register = function(name, factory) { 43 | factories.push({name: name, factory: factory}); 44 | }; 45 | app.inject = function(factory, property, injection) { 46 | injections.push({ 47 | factory: factory, 48 | property: property, 49 | injection: injection 50 | }); 51 | }; 52 | return app; 53 | } 54 | -------------------------------------------------------------------------------- /tests/unit/mixins/authorization-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import AuthorizationMixin from '../../../mixins/authorization'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Mixin | authorization', { 6 | beforeEach() { 7 | window.localStorage.removeItem('AuthorizationHeader'); 8 | }, 9 | afterEach() { 10 | window.localStorage.removeItem('AuthorizationHeader'); 11 | } 12 | }); 13 | 14 | test('it uses "Authorization" for a header field', function(assert) { 15 | let AuthorizationObject = Ember.Object.extend(AuthorizationMixin); 16 | let subject = AuthorizationObject.create(); 17 | let msg = 'Authorization is the value of the property: authorizationHeaderField'; 18 | assert.equal(subject.get('authorizationHeaderField'), 'Authorization', msg); 19 | }); 20 | 21 | test('it uses "AuthorizationHeader" for the storage key used to lookup credentials', function(assert) { 22 | let AuthorizationObject = Ember.Object.extend(AuthorizationMixin); 23 | let subject = AuthorizationObject.create(); 24 | let msg = 'AuthorizationHeader is the value of the property: authorizationHeaderStorageKey'; 25 | assert.equal(subject.get('authorizationHeaderStorageKey'), 'AuthorizationHeader', msg); 26 | }); 27 | 28 | test('it has a (private) property _storage set to: "localStorage"', function(assert) { 29 | let AuthorizationObject = Ember.Object.extend(AuthorizationMixin); 30 | let subject = AuthorizationObject.create(); 31 | let msg = 'localStorage is the value of the property: _storage'; 32 | assert.equal(subject.get('_storage'), 'localStorage', msg); 33 | }); 34 | 35 | test('it has a property authorizationCredential that gets and sets a credential/token', function(assert) { 36 | let AuthorizationObject = Ember.Object.extend(AuthorizationMixin); 37 | let subject = AuthorizationObject.create(); 38 | assert.ok(!subject.get('authorizationCredential'), 'authorizationCredential is not defined yet.'); 39 | 40 | let credential = 'supersecrettokenthatnobodycancrack'; 41 | subject.set('authorizationCredential', credential); 42 | 43 | let msg = 'localStorage["AuthorizationHeader"] is set to ' + credential; 44 | assert.equal(window.localStorage.getItem('AuthorizationHeader'), credential, msg); 45 | 46 | msg = 'authorizationCredential is set to ' + credential; 47 | assert.equal(subject.get('authorizationCredential'), credential, msg); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/unit/mixins/resource-operations-test.js: -------------------------------------------------------------------------------- 1 | import RSVP from 'rsvp'; 2 | // import ResourceOperationsMixin from 'ember-jsonapi-resources/mixins/resource-operations'; 3 | import { module, test } from 'qunit'; 4 | import Resource from 'ember-jsonapi-resources/models/resource'; 5 | import { attr/*, toOne, toMany*/ } from 'ember-jsonapi-resources/models/resource'; 6 | 7 | let promiseResolved = function() { return RSVP.Promise.resolve(); }; 8 | 9 | // ResourceOperationsMixin is mixed into Resource so will use Resource for test subject. 10 | module('Unit | Mixin | resource-operations', { 11 | beforeEach() { 12 | this.sandbox = window.sinon.sandbox.create(); 13 | let Cowboy = Resource.extend({ 14 | type: 'cowboy', 15 | service: { 16 | createResource: this.sandbox.spy(promiseResolved), 17 | updateResource: this.sandbox.spy(promiseResolved), 18 | deleteResource: this.sandbox.spy(promiseResolved), 19 | createRelationship: this.sandbox.spy(promiseResolved), 20 | patchRelationship: this.sandbox.spy(promiseResolved), 21 | deleteRelationship: this.sandbox.spy(promiseResolved), 22 | trigger() {} 23 | }, 24 | name: attr('string'), 25 | // mock relationship computed properties 26 | guns: {kind: 'toMany', mapBy() {} }, // toMany('guns') 27 | horse: {kind: 'toOne', get() {} } // toOne('horse') 28 | }); 29 | this.subject = Cowboy.create({ id: 1, name:'Lone Ranger'}); 30 | // mock payload setup 31 | this.subject.set('relationships', { 32 | guns: { links: { related: 'url' }, data: [] }, 33 | horse: { links: { related: 'url' }, data: null } 34 | }); 35 | }, 36 | afterEach() { 37 | this.sandbox.restore(); 38 | delete this.subject; 39 | delete this.sandbox; 40 | } 41 | }); 42 | 43 | test('createResource via service/adapter', function(assert) { 44 | let promise = this.subject.createResource(); 45 | assert.ok(typeof promise.then === 'function', 'returns a thenable'); 46 | 47 | let args = this.subject.get('service').createResource.firstCall.args; 48 | assert.equal( args[0], this.subject, 49 | 'called service.createResource with the resource instance' 50 | ); 51 | }); 52 | 53 | test('updateResource via service/adapter', function(assert) { 54 | let promise = this.subject.updateResource(); 55 | assert.ok(typeof promise.then === 'function', 'returns a thenable'); 56 | 57 | let args = this.subject.get('service').updateResource.firstCall.args; 58 | assert.equal( args[0], this.subject, 59 | 'called service.updateResource with the resource instance' 60 | ); 61 | }); 62 | 63 | test('deleteResource via service/adapter', function(assert) { 64 | let promise = this.subject.deleteResource(); 65 | assert.ok(typeof promise.then === 'function', 'returns a thenable'); 66 | 67 | let args = this.subject.get('service').deleteResource.firstCall.args; 68 | assert.equal( args[0], this.subject, 69 | 'called service.deleteResource with the resource instance' 70 | ); 71 | }); 72 | 73 | test('createRelationship for to-many relation', function(assert) { 74 | this.sandbox.stub(this.subject, 'addRelationship'); 75 | let promise = this.subject.createRelationship('guns', 1); 76 | assert.ok(typeof promise.then === 'function', 'returns a thenable'); 77 | assert.ok( 78 | this.subject.addRelationship.calledWith('guns', 1), 79 | 'called addRelationship' 80 | ); 81 | 82 | let args = this.subject.get('service').createRelationship.firstCall.args; 83 | assert.equal( args[0], this.subject, 84 | 'called service.createRelationship with the resource instance' 85 | ); 86 | assert.equal( args[1], 'guns', 87 | 'called service.createRelationship with relationship arg: guns' 88 | ); 89 | assert.equal( args[2], 1, 90 | 'called service.createRelationship with id arg: 1' 91 | ); 92 | }); 93 | 94 | test('deleteRelationship for to-many relation', function(assert) { 95 | this.sandbox.stub(this.subject, 'removeRelationship'); 96 | let promise = this.subject.deleteRelationship('guns', 2); 97 | assert.ok(typeof promise.then === 'function', 'returns a thenable'); 98 | assert.ok( 99 | this.subject.removeRelationship.calledWith('guns', 2), 100 | 'called removeRelationship' 101 | ); 102 | 103 | let args = this.subject.get('service').deleteRelationship.firstCall.args; 104 | assert.equal( args[0], this.subject, 105 | 'called service.createRelationship with the resource instance' 106 | ); 107 | assert.equal( args[1], 'guns', 108 | 'called service.createRelationship with relationship arg: guns' 109 | ); 110 | assert.equal( args[2], 2, 111 | 'called service.createRelationship with id arg: 2' 112 | ); 113 | }); 114 | 115 | 116 | test('updateRelationship for to-one relation', function(assert) { 117 | this.sandbox.stub(this.subject, '_replaceRelationshipsData'); 118 | 119 | let promise = this.subject.updateRelationship('horse', 1); 120 | assert.ok(typeof promise.then === 'function', 'returns a thenable'); 121 | assert.ok( 122 | this.subject._replaceRelationshipsData.calledWith('horse', 1), 123 | 'called _replaceRelationshipsData' 124 | ); 125 | 126 | let args = this.subject.get('service').patchRelationship.firstCall.args; 127 | assert.equal( args[0], this.subject, 128 | 'called service.patchRelationship with the resource instance' 129 | ); 130 | assert.equal( args[1], 'horse', 131 | 'called service.patchRelationship with relationship arg: horse' 132 | ); 133 | }); 134 | -------------------------------------------------------------------------------- /tests/unit/mixins/transforms-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import TransformsMixin from 'ember-jsonapi-resources/mixins/transforms'; 3 | import { module, test } from 'qunit'; 4 | import { dateTransform } from 'ember-jsonapi-resources/utils/transforms'; 5 | 6 | let sandbox; 7 | 8 | module('Unit | Mixin | transforms', { 9 | beforeEach() { 10 | let Transforms = Ember.Object.extend(TransformsMixin); 11 | this.subject = Transforms.create(); 12 | sandbox = window.sinon.sandbox.create(); 13 | }, 14 | afterEach() { 15 | sandbox.restore(); 16 | delete this.subject; 17 | } 18 | }); 19 | 20 | test('#serializeDateAttribute', function(assert) { 21 | sandbox.stub(dateTransform, 'serialize'); 22 | this.subject.serializeDateAttribute(new Date()); 23 | assert.ok(dateTransform.serialize.calledOnce, 'called date transform serialize method'); 24 | }); 25 | 26 | test('#deserializeDateAttribute', function(assert) { 27 | sandbox.stub(dateTransform, 'deserialize'); 28 | this.subject.deserializeDateAttribute( (new Date()).toISOString() ); 29 | assert.ok(dateTransform.deserialize.calledOnce, 'called date transform deserialize method'); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/unit/services/store-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | import Ember from 'ember'; 3 | import { pluralize } from 'ember-inflector'; 4 | 5 | let methods = 'find createResource updateResource deleteResource'; 6 | methods += ' createRelationship patchRelationship deleteRelationship'; 7 | methods = Ember.String.w(methods); 8 | 9 | let mockServices, entities = ['post', 'author', 'comment']; 10 | 11 | const MockService = function (name) { 12 | methods.forEach(function (method) { 13 | this[method] = window.sinon.expectation.create(method); 14 | }.bind(this)); 15 | this.cache = { data: Ember.A([{id: '1', type: name}]) }; 16 | return this; 17 | }; 18 | 19 | moduleFor('service:store', 'Unit | Service | store', { 20 | beforeEach() { 21 | mockServices = {}; 22 | entities.forEach(function (entity) { 23 | let serviceName = pluralize(entity); 24 | mockServices[serviceName] = new MockService(serviceName); 25 | }); 26 | }, 27 | afterEach() { 28 | entities.forEach(function (entity) { 29 | let serviceName = pluralize(entity); 30 | methods.forEach(function(method) { 31 | mockServices[serviceName][method].reset(); 32 | }); 33 | }); 34 | mockServices = null; 35 | } 36 | }); 37 | 38 | function methodTest(methodName, assert) { 39 | var store = this.subject(mockServices); 40 | assert.ok(store[methodName], 'service.'+ methodName +' exists'); 41 | entities.forEach(function(entity) { 42 | let serviceName = pluralize(entity); 43 | mockServices[serviceName][methodName].once(); 44 | store[methodName](entity); 45 | let msg = 'verify store.'+ serviceName +'.'+ methodName +' called through store.'+ methodName; 46 | assert.ok(mockServices[serviceName][methodName].verify(), msg); 47 | }); 48 | } 49 | 50 | methods.forEach(function (method) { 51 | test('#' + method, function(assert) { 52 | methodTest.call(this, method, assert); 53 | }); 54 | }); 55 | 56 | test('#all', function (assert) { 57 | var store = this.subject(mockServices); 58 | entities.forEach(function (entity) { 59 | let resources = store.all(entity); 60 | assert.equal(resources.length, 1, '1 item in the store cache for ' + pluralize(entity)); 61 | }); 62 | }); 63 | 64 | test('singleton service', function (assert) { 65 | var singletonService = new MockService('me'); 66 | 67 | var store = this.subject({ 'me': singletonService }); 68 | store.find('me', { singleton: true, id: '1' }); 69 | assert.ok(singletonService.find.once(), 'singleton service #find called'); 70 | 71 | methods.forEach(function(method) { 72 | singletonService[method].reset(); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/unit/utils/is-test.js: -------------------------------------------------------------------------------- 1 | import { isBlank, isDasherized, isType } from 'ember-jsonapi-resources/utils/is'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Utility | is'); 5 | 6 | test('#isBlank null, undefined', function(assert) { 7 | assert.ok(isBlank(null), 'null is blank'); 8 | assert.ok(isBlank(undefined), 'undefined is blank'); 9 | }); 10 | 11 | test('#isBlank primitives: string, number, boolean', function(assert) { 12 | assert.ok(!isBlank('Wild West'), 'A string (with length) is not blank'); 13 | assert.ok(!isBlank(''), 'A string (with no length) is not blank'); 14 | 15 | assert.ok(!isBlank(1848), 'A (postive) number is not blank'); 16 | assert.ok(!isBlank(0), 'A (zero) number is not blank'); 17 | assert.ok(!isBlank(-1), 'A (negative) number is not blank'); 18 | assert.ok(!isBlank(0.25), 'A (float) number is not blank'); 19 | 20 | assert.ok(!isBlank(false), 'A (false) boolean is not blank'); 21 | assert.ok(!isBlank(true), 'A (true) boolean is not blank'); 22 | }); 23 | 24 | test('#isBlank complex: date, object, array', function(assert) { 25 | assert.ok(!isBlank(new Date('1848')), 'A date is not blank'); 26 | assert.ok(!isBlank({}), 'An empty object is not blank'); 27 | assert.ok(!isBlank({name: 'Joe'}), 'An object (with props) is not blank'); 28 | assert.ok(!isBlank(['Sue']), 'An array (with length) is not blank'); 29 | }); 30 | 31 | test('#isDasherized', function(assert) { 32 | assert.ok(isDasherized('is-dashed'), 'is-dashed is dasherized'); 33 | assert.ok(!isDasherized('camelCased'), 'camelCased is not dasherized'); 34 | assert.ok(!isDasherized('snake_case'), 'snake_case is not dasherized'); 35 | }); 36 | 37 | test('#isType - string', function(assert) { 38 | assert.ok(isType('string', 'Los Angeles, CA'), 'value is a string'); 39 | }); 40 | 41 | test('#isType - number', function(assert) { 42 | assert.ok(isType('number', 1901), 'value is a number'); 43 | }); 44 | 45 | test('#isType - boolean', function(assert) { 46 | assert.ok(isType('boolean', true), 'value is a boolean'); 47 | }); 48 | 49 | test('#isType - date', function(assert) { 50 | assert.ok(isType('date', new Date('1848')), 'value is a date'); 51 | }); 52 | 53 | test('#isType - object', function(assert) { 54 | assert.ok(isType('object', {}), 'value is an object'); 55 | }); 56 | 57 | test('#isType - array', function(assert) { 58 | assert.ok(isType('array', []), 'value is an array'); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/unit/utils/to-many-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | import Ember from 'ember'; 3 | import { pluralize } from 'ember-inflector'; 4 | import { setup, teardown } from 'dummy/tests/helpers/resources'; 5 | 6 | let mockServices; 7 | const mockService = function () { 8 | let sandbox = this.sandbox; 9 | return Ember.Service.extend({ 10 | findRelated: sandbox.spy(function () { return Ember.RSVP.Promise.resolve(Ember.A([Ember.Object.create({id: 1})])); }), 11 | cacheLookup: sandbox.spy(function () { return Ember.A([]); }) 12 | }); 13 | }; 14 | let entities = ['post', 'author']; 15 | 16 | moduleFor('model:resource', 'Unit | Utility | toMany', { 17 | beforeEach() { 18 | setup.call(this); 19 | this.sandbox = window.sinon.sandbox.create(); 20 | mockServices = {}; 21 | entities.forEach(function (entity) { 22 | let serviceName = pluralize(entity); 23 | mockServices[serviceName] = mockService.call(this); 24 | this.registry.register('service:'+serviceName, mockServices[serviceName]); 25 | }.bind(this)); 26 | }, 27 | afterEach() { 28 | mockServices = null; 29 | teardown.call(this); 30 | this.sandbox.restore(); 31 | } 32 | }); 33 | 34 | test('toMany() helper sets up a promise proxy to a related resource', function(assert) { 35 | let author = Ember.getOwner(this)._lookupFactory('model:author').create({ 36 | id: '1', attributes: { name: 'pixelhandler' }, 37 | relationships: { 38 | posts: { 39 | links: { 40 | "self": "http://api.pixelhandler.com/api/v1/authors/1/relationships/posts", 41 | "related": "http://api.pixelhandler.com/api/v1/authors/1/posts" 42 | } 43 | } 44 | } 45 | }); 46 | Ember.getOwner(this)._lookupFactory('model:post').create({ 47 | id: '2', attributes: { title: 'Wyatt Earp', excerpt: 'Was a gambler.'}, 48 | relationships: { 49 | author: { 50 | data: { type: 'authors', id: '1' }, 51 | links: { 52 | 'self': 'http://api.pixelhandler.com/api/v1/posts/2/relationships/author', 53 | 'related': 'http://api.pixelhandler.com/api/v1/posts/2/author' 54 | } 55 | }, 56 | } 57 | }); 58 | let promise = author.get('posts'); 59 | assert.ok(promise.toString().match('ArrayProxy').length === 1, 'ArrayProxy used for toMany relation'); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/unit/utils/to-one-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | import Ember from 'ember'; 3 | import RSVP from 'rsvp'; 4 | import { pluralize } from 'ember-inflector'; 5 | import { setup, teardown } from 'dummy/tests/helpers/resources'; 6 | 7 | let mockServices; 8 | const mockService = function () { 9 | let sandbox = this.sandbox; 10 | return Ember.Service.extend({ 11 | findRelated: sandbox.spy(function () { return RSVP.Promise.resolve(Ember.Object.create({id: 1})); }), 12 | cacheLookup: sandbox.spy(function () { return undefined; }) 13 | }); 14 | }; 15 | let entities = ['post', 'author']; 16 | 17 | moduleFor('model:resource', 'Unit | Utility | toOne', { 18 | beforeEach() { 19 | setup.call(this); 20 | this.sandbox = window.sinon.sandbox.create(); 21 | mockServices = {}; 22 | entities.forEach(function (entity) { 23 | let serviceName = pluralize(entity); 24 | mockServices[serviceName] = mockService.call(this); 25 | this.registry.register('service:'+serviceName, mockServices[serviceName]); 26 | }.bind(this)); 27 | }, 28 | afterEach() { 29 | mockServices = null; 30 | teardown.call(this); 31 | this.sandbox.restore(); 32 | } 33 | }); 34 | 35 | test('toOne() helper sets up a promise proxy to a related resource', function(assert) { 36 | let post = Ember.getOwner(this)._lookupFactory('model:post').create({ 37 | id: '1', attributes: { title: 'Wyatt Earp', excerpt: 'Was a gambler.'}, 38 | relationships: { 39 | author: { 40 | data: { type: 'authors', id: '1' }, 41 | links: { 42 | 'self': 'http://api.pixelhandler.com/api/v1/posts/1/relationships/author', 43 | 'related': 'http://api.pixelhandler.com/api/v1/posts/1/author' 44 | } 45 | }, 46 | } 47 | }); 48 | let promise = post.get('author'); 49 | assert.ok(promise.toString().match('ObjectProxy').length === 1, 'ObjectProxy used for toOne relation'); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/unit/utils/transform-map-test.js: -------------------------------------------------------------------------------- 1 | import TransformMap from 'ember-jsonapi-resources/utils/transform-map'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Utility | transform map'); 5 | 6 | const map = Object.create(null); 7 | map.yes = 'Yes'; 8 | map.no = 'No'; 9 | Object.freeze(map); 10 | 11 | test('#values', function(assert) { 12 | let subject = new TransformMap(map); 13 | assert.equal(subject.values[0], 'Yes', 'map value[0] is "Yes"'); 14 | assert.equal(subject.values[1], 'No', 'map value[1] is "No"'); 15 | }); 16 | 17 | test('#lookup by key', function(assert) { 18 | let subject = new TransformMap(map); 19 | assert.equal(subject.lookup('yes'), 'Yes', 'lookup "yes" value is "Yes"'); 20 | assert.equal(subject.lookup('no', 'keys'), 'No', 'lookup "no" value is "No"'); 21 | }); 22 | 23 | test('#lookup by value', function(assert) { 24 | let subject = new TransformMap(map); 25 | assert.equal(subject.lookup('Yes', 'values'), 'yes', 'lookup "Yes" value is "yes"'); 26 | assert.equal(subject.lookup('No', 'values'), 'no', 'lookup "No" value is "no"'); 27 | }); 28 | 29 | test('#lookup null', function(assert) { 30 | let subject = new TransformMap(map); 31 | assert.equal(subject.lookup(null), null, 'lookup null value is null'); 32 | }); 33 | 34 | test('#values', function(assert) { 35 | let subject = new TransformMap(map); 36 | assert.equal(subject.values.toString(), "Yes,No", 'values are "Yes,No"'); 37 | }); 38 | 39 | test('#keys', function(assert) { 40 | let subject = new TransformMap(map); 41 | assert.equal(subject.keys.toString(), "yes,no", 'keys are "yes,no"'); 42 | }); 43 | 44 | test('#entries', function(assert) { 45 | let subject = new TransformMap(map); 46 | assert.equal(subject.entries[0].toString(), "yes,Yes", 'first entry is `["yes", "Yes"]`'); 47 | assert.equal(subject.entries[1].toString(), "no,No", 'last entry is `["no", "No"]`'); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/unit/utils/transforms-test.js: -------------------------------------------------------------------------------- 1 | import { dateTransform } from 'ember-jsonapi-resources/utils/transforms'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Utility | transforms'); 5 | 6 | test('dateTransform#serialize - to ISO String', function(assert) { 7 | let date = 'October 26, 1881'; 8 | let gunfightAtOKCorral = new Date(date); 9 | let expected = gunfightAtOKCorral.toISOString(); 10 | let serialized = dateTransform.serialize(gunfightAtOKCorral); 11 | assert.equal(serialized, expected, 'serialized to ISO String'); 12 | }); 13 | 14 | test('dateTransform#deserialize - from ISO String', function(assert) { 15 | let date = 'October 26, 1881'; 16 | let gunfightAtOKCorral = new Date(date); 17 | let value = gunfightAtOKCorral.toISOString(); 18 | let deserialized = dateTransform.deserialize(value); 19 | assert.equal(deserialized.valueOf(), gunfightAtOKCorral.valueOf(), 'deserialized from ISO String'); 20 | }); 21 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelhandler/ember-jsonapi-resources/9a24b6c6b0f65bff48a45c9154b327b0853e0886/vendor/.gitkeep -------------------------------------------------------------------------------- /yuidoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ember JSON API Resources", 3 | "description": "Lightweight persistence for an Ember CLI app using the JSON API 1.0 specification.", 4 | "version": "0.2.1", 5 | "url": "https://github.com/pixelhandler/ember-jsonapi-resources", 6 | "options": { 7 | "paths": [ 8 | "./addon/*/*.js" 9 | ], 10 | "outdir": "./docs" 11 | } 12 | } 13 | --------------------------------------------------------------------------------