Created by Luke Galea / @lukegalea
Nutrition and fitness coaching
"We use ED Polymorphic assocations a lot." - Me
What are polymorphic associations?
Person = DS.Model.extend({ name: attr('string'), neckbeard: true });
Average ember developer
Person = DS.Model.extend({ name: attr('string'), neckbeard: true, pets: hasMany('pet', { polymorphic: true }) });
Person = DS.Model.extend({ name: attr('string'), neckbeard: true, pets: hasMany('pet', { polymorphic: true }) }); Pet = DS.Model.extend({ owner: belongsTo('person') }); Cat = Pet.extend({ lives: attr('number', {defaultValue: 9}) });
App.Person = DS.Model.extend({ name: attr('string'), neckbeard: true, pets: hasMany('pet', { polymorphic: true }) }); Pet = DS.Model.extend({ owner: belongsTo('person') }); Cat = Pet.extend({ lives: attr('number', {defaultValue: 9}) }); Dog = Pet.extend({ fleas: attr('number', {defaultValue: 0}) });
App.Person = DS.Model.extend({ name: attr('string'), neckbeard: true, pets: hasMany('pet', { polymorphic: true }) }); Pet = DS.Model.extend({ owner: belongsTo('person') }); Cat = Pet.extend({ lives: attr('number', {defaultValue: 9}) }); Dog = Pet.extend({ fleas: attr('number', {defaultValue: 0}) }); HoneyBadger = Pet.extend({ viciousness: attr('number', {defaultValue: 0}) });
As of Ember-Data 1.0b11
a common base class is required
App.Pet = DS.Model.extend({ ... }); App.Cat = App.Pet.extend({ ... }); App.Dog = App.Pet.extend({ ... });
There's a Pull Request open to allow unrelated polymorphic types
Assuming that covers the ember side, how do you actually wire it up with a backend?
{ "drawing":{ "id":1, "title":"A house", "shapes":[ { "id":1, "type":"Rectangle" }, { "id":2, "type":"Rectangle" }, { "id":3, "type":"Triangle" } ] } }
Or for a belongsTo
{ "dog":{ ... "owner": { "id":1, "type":"Human" } }
class DrawingSerializer < ActiveModel::Serializer attributes :id, :title, :shapes def shapes object.shapes.map { |e| { id: e.id, type: e.type } } end end
belongsTo
class DogSerializer < ActiveModel::Serializer ... def owner { id: object.owner.id, type: object.owner.type } if object.owner end endThere's a guide that shows how to backport polymorphic true from 0.9 to 0.8 Assuming you've wired it all up, everything should be good!
Cool Stuff
Composing views using polymorphic models
A student curriculum is composed of many types of Cards.
A Card is composed of many primitive elements.
A drawing is made of many shapes:
App.Drawing = DS.Model.extend({ shapes: belongsTo('shape', { polymorphic: true }) });
App.Shape = DS.Model.extend({ x: DS.attr('number'), y: DS.attr('number'), color: DS.attr('string') }); App.Rectangle = App.Shape.extend({ height: DS.attr('number'), width: DS.attr('number') }); App.Circle = App.Shape.extend({ radius: DS.attr('number') });
Shape = DS.Model.extend({ shapeType: function() { this.constructor.typeKey }.property() });
Rendering views dynamically based on type
<p> <label>{{name}}</label> {{view view.elementViewType}} </p>
A helper to dynamically render a component
var H = Em.Handlebars; H.registerHelper('renderComponent', function(componentPath, options) { var component = H.get(this, componentPath, options), helper = H.resolveHelper(options.data.view.container, component); helper.call(this, options); } );
Polymorphic Endpoints
this.store.find('shapes').then(function(shapes) { // ? });
class RectangleSerializer < ActiveModel::Serializer attributes ..., :type end
Store = DS.Store.extend({ push: function(type, data, _partial) { var dataType, modelType, oldRecord, oldType; modelType = oldType = type; dataType = data.type; if (dataType && (this.modelFor(oldType) !== this.modelFor(dataType))) { modelType = dataType; if (oldRecord = this.getById(oldType, data.id)) { this.dematerializeRecord(oldRecord); } } return this._super(this.modelFor(modelType), data, _partial); } });
Bootstrap Endpoints
this.store.find('bootstrap')
Different adapters per type
API can serve references to other APIs
Contrived Example!
{ "user":{ ... "avatar": { "id":"luke@precisionnutrition.com", "type":"gravatar" } }
{ "user":{ ... "avatar": { "id":"luke@precisionnutrition.com", "type":"s3" } }
The Badness
Tight coupling between API and Ember-Data
{ "drawing":{ "id":1, "title":"A house", "shapes":[ { "id":1, "type":"Rectangle" }, { "id":2, "type":"Rectangle" }, { "id":3, "type":"Triangle" } ] } }
Map type key to ember model name
typeMap = { quadrangle: 'rectangle', rectangle: 'quadrangle' }; mapType = function(key) { return typeMap[key.underscore()] || key; };
Endpoint Mapping
this.store.find('quadrangle', 1);
/quadrangles/1 → /rectangles/1
Custom Adapter
ApplicationAdapter = DS.ActiveModelAdapter.extend({ pathForType: function(type) { return this._super( mapType(type) ); } });
↑
/quadrangles/1 → /rectangles/1
JSON Mapping
{ "rectangle": { "id": "1", .... } }
Custom Serializer
ApplicationSerializer = DS.ActiveModelSerializer.extend({ typeForRoot: function(key) { return this._super( mapType(key) ); }, serializeIntoHash: function(data, type, record, options) { var key, typeKey; typeKey = type.typeKey; key = mapType(typeKey); return data[key] = this.serialize(record, options); } });
↑ JSON → Model ↑ Model → JSON
Mission Accomplished?
{ "drawing":{ "id":1, "title":"A house", "shapes":[ { "id":1, "type":"Rectangle" }, { "id":4, "type":"Circle" } ] } }
3 seperate requests
({Number of types} + 1)
This can add up pretty quickly
True story
But at least they are executed concurrently
Sideload
class DrawingSerializer < ActiveModel::Serializer ....... has_many :triangles, include: true, embed: :ids has_many :rectangles, include: true, embed: :ids end
{ "drawing":{ ...... "triangle_ids":[3], // Redundant "rectangle_ids":[1, 2] // Redundant }, "triangles":[ { "id":3, "x":0, "y":0, "fill":"blue", "width":"200", "height":"100", "drawing_id":1 } ], "rectangles":[ ..... ] }
STI?
https://github.com/Bestra/ember-data-sti-guide
Ember.MODEL_FACTORY_INJECTIONS = true;
doubleplusungood
Ember.assert(..., record instanceof this.type);
rectified
App.Shape.detect(myRectangle.constructor)
Presentation
http://lukegalea.github.io/ember_data_polymorphic_presentation
Demo App
https://github.com/PrecisionNutrition/ember-data-polymorphic-full-stack-example