├── app
├── .gitkeep
└── initializers
│ └── ember-data-meta-links-improvements.js
├── addon
├── .gitkeep
├── patches
│ ├── model.js
│ ├── rest-adapter.js
│ ├── belongs-to-reference.js
│ ├── adapter-populated-record-array.js
│ ├── json-serializer.js
│ ├── record-array.js
│ ├── record-reference.js
│ ├── rest-serializer.js
│ ├── json-api-serializer.js
│ ├── has-many-reference.js
│ └── store.js
├── initializers
│ └── ember-data-meta-links-improvements.js
├── index.js
└── references
│ ├── link.js
│ └── record-array.js
├── vendor
└── .gitkeep
├── tests
├── unit
│ └── .gitkeep
├── integration
│ ├── .gitkeep
│ ├── belongs-to-reference-test.js
│ ├── has-many-reference-test.js
│ ├── findAll-test.js
│ ├── query-test.js
│ ├── has-many-links-test.js
│ └── record-meta-test.js
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ ├── .gitkeep
│ │ │ ├── author.js
│ │ │ ├── chapter.js
│ │ │ ├── rest-book.js
│ │ │ └── book.js
│ │ ├── routes
│ │ │ └── .gitkeep
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── templates
│ │ │ └── components
│ │ │ │ └── .gitkeep
│ │ ├── resolver.js
│ │ ├── serializers
│ │ │ └── rest-book.js
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ └── config
│ │ └── environment.js
├── .eslintrc.js
├── test-helper.js
├── helpers
│ ├── destroy-app.js
│ ├── resolver.js
│ ├── start-app.js
│ └── module-for-acceptance.js
└── index.html
├── .watchmanconfig
├── .bowerrc
├── index.js
├── config
├── environment.js
└── ember-try.js
├── .eslintrc.js
├── bower.json
├── .npmignore
├── testem.js
├── .ember-cli
├── .gitignore
├── .editorconfig
├── ember-cli-build.js
├── .travis.yml
├── LICENSE.md
├── package.json
└── README.md
/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: '../node_modules/ember-cli-eslint/coding-standard/ember-testing.js'
3 | };
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 |
4 | module.exports = {
5 | name: 'ember-data-meta-links-improvements'
6 | };
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/author.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { Model } = DS;
4 |
5 | export default Model.extend({
6 | });
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/chapter.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { Model } = DS;
4 |
5 | export default Model.extend({
6 | });
7 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 |
6 | setResolver(resolver);
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/serializers/rest-book.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { RESTSerializer } = DS;
4 |
5 | export default RESTSerializer.extend();
6 |
--------------------------------------------------------------------------------
/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/dummy/app/models/rest-book.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { Model, attr } = DS;
4 |
5 | export default Model.extend({
6 | name: attr()
7 | });
8 |
--------------------------------------------------------------------------------
/app/initializers/ember-data-meta-links-improvements.js:
--------------------------------------------------------------------------------
1 | export { default, initialize } from 'ember-data-meta-links-improvements/initializers/ember-data-meta-links-improvements';
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | extends: [
5 | require.resolve('ember-cli-eslint/coding-standard/ember-application.js')
6 | ]
7 | };
8 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/book.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { Model, belongsTo, hasMany } = DS;
4 |
5 | export default Model.extend({
6 | author: belongsTo(),
7 | chapters: hasMany()
8 | });
9 |
--------------------------------------------------------------------------------
/addon/patches/model.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | DS.Model.reopen({
4 | ref() {
5 | let modelName = this.constructor.modelName;
6 | return this.store.getReference(modelName, this.id);
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/addon/patches/rest-adapter.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { RESTAdapter } = DS;
4 |
5 | RESTAdapter.reopen({
6 |
7 | findLink(link) {
8 | return this.ajax(link, "GET");
9 | }
10 |
11 | });
12 |
--------------------------------------------------------------------------------
/addon/patches/belongs-to-reference.js:
--------------------------------------------------------------------------------
1 | import BelongsToReference from 'ember-data/-private/system/references/belongs-to';
2 |
3 | BelongsToReference.prototype.parentRef = function() {
4 | return this.internalModel.recordReference;
5 | }
6 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-data-meta-links-improvements",
3 | "dependencies": {
4 | "ember": "~2.7.0",
5 | "ember-cli-shims": "0.1.1",
6 | "ember-qunit-notifications": "0.1.0",
7 | "pretender": "^0.12.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .jshintrc
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/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/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 | });
11 |
12 | export default Router;
13 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://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 |
--------------------------------------------------------------------------------
/addon/initializers/ember-data-meta-links-improvements.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements"
2 |
3 | export function initialize(/* application */) {
4 | // patches are applied by importing ember-data-meta-links-improvements
5 | }
6 |
7 | export default {
8 | name: 'ember-data-meta-links-improvements',
9 | initialize
10 | };
11 |
--------------------------------------------------------------------------------
/addon/patches/adapter-populated-record-array.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { AdapterPopulatedRecordArray } = DS;
4 |
5 | AdapterPopulatedRecordArray.reopen({
6 |
7 | loadRecords(records, payload) {
8 | this._super(...arguments);
9 |
10 | let ref = this.ref();
11 | ref.__update_meta(payload.meta);
12 | ref.__update_links(payload.links);
13 | }
14 |
15 | });
16 |
--------------------------------------------------------------------------------
/addon/patches/json-serializer.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 |
3 | const { JSONSerializer } = DS;
4 |
5 | JSONSerializer.reopen({
6 |
7 | normalizeFindAllResponse(store, modelClass) {
8 | let normalized = this._super(...arguments);
9 | normalized.__is_findAll = true;
10 | normalized.__primaryModelName = modelClass.modelName;
11 | return normalized;
12 | }
13 |
14 | });
15 |
--------------------------------------------------------------------------------
/addon/patches/record-array.js:
--------------------------------------------------------------------------------
1 | import DS from "ember-data";
2 | import RecordArrayReference from "../references/record-array";
3 |
4 | const { RecordArray } = DS;
5 |
6 | RecordArray.reopen({
7 |
8 | init() {
9 | this._super(...arguments);
10 |
11 | let { store } = this;
12 | this._ref = new RecordArrayReference({ recordArray: this, store });
13 | },
14 |
15 | ref() {
16 | return this._ref;
17 | }
18 |
19 | });
20 |
--------------------------------------------------------------------------------
/addon/index.js:
--------------------------------------------------------------------------------
1 | import './patches/store';
2 | import './patches/rest-adapter';
3 | import './patches/json-serializer';
4 | import './patches/json-api-serializer';
5 | import './patches/rest-serializer';
6 | import './patches/record-reference';
7 | import './patches/has-many-reference';
8 | import './patches/belongs-to-reference';
9 | import './patches/model';
10 | import './patches/adapter-populated-record-array';
11 | import './patches/record-array';
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/addon/patches/record-reference.js:
--------------------------------------------------------------------------------
1 | import RecordReference from 'ember-data/-private/system/references/record';
2 |
3 | RecordReference.prototype.__update_meta = function(meta) {
4 | this._meta = meta;
5 | };
6 |
7 | RecordReference.prototype.__update_responseMeta = function(meta) {
8 | this._responseMeta = meta;
9 | };
10 |
11 | RecordReference.prototype.meta = function(which) {
12 | if (which === "response") {
13 | return this._responseMeta;
14 | }
15 |
16 | return this._meta;
17 | };
18 |
--------------------------------------------------------------------------------
/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/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;
7 |
8 | let attributes = Ember.merge({}, config.APP);
9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
10 |
11 | Ember.run(() => {
12 | application = Application.create(attributes);
13 | application.setupForTesting();
14 | application.injectTestHelpers();
15 | });
16 |
17 | return application;
18 | }
19 |
--------------------------------------------------------------------------------
/addon/references/link.js:
--------------------------------------------------------------------------------
1 | export default class LinkReference {
2 |
3 | constructor({ store, parentRef, name, href, meta }) {
4 | this._store = store;
5 | this._parentRef = parentRef;
6 | this._name = name;
7 | this._href = href;
8 | this._meta = meta;
9 | }
10 |
11 | name() {
12 | return this._name;
13 | }
14 |
15 | href() {
16 | return this._href;
17 | }
18 |
19 | meta() {
20 | return this._meta;
21 | }
22 |
23 | parentRef() {
24 | return this._parentRef;
25 | }
26 |
27 | load() {
28 | return this._store.loadLink(this);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | /* global require, module */
3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function(defaults) {
6 | var app = new EmberAddon(defaults, {
7 | babel: {
8 | includePolyfill: true
9 | }
10 | });
11 |
12 | /*
13 | This build file specifies the options for the dummy test app of this
14 | addon, located in `/tests/dummy`
15 | This build file does *not* influence how the addon or the app using it
16 | behave. You most likely want to be modifying `./index.js` or app's build file
17 | */
18 |
19 | return app.toTree();
20 | };
21 |
--------------------------------------------------------------------------------
/addon/patches/rest-serializer.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 |
3 | const { RESTSerializer } = DS;
4 |
5 | RESTSerializer.reopen({
6 |
7 | normalize(model, payload) {
8 | let normalized = this._super(...arguments);
9 | normalized.data.meta = payload.meta;
10 | return normalized;
11 | },
12 |
13 | pushPayload(store, payload) {
14 | const orig = store.push;
15 | let normalized;
16 |
17 | store.push = function(data) {
18 | normalized = data;
19 | }
20 |
21 | this._super(...arguments);
22 |
23 | store.push = orig;
24 |
25 | normalized.meta = payload.meta;
26 |
27 | store.push(normalized);
28 | }
29 |
30 | });
31 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/addon/patches/json-api-serializer.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DS from 'ember-data';
3 |
4 | const { typeOf } = Ember;
5 | const { JSONAPISerializer } = DS;
6 |
7 | JSONAPISerializer.reopen({
8 |
9 | normalizeSingleResponse(store, modelClass, payload) {
10 | let recordLevelMeta = payload.data && payload.data.meta;
11 | let normalized = this._super(...arguments);
12 |
13 | if (typeOf(normalized.data) === "object") {
14 | normalized.data._top_level_meta = normalized.meta;
15 | normalized.data.meta = recordLevelMeta;
16 | }
17 |
18 | // TODO included
19 |
20 | return normalized;
21 | },
22 |
23 | normalize(modelClass, resourceHash) {
24 | let normalized = this._super(...arguments);
25 | normalized.data.meta = resourceHash.meta;
26 | return normalized;
27 | }
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "4"
5 |
6 | sudo: false
7 |
8 | cache:
9 | directories:
10 | - node_modules
11 |
12 | env:
13 | - EMBER_TRY_SCENARIO=default
14 | - EMBER_TRY_SCENARIO=ember-1.13
15 | - EMBER_TRY_SCENARIO=ember-release
16 | - EMBER_TRY_SCENARIO=ember-beta
17 | - EMBER_TRY_SCENARIO=ember-canary
18 |
19 | matrix:
20 | fast_finish: true
21 | allow_failures:
22 | - env: EMBER_TRY_SCENARIO=ember-canary
23 |
24 | before_install:
25 | - npm config set spin false
26 | - npm install -g bower
27 | - bower --version
28 | - npm install phantomjs-prebuilt
29 | - phantomjs --version
30 |
31 | install:
32 | - npm install
33 | - bower install
34 |
35 | script:
36 | # Usually, it's ok to finish the test scenario without reverting
37 | # to the addon's original dependency state, skipping "cleanup".
38 | - ember try:one $EMBER_TRY_SCENARIO test --skip-cleanup
39 |
--------------------------------------------------------------------------------
/addon/patches/has-many-reference.js:
--------------------------------------------------------------------------------
1 | import HasManyReference from 'ember-data/-private/system/references/has-many';
2 | import LinkReference from 'ember-data-meta-links-improvements/references/link';
3 |
4 | HasManyReference.prototype.links = function(name) {
5 | if (name) {
6 | return this._links[name];
7 | }
8 |
9 | return Object.keys(this._links).map((name) => this[name], this);
10 | };
11 |
12 | HasManyReference.prototype.__update_links = function(links) {
13 | this._links = {};
14 |
15 | Object.keys(links || {}).forEach((name) => {
16 | let { href, meta } = links[name];
17 |
18 | href = href || links[name];
19 |
20 | let { store } = this;
21 |
22 | let link = new LinkReference({ parentRef: this, store, name, href, meta });
23 |
24 | this._links[name] = link;
25 | });
26 | };
27 |
28 | HasManyReference.prototype.modelName = function() {
29 | return this.type;
30 | }
31 |
32 | HasManyReference.prototype.parentRef = function() {
33 | return this.internalModel.recordReference;
34 | }
35 |
--------------------------------------------------------------------------------
/tests/integration/belongs-to-reference-test.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements";
2 | import Ember from "ember";
3 | import Pretender from "pretender";
4 | import { moduleFor, test } from "ember-qunit";
5 |
6 | const { run } = Ember;
7 |
8 | function runify(context, method) {
9 | context[method] = function() {
10 | return run(context.store, method, ...arguments);
11 | };
12 | }
13 |
14 | moduleFor("service:store", "BelongsToReference", {
15 | integration: true,
16 |
17 | beforeEach() {
18 | this.server = new Pretender();
19 |
20 | this.store = this.subject();
21 |
22 | runify(this, "push");
23 | },
24 |
25 | afterEach() {
26 | this.server.shutdown();
27 | }
28 | });
29 |
30 | test("parentRef()", async function(assert) {
31 | let book = this.push({
32 | data: {
33 | type: "book",
34 | id: 1
35 | }
36 | });
37 |
38 | let bookRef = book.ref();
39 | let authorRef = book.belongsTo("author");
40 |
41 | assert.deepEqual(bookRef, authorRef.parentRef());
42 | });
43 |
--------------------------------------------------------------------------------
/tests/integration/has-many-reference-test.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements";
2 | import Ember from "ember";
3 | import Pretender from "pretender";
4 | import { moduleFor, test } from "ember-qunit";
5 |
6 | const { run } = Ember;
7 |
8 | function runify(context, method) {
9 | context[method] = function() {
10 | return run(context.store, method, ...arguments);
11 | };
12 | }
13 |
14 | moduleFor("service:store", "HasManyReference", {
15 | integration: true,
16 |
17 | beforeEach() {
18 | this.server = new Pretender();
19 |
20 | this.store = this.subject();
21 |
22 | runify(this, "push");
23 | },
24 |
25 | afterEach() {
26 | this.server.shutdown();
27 | }
28 | });
29 |
30 | test("parentRef()", async function(assert) {
31 | let book = this.push({
32 | data: {
33 | type: "book",
34 | id: 1
35 | }
36 | });
37 |
38 | let bookRef = book.ref();
39 | let chaptersRef = book.hasMany("chapters");
40 |
41 | assert.deepEqual(bookRef, chaptersRef.parentRef());
42 | });
43 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/addon/references/record-array.js:
--------------------------------------------------------------------------------
1 | import LinkReference from 'ember-data-meta-links-improvements/references/link';
2 |
3 | export default class RecordArrayReference {
4 |
5 | constructor({ store, recordArray }) {
6 | this.store = store;
7 | this._recordArray = recordArray;
8 | }
9 |
10 | value() {
11 | return this._recordArray;
12 | }
13 |
14 | meta() {
15 | return this._meta;
16 | }
17 |
18 | links(name) {
19 | if (name) {
20 | return this._links[name];
21 | }
22 |
23 | return Object.keys(this._links).map((name) => this[name], this);
24 | }
25 |
26 | modelName() {
27 | return this._recordArray.type.modelName;
28 | }
29 |
30 | __update_meta(meta) {
31 | this._meta = meta;
32 | }
33 |
34 | __update_links(links) {
35 | this._links = {};
36 |
37 | Object.keys(links || {}).forEach((name) => {
38 | let { href, meta } = links[name];
39 |
40 | href = href || links[name];
41 |
42 | let { store } = this;
43 | let link = new LinkReference({ parentRef: this, store, name, href, meta });
44 |
45 | this._links[name] = link;
46 | });
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | scenarios: [
4 | {
5 | name: 'default',
6 | bower: {
7 | dependencies: { }
8 | }
9 | },
10 | {
11 | name: 'ember-1.13',
12 | bower: {
13 | dependencies: {
14 | 'ember': '~1.13.0'
15 | },
16 | resolutions: {
17 | 'ember': '~1.13.0'
18 | }
19 | }
20 | },
21 | {
22 | name: 'ember-release',
23 | bower: {
24 | dependencies: {
25 | 'ember': 'components/ember#release'
26 | },
27 | resolutions: {
28 | 'ember': 'release'
29 | }
30 | }
31 | },
32 | {
33 | name: 'ember-beta',
34 | bower: {
35 | dependencies: {
36 | 'ember': 'components/ember#beta'
37 | },
38 | resolutions: {
39 | 'ember': 'beta'
40 | }
41 | }
42 | },
43 | {
44 | name: 'ember-canary',
45 | bower: {
46 | dependencies: {
47 | 'ember': 'components/ember#canary'
48 | },
49 | resolutions: {
50 | 'ember': 'canary'
51 | }
52 | }
53 | }
54 | ]
55 | };
56 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ember-data-meta-links-improvements 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 | {{content-for "body-footer"}}
31 | {{content-for "test-body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/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 | },
15 |
16 | APP: {
17 | // Here you can pass flags/options to your application instance
18 | // when it is created
19 | }
20 | };
21 |
22 | if (environment === 'development') {
23 | // ENV.APP.LOG_RESOLVER = true;
24 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
25 | // ENV.APP.LOG_TRANSITIONS = true;
26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
27 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
28 | }
29 |
30 | if (environment === 'test') {
31 | // Testem prefers this...
32 | ENV.locationType = 'none';
33 |
34 | // keep test console output quieter
35 | ENV.APP.LOG_ACTIVE_GENERATION = false;
36 | ENV.APP.LOG_VIEW_LOOKUPS = false;
37 |
38 | ENV.APP.rootElement = '#ember-testing';
39 | }
40 |
41 | if (environment === 'production') {
42 |
43 | }
44 |
45 | return ENV;
46 | };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-data-meta-links-improvements",
3 | "version": "0.0.2",
4 | "description": "POC for https://github.com/emberjs/rfcs/pull/160",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "build": "ember build",
11 | "start": "ember server",
12 | "test": "ember try:each"
13 | },
14 | "repository": "https://github.com/pangratz/ember-data-meta-links-improvements",
15 | "engines": {
16 | "node": ">= 0.10.0"
17 | },
18 | "author": "Clemens Müller ",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "broccoli-asset-rev": "^2.4.2",
22 | "ember-ajax": "^2.0.1",
23 | "ember-cli": "2.7.0",
24 | "ember-cli-app-version": "^1.0.0",
25 | "ember-cli-dependency-checker": "^1.2.0",
26 | "ember-cli-eslint": "1.7.0",
27 | "ember-cli-htmlbars": "^1.0.3",
28 | "ember-cli-htmlbars-inline-precompile": "^0.3.1",
29 | "ember-cli-inject-live-reload": "^1.4.0",
30 | "ember-cli-pretender": "0.6.0",
31 | "ember-cli-qunit": "^2.0.0",
32 | "ember-cli-release": "^0.2.9",
33 | "ember-cli-sri": "^2.1.0",
34 | "ember-cli-test-loader": "^1.1.0",
35 | "ember-cli-uglify": "^1.2.0",
36 | "ember-data": "^2.7.0",
37 | "ember-disable-prototype-extensions": "^1.1.0",
38 | "ember-export-application-global": "^1.0.5",
39 | "ember-load-initializers": "^0.5.1",
40 | "ember-resolver": "^2.0.3",
41 | "ember-welcome-page": "^1.0.1",
42 | "loader.js": "^4.0.1"
43 | },
44 | "keywords": [
45 | "ember-addon"
46 | ],
47 | "dependencies": {
48 | "ember-cli-babel": "^5.1.6"
49 | },
50 | "ember-addon": {
51 | "configPath": "tests/dummy/config",
52 | "versionCompatibility": {
53 | "ember-data": ">=2.5.0"
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/addon/patches/store.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 |
3 | const { Store, Model } = DS;
4 |
5 | Store.reopen({
6 |
7 | push(data) {
8 | let pushed = this._super(...arguments);
9 |
10 | if (Model.detectInstance(pushed)) {
11 | let recordRef = pushed.ref();
12 | recordRef.__update_meta(data.data.meta);
13 | recordRef.__update_responseMeta(data.meta);
14 |
15 | let relationships = data.data.relationships || {};
16 | Object.keys(relationships).forEach((name) => {
17 | let links = relationships[name].links;
18 | pushed.hasMany(name).__update_links(links);
19 | });
20 | }
21 |
22 | if (Array.isArray(pushed)) {
23 | data.data.forEach(({ type, id, meta }) => {
24 | let recordReference = this.getReference(type, id);
25 | recordReference.__update_meta(meta);
26 | recordReference.__update_responseMeta(data.meta);
27 | });
28 | }
29 |
30 | if (Array.isArray(data.included)) {
31 | data.included.forEach(({ type, id, meta }) => {
32 | this.getReference(type, id).__update_meta(meta);
33 | });
34 | }
35 |
36 | if (data.__is_findAll) {
37 | let recordArray = this.peekAll(data.__primaryModelName);
38 | recordArray.ref().__update_meta(data.meta);
39 | recordArray.ref().__update_links(data.links);
40 | }
41 |
42 | return pushed;
43 | },
44 |
45 | didSaveRecord(internalModel, { data }) {
46 | internalModel.recordReference.__update_meta(data.meta);
47 | internalModel.recordReference.__update_responseMeta(data._top_level_meta);
48 | return this._super(...arguments);
49 | },
50 |
51 | loadLink(link) {
52 | let modelName = link.parentRef().modelName();
53 | let href = link.href();
54 | let model = this.modelFor(modelName);
55 | let adapter = this.adapterFor(modelName);
56 | let serializer = this.serializerFor(modelName);
57 |
58 | return adapter.findLink(href).then((payload) => {
59 | // TODO handle case where primary data of link is single resource
60 | let normalized = serializer.normalizeArrayResponse(this, model, payload);
61 | let pushed = this.push(normalized);
62 | let recordArray = this.recordArrayManager.createAdapterPopulatedRecordArray(modelName, {});
63 | recordArray.loadRecords(pushed, normalized);
64 | return recordArray;
65 | });
66 | }
67 |
68 | });
69 |
--------------------------------------------------------------------------------
/tests/integration/findAll-test.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements";
2 | import Ember from "ember";
3 | import Pretender from "pretender";
4 | import { moduleFor, test } from "ember-qunit";
5 |
6 | const { run } = Ember;
7 |
8 | function runify(context, method) {
9 | context[method] = function() {
10 | return run(context.store, method, ...arguments);
11 | };
12 | }
13 |
14 | moduleFor("service:store", "store.findAll", {
15 | integration: true,
16 |
17 | beforeEach() {
18 | this.server = new Pretender();
19 |
20 | this.store = this.subject();
21 |
22 | runify(this, "findAll");
23 | },
24 |
25 | afterEach() {
26 | this.server.shutdown();
27 | }
28 | });
29 |
30 | test("ref().value()", async function(assert) {
31 | this.server.get('/books', function() {
32 | return [200, {}, JSON.stringify({
33 | data: [{
34 | id: 1,
35 | type: 'book'
36 | }]
37 | })];
38 | });
39 |
40 | let books = await this.findAll('book');
41 |
42 | assert.deepEqual(books, books.ref().value());
43 | });
44 |
45 | test("links()", async function(assert) {
46 | this.server.get('/books', function() {
47 | return [200, {}, JSON.stringify({
48 | data: [{
49 | id: 1,
50 | type: 'book'
51 | }],
52 | links: {
53 | next: {
54 | href: "next-link",
55 | meta: {
56 | next: true
57 | }
58 | }
59 | }
60 | })];
61 | });
62 |
63 | let books = await this.findAll('book');
64 |
65 | let nextLink = books.ref().links("next");
66 | assert.ok(nextLink);
67 | assert.equal(nextLink.name(), "next");
68 | assert.equal(nextLink.href(), "next-link");
69 | assert.deepEqual(nextLink.meta(), { next: true });
70 | });
71 |
72 | test("meta()", async function(assert) {
73 | this.server.get('/books', function() {
74 | return [200, {}, JSON.stringify({
75 | data: [{
76 | id: 1,
77 | type: 'book'
78 | }],
79 | meta: {
80 | topLevel: true
81 | }
82 | })];
83 | });
84 |
85 | let books = await this.findAll('book');
86 |
87 | let booksRef = books.ref();
88 | assert.deepEqual(booksRef.meta(), { topLevel: true });
89 | });
90 |
91 | test("meta() is updated", async function(assert) {
92 | let counter;
93 |
94 | this.server.get('/books', function() {
95 | return [200, {}, JSON.stringify({
96 | data: [{
97 | id: 1,
98 | type: 'book'
99 | }],
100 | meta: {
101 | topLevel: counter
102 | }
103 | })];
104 | });
105 |
106 | counter = "first";
107 | let books = await this.findAll('book');
108 |
109 | let booksRef = books.ref();
110 | assert.deepEqual(booksRef.meta(), { topLevel: "first" });
111 |
112 | counter = "second";
113 | await this.findAll('book', { reload: true });
114 |
115 | assert.deepEqual(booksRef.meta(), { topLevel: "second" });
116 | });
117 |
--------------------------------------------------------------------------------
/tests/integration/query-test.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements";
2 | import Ember from "ember";
3 | import Pretender from "pretender";
4 | import { moduleFor, test } from "ember-qunit";
5 |
6 | const { run } = Ember;
7 |
8 | function runify(context, method) {
9 | context[method] = function() {
10 | return run(context.store, method, ...arguments);
11 | };
12 | }
13 |
14 | moduleFor("service:store", "store.query", {
15 | integration: true,
16 |
17 | beforeEach() {
18 | this.server = new Pretender();
19 |
20 | this.store = this.subject();
21 |
22 | runify(this, "query");
23 | },
24 |
25 | afterEach() {
26 | this.server.shutdown();
27 | }
28 | });
29 |
30 | test("ref().value()", async function(assert) {
31 | this.server.get('/books', function() {
32 | return [200, {}, JSON.stringify({
33 | data: [{
34 | id: 1,
35 | type: 'book'
36 | }]
37 | })];
38 | });
39 |
40 | let books = await this.query('book', {});
41 |
42 | assert.deepEqual(books, books.ref().value());
43 | });
44 |
45 | test("links()", async function(assert) {
46 | this.server.get('/books', function() {
47 | return [200, {}, JSON.stringify({
48 | data: [{
49 | id: 1,
50 | type: 'book'
51 | }],
52 | links: {
53 | next: {
54 | href: "next-link",
55 | meta: {
56 | next: true
57 | }
58 | }
59 | }
60 | })];
61 | });
62 |
63 | let books = await this.query('book', {});
64 |
65 | let nextLink = books.ref().links("next");
66 | assert.ok(nextLink);
67 | assert.equal(nextLink.name(), "next");
68 | assert.equal(nextLink.href(), "next-link");
69 | assert.deepEqual(nextLink.meta(), { next: true });
70 | });
71 |
72 | test("links().load()", async function(assert) {
73 | this.server.get('/books', function() {
74 | return [200, {}, JSON.stringify({
75 | data: [{
76 | id: 1,
77 | type: 'book'
78 | }],
79 | links: {
80 | next: "/next-link",
81 | prev: "/prev-link"
82 | }
83 | })];
84 | });
85 |
86 | let books = await this.query('book', {});
87 |
88 | this.server.get('/next-link', function() {
89 | return [200, {}, JSON.stringify({
90 | data: [{
91 | type: "book",
92 | id: 2
93 | }],
94 | meta: {
95 | isNext: true
96 | }
97 | })];
98 | });
99 |
100 | let nextLink = books.ref().links("next");
101 | let next = await nextLink.load();
102 |
103 | assert.equal(next.get('length'), 1);
104 | assert.deepEqual(next.ref().meta(), { isNext: true });
105 |
106 | let book2Ref = this.store.getReference("book", 2);
107 | assert.deepEqual(book2Ref.meta("response"), { isNext: true });
108 | });
109 |
110 | test("meta()", async function(assert) {
111 | this.server.get('/books', function() {
112 | return [200, {}, JSON.stringify({
113 | data: [{
114 | id: 1,
115 | type: 'book'
116 | }],
117 | meta: {
118 | topLevel: true
119 | }
120 | })];
121 | });
122 |
123 | let books = await this.query('book', {});
124 |
125 | let booksRef = books.ref();
126 | assert.deepEqual(booksRef.meta(), { topLevel: true });
127 | });
128 |
--------------------------------------------------------------------------------
/tests/integration/has-many-links-test.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements";
2 | import Ember from "ember";
3 | import Pretender from "pretender";
4 | import { moduleFor, test } from "ember-qunit";
5 |
6 | const { run } = Ember;
7 |
8 | function runify(context, method) {
9 | context[method] = function() {
10 | return run(context.store, method, ...arguments);
11 | };
12 | }
13 |
14 | moduleFor("service:store", "hasMany links", {
15 | integration: true,
16 |
17 | beforeEach() {
18 | this.server = new Pretender();
19 |
20 | this.store = this.subject();
21 |
22 | runify(this, "push");
23 | runify(this, "pushPayload");
24 | runify(this, "findRecord");
25 | runify(this, "createRecord");
26 | runify(this, "queryRecord");
27 | },
28 |
29 | afterEach() {
30 | this.server.shutdown();
31 | }
32 | });
33 |
34 | test("no links", function(assert) {
35 | let book = this.push({
36 | data: {
37 | id: 1,
38 | type: 'book',
39 | relationships: {
40 | chapters: {
41 | data: []
42 | }
43 | }
44 | }
45 | });
46 |
47 | let chaptersRef = book.hasMany("chapters");
48 |
49 | let links = chaptersRef.links();
50 | assert.equal(links.length, 0);
51 | });
52 |
53 | test("links()", function(assert) {
54 | let book = this.push({
55 | data: {
56 | id: 1,
57 | type: 'book',
58 | relationships: {
59 | chapters: {
60 | links: {
61 | self: {
62 | href: "self-link",
63 | meta: {
64 | chapters: true
65 | }
66 | },
67 | related: "related-link"
68 | }
69 | }
70 | }
71 | }
72 | });
73 |
74 | let chaptersRef = book.hasMany("chapters");
75 |
76 | let links = chaptersRef.links();
77 | assert.equal(links.length, 2);
78 | });
79 |
80 | test("links().load()", async function(assert) {
81 | let book = this.push({
82 | data: {
83 | id: 1,
84 | type: 'book',
85 | relationships: {
86 | chapters: {
87 | links: {
88 | self: "/self-link",
89 | related: "/related-link"
90 | }
91 | }
92 | }
93 | }
94 | });
95 |
96 | let chaptersRef = book.hasMany("chapters");
97 |
98 | this.server.get('/self-link', function() {
99 | return [200, {}, JSON.stringify({
100 | data: [],
101 | meta: {
102 | isSelf: true
103 | }
104 | })];
105 | });
106 |
107 | let next = await chaptersRef.links("self").load();
108 |
109 | assert.deepEqual(next.ref().meta(), { isSelf: true });
110 | });
111 |
112 | test("links(name) with link being an object", function(assert) {
113 | let book = this.push({
114 | data: {
115 | id: 1,
116 | type: 'book',
117 | relationships: {
118 | chapters: {
119 | links: {
120 | self: {
121 | href: "self-link",
122 | meta: {
123 | chapters: true
124 | }
125 | }
126 | }
127 | }
128 | }
129 | }
130 | });
131 |
132 | let chaptersRef = book.hasMany("chapters");
133 |
134 | let selfLink = chaptersRef.links("self");
135 | assert.ok(selfLink);
136 | assert.equal(selfLink.name(), "self");
137 | assert.equal(selfLink.href(), "self-link");
138 | assert.deepEqual(selfLink.meta(), {
139 | chapters: true
140 | });
141 | });
142 |
143 | test("links(name) with link being a string", function(assert) {
144 | let book = this.push({
145 | data: {
146 | id: 1,
147 | type: 'book',
148 | relationships: {
149 | chapters: {
150 | links: {
151 | related: "related-link"
152 | }
153 | }
154 | }
155 | }
156 | });
157 |
158 | let chaptersRef = book.hasMany("chapters");
159 |
160 | let relatedLink = chaptersRef.links("related");
161 | assert.ok(relatedLink);
162 | assert.equal(relatedLink.name(), "related");
163 | assert.equal(relatedLink.href(), "related-link");
164 | assert.notOk(relatedLink.meta());
165 | });
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ember-data-meta-links-improvements
2 |
3 | [](https://travis-ci.org/pangratz/ember-data-meta-links-improvements)
4 | [](https://emberobserver.com/addons/ember-data-meta-links-improvements)
5 |
6 | This addon is a Proof of Concept for
7 | [RFC#160](https://github.com/emberjs/rfcs/pull/160) which aims to improve the
8 | meta and links situation within Ember Data.
9 | The goal is to see how the proposed API solves use cases when used in real
10 | applications. The outcome of this addon should be a profound test suite which
11 | covers all the use cases of the RFC.
12 |
13 | :warning: The current implementation heavily relies on patches of Ember Data
14 | internals, so it is definitely not encouraged to use in production, as
15 | stability and decent performance can not be guaranteed. Also, the proposed API
16 | of the RFC and this addon might diverge, though the goal is to keep them in
17 | alignment within a narrow time frame :warning:
18 |
19 | Currently the following improvements are implemented:
20 |
21 | - [x] [single record meta data](#single-record-meta-data) ([tests](tests/integration/record-meta-test.js))
22 | - [x] get record level meta data via `record.ref().meta()`
23 | - [x] get response level meta data for single resource via `record.ref().meta("response")`
24 | - [ ] single record links
25 | - [ ] links for relationship references
26 | - [ ] belongs to
27 | - [x] [has many](#has-many-links) ([tests](tests/integration/has-many-links-test.js))
28 | - [ ] meta and links for finders
29 | - [ ] queryRecord (meta works, links still missing)
30 | - [x] [query](#storequery) ([tests](tests/integration/query-test.js))
31 | - [x] [findAll](#storefindall) ([tests](tests/integration/findAll-test.js))
32 | - [ ] get reference for has-many via `hasManyRelationship.ref()`
33 | - [x] get parent reference for relationship reference
34 | - [x] belongs to ([tests](tests/integration/belongs-to-reference-test.js))
35 | - [x] has many ([tests](tests/integration/has-many-reference-test.js))
36 | - [ ] add hook when new data is received
37 | - [ ] `Model#didReceiveData`
38 | - [ ] further miscellaneous changes
39 | - [x] get parent reference of link via `linkRef.parentRef()`
40 |
41 | ## Installation
42 |
43 | `ember install ember-data-meta-links-improvements`
44 |
45 | ## Code samples
46 |
47 | ### Single record meta data
48 |
49 | ```js
50 | // GET /books/1
51 | // {
52 | // data: {
53 | // type: "book",
54 | // id: 1,
55 | // meta: {
56 | // recordLevel: true
57 | // }
58 | // },
59 | // meta: {
60 | // topLevel: true
61 | // }
62 | // }
63 | this.store.findRecord('book', 1).then(function(book) {
64 | // get reference for record
65 | let bookRef = book.ref();
66 |
67 | // get record level meta data
68 | let meta = bookRef.meta();
69 | meta === { recordLevel: true };
70 |
71 | // get response level meta data
72 | let topLevelMeta = bookRef.meta("response");
73 | topLevelMeta === { topLevel: true };
74 | });
75 | ```
76 |
77 | ### Has-many links
78 |
79 | ```js
80 | // GET /books/1
81 | // {
82 | // data: {
83 | // type: "book",
84 | // id: 1,
85 | // relationships: {
86 | // chapters: {
87 | // links: {
88 | // related: "related-link",
89 | // self: {
90 | // href: "self-link",
91 | // meta: {
92 | // selfLink: true
93 | // }
94 | // }
95 | // }
96 | // }
97 | // }
98 | // }
99 | // }
100 | this.store.findRecord('book', 1).then(function(book) {
101 | let chaptersRef = book.hasMany("chapters");
102 |
103 | let related = chaptersRef.links("related");
104 | related.href() === "related-link";
105 |
106 | let next = chaptersRef.links("self");
107 | next.meta() === { selfLink: true };
108 |
109 | // GET /self-link
110 | // {
111 | // data: [],
112 | // meta: {
113 | // isSelf: true
114 | // }
115 | // }
116 | next.load().then(function(nextArray) {
117 | nextArray.ref().meta() === { isSelf: true }
118 | });
119 | });
120 | ```
121 |
122 | ### `store.query`
123 |
124 | ```js
125 | // GET /books?page=2
126 | // {
127 | // data: [{
128 | // type: "book",
129 | // id: 1
130 | // }],
131 | // links: {
132 | // next: {
133 | // href: "/books?page=3",
134 | // meta: {
135 | // isLast: true
136 | // }
137 | // },
138 | // prev: {
139 | // href: "/books?page=1"
140 | // }
141 | // },
142 | // meta: {
143 | // total: 123
144 | // }
145 | // }
146 | let books = await this.store.query('book', { page: 2 }).then(function(books) {
147 | let booksRef = books.ref();
148 |
149 | let prev = booksRef.links("prev");
150 | prev.href() === "/books?page=1";
151 |
152 | let next = booksRef.links("next");
153 | next.meta() === { isLast: true };
154 |
155 | let meta = booksRef.meta();
156 | meta === { total: 123 };
157 | });
158 |
159 | // GET /books?page=3
160 | // {
161 | // data: [{
162 | // type: "book",
163 | // id: 1
164 | // }],
165 | // links: {
166 | // prev: {
167 | // href: "/books?page=2"
168 | // }
169 | // },
170 | // meta: {
171 | // isLastPage: true
172 | // }
173 | // }
174 | let next = await books.ref().links("next").load();
175 |
176 | next.ref().meta() === { isLastPage: true }
177 | ```
178 |
179 | ### `store.findAll`
180 |
181 | ```js
182 | // GET /books
183 | // {
184 | // data: [{
185 | // type: "book",
186 | // id: 1
187 | // }],
188 | // links: {
189 | // self: "self-link"
190 | // },
191 | // meta: {
192 | // total: 123
193 | // }
194 | // }
195 | let books = await this.store.findAll('book');
196 |
197 | let booksRef = books.ref();
198 |
199 | let self = booksRef.links("self");
200 | self.href() === "self-link";
201 |
202 | let meta = booksRef.meta();
203 | meta === { total: 123 };
204 |
205 | // GET /books
206 | // {
207 | // data: [{
208 | // type: "book",
209 | // id: 1
210 | // }],
211 | // meta: {
212 | // total: 456
213 | // }
214 | // }
215 | await this.store.findAll('book', { reload: true });
216 |
217 | booksRef.meta() === { total: 456 };
218 | ```
219 |
220 | # Development
221 |
222 | ## Installation
223 |
224 | * `git clone` this repository
225 | * `npm install`
226 | * `bower install`
227 |
228 | ## Running
229 |
230 | * `ember serve`
231 | * Visit your app at http://localhost:4200.
232 |
233 | ## Running Tests
234 |
235 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)
236 | * `ember test`
237 | * `ember test --server`
238 |
239 | ## Building
240 |
241 | * `ember build`
242 |
243 | For more information on using ember-cli, visit [http://ember-cli.com/](http://ember-cli.com/).
244 |
--------------------------------------------------------------------------------
/tests/integration/record-meta-test.js:
--------------------------------------------------------------------------------
1 | import "ember-data-meta-links-improvements";
2 | import Ember from "ember";
3 | import Pretender from "pretender";
4 | import { moduleFor, test } from "ember-qunit";
5 |
6 | const { run } = Ember;
7 |
8 | function runify(context, method) {
9 | context[method] = function() {
10 | return run(context.store, method, ...arguments);
11 | };
12 | }
13 |
14 | moduleFor("service:store", "single record meta", {
15 | integration: true,
16 |
17 | beforeEach() {
18 | this.server = new Pretender();
19 |
20 | this.store = this.subject();
21 |
22 | runify(this, "push");
23 | runify(this, "pushPayload");
24 | runify(this, "findRecord");
25 | runify(this, "createRecord");
26 | runify(this, "queryRecord");
27 | },
28 |
29 | afterEach() {
30 | this.server.shutdown();
31 | }
32 | });
33 |
34 | test("via store.push(single)", function(assert) {
35 | let book = this.push({
36 | data: {
37 | id: 1,
38 | type: 'book',
39 | meta: {
40 | recordLevel: true
41 | }
42 | },
43 | meta: {
44 | topLevel: true
45 | }
46 | });
47 |
48 | let meta = book.ref().meta();
49 | assert.deepEqual(meta, {
50 | recordLevel: true
51 | });
52 |
53 | let topLevelMeta = book.ref().meta("response");
54 | assert.deepEqual(topLevelMeta, {
55 | topLevel: true
56 | });
57 | });
58 |
59 | test("via store.pushPayload(single)", function(assert) {
60 | this.pushPayload({
61 | data: {
62 | id: 1,
63 | type: 'book',
64 | meta: {
65 | recordLevel: true
66 | }
67 | },
68 | meta: {
69 | topLevel: true
70 | }
71 | });
72 |
73 | let bookRef = this.store.getReference("book", 1);
74 |
75 | let meta = bookRef.meta();
76 | assert.deepEqual(meta, {
77 | recordLevel: true
78 | });
79 |
80 | let topLevelMeta = bookRef.meta("response");
81 | assert.deepEqual(topLevelMeta, {
82 | topLevel: true
83 | });
84 | });
85 |
86 | test("via store.push(array)", function(assert) {
87 | let array = this.push({
88 | data: [{
89 | id: 1,
90 | type: 'book',
91 | meta: {
92 | recordLevel: "first"
93 | }
94 | } , {
95 | id: 2,
96 | type: 'book',
97 | meta: {
98 | recordLevel: "second"
99 | }
100 | }],
101 | meta: {
102 | topLevel: true
103 | }
104 | });
105 |
106 | assert.deepEqual(array[0].ref().meta(), {
107 | recordLevel: "first"
108 | });
109 |
110 | assert.deepEqual(array[1].ref().meta(), {
111 | recordLevel: "second"
112 | });
113 | });
114 |
115 | test("via included", function(assert) {
116 | this.push({
117 | data: null,
118 | included: [{
119 | id: 1,
120 | type: 'book',
121 | meta: {
122 | recordLevel: "first"
123 | }
124 | } , {
125 | id: 2,
126 | type: 'book',
127 | meta: {
128 | recordLevel: "second"
129 | }
130 | }],
131 | meta: {
132 | topLevel: true
133 | }
134 | });
135 |
136 | let firstBookRef = this.store.getReference("book", 1);
137 | assert.deepEqual(firstBookRef.meta(), {
138 | recordLevel: "first"
139 | });
140 |
141 | let secondBookRef = this.store.getReference("book", 2);
142 | assert.deepEqual(secondBookRef.meta(), {
143 | recordLevel: "second"
144 | });
145 |
146 | // TODO API for getting top level meta: store.pushRef()?
147 | });
148 |
149 | test("via store.findRecord()", async function(assert) {
150 | this.server.get('/books/1', function() {
151 | return [200, {}, JSON.stringify({
152 | data: {
153 | type: 'book',
154 | id: 1,
155 | meta: {
156 | recordLevel: true
157 | }
158 | },
159 | meta: {
160 | topLevel: true
161 | }
162 | })];
163 | });
164 |
165 | let book = await this.findRecord('book', 1);
166 |
167 | let meta = book.ref().meta();
168 | assert.deepEqual(meta, {
169 | recordLevel: true
170 | });
171 |
172 | let topLevelMeta = book.ref().meta("response");
173 | assert.deepEqual(topLevelMeta, {
174 | topLevel: true
175 | });
176 | });
177 |
178 | test("via store.queryRecord()", async function(assert) {
179 | this.server.get('/books', function() {
180 | return [200, {}, JSON.stringify({
181 | data: {
182 | type: 'book',
183 | id: 1,
184 | meta: {
185 | recordLevel: true
186 | }
187 | },
188 | meta: {
189 | topLevel: true
190 | }
191 | })];
192 | });
193 |
194 | let book = await this.queryRecord('book', {});
195 |
196 | let meta = book.ref().meta();
197 | assert.deepEqual(meta, {
198 | recordLevel: true
199 | });
200 |
201 | let topLevelMeta = book.ref().meta("response");
202 | assert.deepEqual(topLevelMeta, {
203 | topLevel: true
204 | });
205 | });
206 |
207 | test("via store.createRecord()", async function(assert) {
208 | this.server.post('/books', function() {
209 | return [200, {}, JSON.stringify({
210 | data: {
211 | type: 'book',
212 | id: 1,
213 | meta: {
214 | recordLevel: true
215 | }
216 | },
217 | meta: {
218 | topLevel: true
219 | }
220 | })];
221 | });
222 |
223 | let book = this.createRecord('book');
224 | await run(book, "save");
225 |
226 | let meta = book.ref().meta();
227 | assert.deepEqual(meta, {
228 | recordLevel: true
229 | });
230 |
231 | let topLevelMeta = book.ref().meta("response");
232 | assert.deepEqual(topLevelMeta, {
233 | topLevel: true
234 | });
235 | });
236 |
237 | test("via store.updateRecord()", async function(assert) {
238 | this.server.patch('/books/1', function() {
239 | return [200, {}, JSON.stringify({
240 | data: {
241 | type: 'book',
242 | id: 1,
243 | meta: {
244 | recordLevel: true
245 | }
246 | },
247 | meta: {
248 | topLevel: true
249 | }
250 | })];
251 | });
252 |
253 | let book = this.push({
254 | data: {
255 | type: 'book',
256 | id: 1
257 | }
258 | });
259 | await run(book, "save");
260 |
261 | let meta = book.ref().meta();
262 | assert.deepEqual(meta, {
263 | recordLevel: true
264 | });
265 |
266 | let topLevelMeta = book.ref().meta("response");
267 | assert.deepEqual(topLevelMeta, {
268 | topLevel: true
269 | });
270 | });
271 |
272 | test("works for rest serializer", async function(assert) {
273 | this.server.get('/rest-books/1', function() {
274 | return [200, {}, JSON.stringify({
275 | "rest-book": {
276 | id: 1,
277 | meta: {
278 | recordLevel: true
279 | }
280 | },
281 | meta: {
282 | topLevel: true
283 | }
284 | })];
285 | });
286 |
287 | let book = await this.findRecord("rest-book", 1);
288 |
289 | let meta = book.ref().meta();
290 | assert.deepEqual(meta, {
291 | recordLevel: true
292 | });
293 |
294 | let topLevelMeta = book.ref().meta("response");
295 | assert.deepEqual(topLevelMeta, {
296 | topLevel: true
297 | });
298 | });
299 |
300 | test("pushPayload(single) works with rest serializer", async function(assert) {
301 | this.pushPayload("rest-book", {
302 | "rest-book": {
303 | id: 1,
304 | meta: {
305 | recordLevel: true
306 | }
307 | },
308 | meta: {
309 | topLevel: true
310 | }
311 | });
312 |
313 | let bookRef = this.store.getReference("rest-book", 1);
314 |
315 | let meta = bookRef.meta();
316 | assert.deepEqual(meta, {
317 | recordLevel: true
318 | });
319 |
320 | let topLevelMeta = bookRef.meta("response");
321 | assert.deepEqual(topLevelMeta, {
322 | topLevel: true
323 | });
324 | });
325 |
326 | test("pushPayload(array) works with rest serializer", async function(assert) {
327 | this.pushPayload("rest-book", {
328 | "rest-books": [{
329 | id: 1,
330 | meta: {
331 | recordLevel: true
332 | }
333 | }],
334 | meta: {
335 | topLevel: true
336 | }
337 | });
338 |
339 | let bookRef = this.store.getReference("rest-book", 1);
340 |
341 | let meta = bookRef.meta();
342 | assert.deepEqual(meta, {
343 | recordLevel: true
344 | });
345 |
346 | let topLevelMeta = bookRef.meta("response");
347 | assert.deepEqual(topLevelMeta, {
348 | topLevel: true
349 | });
350 | });
351 |
--------------------------------------------------------------------------------