ember-data upcoming features

ember-data upcoming features

Slides for talk at Ember.js Munich Meetup in June 2016

http://www.meetup.com/Ember-js-Munich/events/231881195/

97c57e7e99431e76fbc04173cca51eab?s=128

Clemens Müller

June 28, 2016
Tweet

Transcript

  1. 3.
  2. 5.
  3. 6.
  4. 7.
  5. 8.
  6. 9.
  7. 10.
  8. 11.
  9. 12.
  10. 13.
  11. 14.
  12. 15.
  13. 16.

    WHOIS • Hi, I’m Clemens • @pangratz • ember-data core

    team • counsellor doing counselling • cmueller.418@gmail.com 16
  14. 18.

    Ember Thalassa • They are looking for Ember developers •

    https://www.bookingsync.com/en/jobs 18
  15. 19.

    ember-twiddle • shoutout to Joost de Vries @joostdvrs • https://canary.ember-twiddle.com/

    19 // twiddle.json { "addons": { "liquid-fire": "latest", "ember-power-select": "1.0.0-alpha.5", "ember-cli-cornify": "latest" } }
  16. 21.

    Once upon a time • Snapshots • Serializer refactor •

    async Relationships • JSON-API as first class citizen • … 21
  17. 22.

    Once upon a time • Snapshots • Serializer refactor •

    async Relationships • JSON-API as first class citizen* • … 22
  18. 23.

    • RFC process & feature flags • all aboard the

    ember release train • emberjs slack community • #-ember-data • #dev-ember-data 23 git diff master..@{may.2015}
  19. 25.

    // instead of import DS from "ember-data"; const Store =

    DS.Store; // you can import directly via import Store from 'ember-data/store'; 25 proper ember-cli addon
  20. 26.

    // instead of import DS from "ember-data"; const Store =

    DS.Store; // you can import directly via import Store from 'ember-data/store'; 26 proper ember-cli addon
  21. 27.

    import Serializer from 'ember-data/serializers/json-api'; import Adapter from 'ember-data/adapters/json-api'; import Transform

    from 'ember-data/transform'; import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import { hasMany, belongsTo } from 'ember-data/relationships'; 27 proper ember-cli addon
  22. 28.

    !

  23. 31.

    ds-transform-pass-options // app/models/blog.js import Model from "ember-data"; export default Model.extend({

    datePublished: attr("my-date"), modifiedAtWithTime: attr("my-date", { format: "YYYY-MM-DD HH:mm" }) }); 31
  24. 32.

    ds-transform-pass-options // app/transforms/my-date.js import Transform from "ember-data/transform"; export default Transform.extend({

    serialize(value, options) { let { format = "YYYY-MM-DD" } = options; return moment(value).format(format); }, deserialize(value, options) { let { format = "YYYY-MM-DD" } = options; return moment(value, format).toDate(); } }); 32
  25. 33.

    ds-boolean-transform-allow-null // app/models/user.js import Model from "ember-data/model"; export default Model.extend({

    hasAcceptedTerms: attr("boolean") }); // { // "user": { // "hasAcceptedTerms": false // } // } store.createRecord("user", {}).serialize(); 33
  26. 34.

    ds-boolean-transform-allow-null // app/models/user.js import Model from "ember-data/model"; export default Model.extend({

    hasAcceptedTerms: attr("boolean", { allowNull: true }) }); // { // "user": { // "hasAcceptedTerms": null // } // } store.createRecord("user", {}).serialize(); 34
  27. 35.
  28. 36.

    ds-finder-include • why no query params for findRecord or findAll?

    • comment by @bmac in #4156 https://git.io/voHn8 • records in partial state • complexity for caching scenarios • include query params for GET requests • http://jsonapi.org/format/#fetching-includes 36
  29. 38.

    ds-finder-include // GET /books/1?include=author store.findRecord("book", 1, { include: "author" });

    // GET /books?include=author store.findAll("book", { include: "author" }); 38
  30. 39.

    ds-finder-include // GET /books/1?include=author store.findRecord("book", 1, { include: "author" });

    // GET /books?include=author store.findAll("book", { include: "author" }); 39 // GET /books/1?include=author { data: { type: "books", id: 1, relationships: { author: { data: { type: "authors", id: 1 } } } }, included: [{ type: "authors", id: 1, attributes: { name: "Tobias O. Fünke" } }] }
  31. 40.

    ds-overhaul-queryRecord* • not a feature, but clarification for usage PR#4300

    • use store.queryRecord() when server returns a single record and id is not known beforehand: • GET /current_user • urlForQueryRecord() • store.query() & firstObject for other cases 40
  32. 41.

    ds-overhaul-queryRecord* // app/adapters/user.js import Adapter from "./application"; export default Adapter.extend({

    urlForQueryRecord(query) { if (query.current) { return "/current_user"; } return this._super(...arguments); } }); let currentUser = store.queryRecord("user", { current: true }); 41
  33. 42.

    ds-overhaul-queryRecord* // app/adapters/user.js import Adapter from "./application"; export default Adapter.extend({

    urlForQueryRecord(query) { if (query.current) { return "/current_user"; } return this._super(...arguments); } }); let currentUser = store.queryRecord("user", { current: true }); // GET /current_user { data: { type: "user", id: 1, ... } } 42
  34. 43.

    ds-overhaul-queryRecord* // app/services/store.js import Store from "ember-data/store"; export default Store.extend({

    queryFirst() { let query = this.query(...arguments); return query.then(function(result) { return result.get("firstObject"); }); } }); 43
  35. 44.

    ds-overhaul-queryRecord* // app/services/store.js import Store from "ember-data/store"; export default Store.extend({

    queryFirst() { let query = this.query(...arguments); return query.then(function(result) { return result.get("firstObject"); }); } }); let pope = store.queryFirst("user", { email: "urbi@orbi.com" }); 44
  36. 45.

    ds-overhaul-queryRecord* // app/services/store.js import Store from "ember-data/store"; export default Store.extend({

    queryFirst() { let query = this.query(...arguments); return query.then(function(result) { return result.get("firstObject"); }); } }); let pope = store.queryFirst("user", { email: "urbi@orbi.com" }); // GET /users?email=... { data: [{ type: "user", id: 1, ... }] } 45
  37. 46.

    ds-references • RFC #57 • low level API for record

    and relationships • plain JS objects • groundwork for future improvements • available since 2.5 46
  38. 47.

    ds-references • check if record / relationship is loaded without

    triggering request • public API to get id / ids of relationship without triggering request • (re)load relationship • get meta of relationship 47
  39. 48.

    ds-references let book = store.peekRecord("book", 1); let authorRef = book.belongsTo("author");

    let meta = authorRef.meta(); let id = authorRef.id(); // null if not yet loaded let author = authorRef.value(); authorRef.load().then(function(author) { }); authorRef.reload().then(...); 48 { data: { type: "books", id: 1, relationships: { author: { data: { type:" authors", id: 2 }, meta: { isFamous: true } } } } }
  40. 49.

    ds-references let book = store.peekRecord("book", 1); let chaptersRef = book.hasMany("chapters");

    let meta = chaptersRef.meta(); let ids = chaptersRef.ids(); // null if not yet loaded let chapters = chaptersRef.value(); chaptersRef.load().then(function(chapters) { }); chaptersRef.reload().then(...); 49
  41. 50.

    ds-references let bookRef = store.getReference("book", 1); let id = bookRef.id();

    // null if not yet loaded let value = bookRef.value(); bookRef.load().then(function(book) { }); bookRef.reload().then(function(book) { }); 50
  42. 52.

    ds-improved-ajax • tackles problem of overwriting of private ajax() and

    ajaxOptions() • new public adapter hooks to get properties of request • uses ajax() and ajaxOptions() if present and log a deprecation • plan is to eventually use ember-ajax 52
  43. 53.

    ds-improved-ajax // app/adapters/application.js import Adapter from "ember-data/adapters/rest"; export default Adapter.extend({

    methodForRequest(options) {}, urlForRequest(options) {}, dataForRequest(options) {}, headersForRequest(options) {} }); 53
  44. 54.

    ds-improved-ajax // app/adapters/application.js import Adapter from "ember-data/adapters/rest"; export default Adapter.extend({

    methodForRequest(options) {}, urlForRequest(options) {}, dataForRequest(options) {}, headersForRequest(options) {} }); { requestType: "...", snapshot, snapshots, id, ids, query, store, type } 54
  45. 55.

    ds-improved-ajax // app/adapters/application.js import Adapter from "ember-data/adapters/rest"; export default Adapter.extend({

    methodForRequest(options) {}, urlForRequest(options) {}, dataForRequest(options) {}, headersForRequest(options) {} }); { requestType: "query", query, store, type } 55
  46. 56.

    ds-improved-ajax // app/adapters/application.js import Adapter from "ember-data/adapters/rest"; export default Adapter.extend({

    methodForRequest(options) {}, urlForRequest(options) {}, dataForRequest(options) {}, headersForRequest(options) {} }); { requestType: "updateRecord", snapshot, id, store, type } 56
  47. 57.

    ds-improved-ajax // app/adapters/application.js import Adapter from "ember-data/adapters/rest"; export default Adapter.extend({

    methodForRequest({ requestType }) { if (requestType === "createRecord") { return "PUT"; } return this._super(...arguments); } }); 57
  48. 58.

    ds-improved-ajax // app/adapters/application.js import Adapter from "ember-data/adapters/rest"; export default Adapter.extend({

    headersForRequest({ requestType, snapshot }) { let headers = this._super(...arguments); if (requestType === "findRecord") { headers["If-None-Match"] = snapshot.get("etag"); } return headers; } }); 58
  49. 59.

    ds-save-include • basically ds-finder-include for save() • currently unsure about

    specific API • book.save({ include: "author" }) • does it include author relationship in request? • should author be included on response? • both? • discussion at PR #4354 59
  50. 60.

    ds-reset-attribute • record.resetAttribute() to reset an attr to its latest

    acknowledged value • last canonical value pushed into store • the last inFlight value 60
  51. 61.

    ds-reset-attribute let book = store.peekRecord("book", 1); book.get("title") === "old title");

    book.set("title", "new title"); book.resetAttribute("title"); book.get("title") === "old title"); book.set("title", "new title"); book.save(); book.set("title", "updated title"); book.resetAttribute("title"); book.get("title") === "new title"); 61
  52. 62.

    ds-pushpayload-return • return pushed records for store.pushPayload() • not yet

    go’ed • possible performance implications • shut down the door on API if materialized records are returned for store.pushPayload() 62
  53. 63.

    ds-pushpayload-return let pushedUsers = store.pushPayload({ users: [ { id: 1,

    email: "george.oscar@bluths.com" }, { id: 2, email: "mr.manager@bluths.com" } ] }); // [<App:User:1>, <App:User:2>] Ember.Logger.info(pushedUsers); // get materialized record let gob = pushedUsers[0]; 63
  54. 64.

    ds-pushpayload-return let refs = store.pushPayload({ users: [ { id: 1,

    email: "george.oscar@bluths.com" }, { id: 2, email: "mr.manager@bluths.com" } ] }); // [<RecordReference:User:1>, <RecordReference:User:2>] Ember.Logger.info(refs); // get materialized record let gob = refs[0].value(); 64
  55. 65.

    ds-pushpayload-return let normalized = store.normalizePayload({ users: [ { id: 1,

    email: "george.oscar@bluths.com" }, { id: 2, email: "mr.manager@bluths.com" } ] }); let refs = store.pushRef(normalized); // [<RecordReference:User:1>, <RecordReference:User:2>] Ember.Logger.info(refs); // get materialized record let gob = refs[0].value(); 65
  56. 66.

    ds-improved-references • ds-references laid groundwork • next up are enhancements

    for missing parts • model lifecycle hooks RFC#123 • more type of references RFC#150 66
  57. 67.

    ds-improved-references // app/models/books.js import Model from "ember-data/model"; let Book =

    Model.extend(); Book.reopenClass({ didReceiveData(bookRef) { let book = bookRef.value(); let meta = bookRef.meta(); if (book && meta) { book.set("meta", meta); } } }); export default Book; 67
  58. 68.

    ds-improved-references // app/models/books.js import Model from "ember-data/model"; let Book =

    Model.extend(); Book.reopenClass({ didReceiveRelationship(parent, name, relRef) { if (name === "author") { let book = parent.value(); let meta = relRef.meta(); book.set("isFamousAuthor", meta.isFamous); } } }); export default Book; 68
  59. 69.

    ds-improved-references // app/models/books.js import Model from "ember-data/model"; let Book =

    Model.extend(); Book.reopenClass({ didReceiveRelationship(parent, name, relRef) { if (name === "chapters") { let book = parent.value(); let ids = relRef.ids(); book.set("chapterIds", ids); } } }); export default Book; 69
  60. 70.

    ds-improved-references // app/models/books.js import Model from "ember-data/model"; let Book =

    Model.extend(); Book.reopenClass({ didReceiveRecordArray(recordArrayRef) { let recordArray = recordArrayRef.value(); let meta = recordArrayRef.meta(); recordArray.set("meta", meta); } }); export default Book; 70
  61. 71.

    ds-improved-references // GET /books store.query("book", {}) 71 { data: [...],

    links: { next: { href: "/books?page=2", meta: { page: 2 } }, ... }, meta: { total: 12 } }
  62. 72.

    ds-improved-references // GET /books store.query("book", {}).then((books) => { let ref

    = books.ref(); ref.meta().total === 12; let nextPageRef = ref.links("next"); nextPageRef.meta().page === 2; // GET /books?page=2 nextPageRef.load().then(…); }); 72 { data: [...], links: { next: { href: "/books?page=2", meta: { page: 2 } }, ... }, meta: { total: 12 } }
  63. 73.

    ds-improved-references // GET /books/1/relationships/chapters?include=bookmarks book.hasMany("chapters").load({ include: "bookmarks" }); // PATCH

    /books/1/relationships/chapters book.hasMany("chapters").save(); // DELETE /books/1/relationships/chapters book.hasMany("chapters").delete(); 73