Kuba Niechcial
I am senior software developer and team leader at netguru, Poland. I work mostly with Ember.js, React and Ruby on Rails. I am passionate blogger and you can find out most of my work on my website.
@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
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
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
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
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
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
Ember Inspector Data
Chrome Network Tab
Use _internalModel to check out raw Ember Data... data
Don't write goddamn debugger
Use Chrome Developer Tools!
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
By Kuba Niechcial
Presentation for webinar about ember-data @netguru.
I am senior software developer and team leader at netguru, Poland. I work mostly with Ember.js, React and Ruby on Rails. I am passionate blogger and you can find out most of my work on my website.