Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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/

Clemens Müller

June 28, 2016
Tweet

More Decks by Clemens Müller

Other Decks in Programming

Transcript

  1. Upcoming Features in ember-data

    View Slide

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

    View Slide

  3. View Slide

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

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. WHOIS

    Hi, I’m Clemens
    • @pangratz
    • ember-data core team
    • counsellor doing counselling
    [email protected]
    16

    View Slide

  17. Ember Thalassa
    17

    View Slide

  18. Ember Thalassa
    • They are looking for Ember developers
    • https://www.bookingsync.com/en/jobs
    18

    View Slide

  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"
    }
    }

    View Slide

  20. Once upon a time
    20

    View Slide

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

    View Slide

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

    View Slide

  23. • RFC process & feature flags
    • all aboard the ember release train
    • emberjs slack community
    • #-ember-data
    • #dev-ember-data
    23
    git diff [email protected]{may.2015}

    View Slide

  24. proper ember-cli addon
    // instead of
    import DS from "ember-data";
    const Store = DS.Store;
    24

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  28. !

    View Slide

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

    View Slide

  30. ds-transform-pass-options
    • possibility to configure DS.Transform’s for better
    reusability
    • very first RFC#1 in emberjs/rfcs
    30

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  37. ds-finder-include
    store.findRecord("book", 1, {
    include: "author"
    });
    store.findAll("book", {
    include: "author"
    });
    37

    View Slide

  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

    View Slide

  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"
    }
    }]
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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: "[email protected]"
    });
    44

    View Slide

  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: "[email protected]"
    });
    // GET /users?email=...
    {
    data: [{
    type: "user",
    id: 1,
    ...
    }]
    }
    45

    View Slide

  46. ds-references
    • RFC #57
    • low level API for record and relationships
    • plain JS objects
    • groundwork for future improvements
    • available since 2.5
    46

    View Slide

  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

    View Slide

  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
    }
    }
    }
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  51. upcoming features
    • ds-improved-ajax
    • ds-save-include
    • ds-reset-attribute
    • ds-pushpayload-return
    • ds-improved-references
    51

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  63. ds-pushpayload-return
    let pushedUsers = store.pushPayload({
    users: [
    { id: 1, email: "[email protected]" },
    { id: 2, email: "[email protected]" }
    ]
    });
    // [, ]
    Ember.Logger.info(pushedUsers);
    // get materialized record
    let gob = pushedUsers[0];
    63

    View Slide

  64. ds-pushpayload-return
    let refs = store.pushPayload({
    users: [
    { id: 1, email: "[email protected]" },
    { id: 2, email: "[email protected]" }
    ]
    });
    // [, ]
    Ember.Logger.info(refs);
    // get materialized record
    let gob = refs[0].value();
    64

    View Slide

  65. ds-pushpayload-return
    let normalized = store.normalizePayload({
    users: [
    { id: 1, email: "[email protected]" },
    { id: 2, email: "[email protected]" }
    ]
    });
    let refs = store.pushRef(normalized);
    // [, ]
    Ember.Logger.info(refs);
    // get materialized record
    let gob = refs[0].value();
    65

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  71. ds-improved-references
    // GET /books
    store.query("book", {})
    71
    {
    data: [...],
    links: {
    next: {
    href: "/books?page=2",
    meta: {
    page: 2
    }
    },
    ...
    },
    meta: {
    total: 12
    }
    }

    View Slide

  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
    }
    }

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  76. Ember.run.end();

    View Slide