Ember Data

@jniechcial

Introduction

Why is it worth to use any kind of data storage layer?

Backend-agnostic

Reusable

Caching mechanism

Mirroring server logic

Asynchrony handling

CRUD ready

Introduction

Does it have any disadvantages?

API specs

Huge codebase

Conventions

Steep learning curve

Edge cases

Hard to debug

Ember Data

Schema

Store

Global reference for local data

Store is the container for all fetched data

It's the service, injected to controllers and route

Naturally, it's a singleton

Model

Class of objects representing your data

Model is a class, record is an instance

Store contains all records fetched from API

Serializer

Instantiate your records from raw JSON data

Allows to normalize custom JSON responses to ember-data format

Adapter

Translate store API to backend requests

Responsible for fetching data that is not cached yet

Protocol independent - WebSocket, HTTP, etc.

Caching mechanism

Caches each fetched data to always serve same record

Fetches new data and stores it in identity map

Keeps your records in sync between routes

Easy to cache single instances; hard to cache queries

Summary

Ember Data provide multiple abstraction layers

Customization should stop on serializer layer

Model

Attributes

Filled in during deserialization of fetched data

Can have specified type or no type at all

Works like any other object property for computed properties

Can have defaultValue specified as an option

Transforms

Are executed during serialization and deserialization

Provides process layer

between backend and Ember app data

RESTAdapter provides

date, number, boolean and string

Examples of custom transforms?

Coordinate point, binary image, UTC date, etc.

Relationships (1)

One-to-one relationship belongsTo

One-to-many and many-to-many relationship 

hasMany

Lack of dirty tracking in relationship

What if one model is fetched with relationship while the other one is not?

Relationships (2)

Optional explicit inverses

Reflexive relationship

And what if I want to define multiple relationships to same model type?

Or I need a relationship to myself?

Relationships (3)

Polymorphic relationships

Not so flexible as in Rails

Demands common base class that other objects extends from

CRUD

Store let's you create record

Record provides save method

Record provides deleteRecord method

Record provides destroyRecord method

this.store.createRecord('post', { title: 'Beautiful'});
this.set('post.title', 'New Beauty');
this.get('post').save().then(() => {
  // ... inform user
});
this.get('post').deleteRecord(); // not persisted
this.get('post').save().then(() => {
  // ... inform user
});
this.get('post').destroyRecord().then(() => {
  // ... inform user
});

How does fetching ID after save work?

Syncing data from backend after CRUD actions is ruled by specifications

E.g. in JSONAPI, after successful save backend should respond with 201 HTTP status and mirror document with filled in ID

Store

Fetching records (1)

Caching mechanism

First time called, the data is fetched

Next time,  cache is returned

Fetch data and update in background

Optional reload and backgroundReload

Fetching records (2)

New adapter callbacks for handling

auto reload

shouldReloadRecord(store, snapshot)

shouldBackgroundReloadRecord(store, snapshot)

Metadata

Format of JSON metadata is serializer specific

Metadata are deserialized to meta property on:

each store query request

each belongsTo relationship

each hasMany relationship

this.store.query('post').then((result) => {
  let meta = result.get('meta');
});

Pushing and unloading

Push record to the store's cache on demand without using adapter

(e.g. when you got data via socket)

Unload record from store on demand if it's not needed anymore

Adapter

DS.Adapter

DS.Adapter provides abstract class with all minimal needed methods

DS.ActiveModelAdapter

Based on DS.RESTAdapter

Provides statuses, HTTP methods and URLs crafted for Rails routing mechanism

Adapter customization

Host and namespace

App.ApplicationAdapter = DS.RESTAdapter.extend({
  host: 'https://api.example.com',
  namespace: 'api/v1'
});

Path customization

App.ApplicationAdapter = DS.RESTAdapter.extend({
  pathForType: function(modelName) {
    const underscored = Ember.String.underscore(modelName);
    return Ember.String.pluralize(underscored);
  }
});

By default, the path name (from model name) is pluralized and camelCased

Serializer

DS.Serializer

Minimal working serializer should cover serialize, normalizeResponse and optionally normalize methods

DS.ActiveModelSerializer

ActiveModelSerializer works with AMS in Rails - serializes using underscore, enclose in root key, etc.

DS.JSONAPISerializer

Supports JSONAPI spec and is recommended for Ember Data

Debug Tips

Ember Inspector Data

Chrome Network Tab

Use _internalModel to check out raw Ember Data... data

Don't write goddamn debugger

Use Chrome Developer Tools!

Summary

Architecture of Ember Data

Ember Data is complex, abstract library

It's important to understand the responsibilites of each abstraction layer

Don't be afraid of customization of low layers (serializers and adapters) - back up with official codebase

Models

Be aware of possibilites with transformations and relationships

Remember that your front-end models don't have to be 1-to-1 mirror of backend models

Debugging

Don't use debugger keyword in your code

Everytime you do it, I cry

More to read

Ember Data

By Kuba Niechcial

Ember Data

Presentation for webinar about ember-data @netguru.

  • 1,183