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. Upcoming Features in ember-data

  2. Upcoming Features in ember-data What happened since May 2015 Ember.run.begin();

  3. None
  4. © http://www.redmondpie.com

  5. None
  6. None
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. WHOIS • Hi, I’m Clemens • @pangratz • ember-data core

    team • counsellor doing counselling • cmueller.418@gmail.com 16
  17. Ember Thalassa 17

  18. Ember Thalassa • They are looking for Ember developers •

    https://www.bookingsync.com/en/jobs 18
  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" } }
  20. Once upon a time 20

  21. Once upon a time • Snapshots • Serializer refactor •

    async Relationships • JSON-API as first class citizen • … 21
  22. Once upon a time • Snapshots • Serializer refactor •

    async Relationships • JSON-API as first class citizen* • … 22
  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}
  24. proper ember-cli addon // instead of import DS from "ember-data";

    const Store = DS.Store; 24
  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
  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
  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
  28. !

  29. recent features • ds-transform-pass-options • ds-boolean-transform-allow-null • ds-finder-include • ds-references

    • ds-overhaul-queryRecord* 29
  30. ds-transform-pass-options • possibility to configure DS.Transform’s for better reusability •

    very first RFC#1 in emberjs/rfcs 30
  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
  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
  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
  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
  35. ds-boolean-transform-allow-null • fixes inconsistency with other transforms • turned on

    in 2.7-beta • eventually might become default in 3.0 35
  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
  37. ds-finder-include store.findRecord("book", 1, { include: "author" }); store.findAll("book", { include:

    "author" }); 37
  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
  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" } }] }
  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
  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
  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
  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
  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
  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
  46. ds-references • RFC #57 • low level API for record

    and relationships • plain JS objects • groundwork for future improvements • available since 2.5 46
  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
  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 } } } } }
  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
  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
  51. upcoming features • ds-improved-ajax • ds-save-include • ds-reset-attribute • ds-pushpayload-return

    • ds-improved-references 51
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  71. ds-improved-references // GET /books store.query("book", {}) 71 { data: [...],

    links: { next: { href: "/books?page=2", meta: { page: 2 } }, ... }, meta: { total: 12 } }
  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 } }
  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
  74. ds-improved-references • model-lifecycle-hooks RFC#123 • enhance-references RFC#160 • JSON-API being

    first class citizen* 74
  75. ds-improved-references • model-lifecycle-hooks RFC#123 • enhance-references RFC#160 • JSON-API being

    first class citizen 75
  76. Ember.run.end();