A Simplistic Explanation to EmberData Polymorphism
August 18, 2014 10:37 pm Leave your thoughtsI have been implementing Ember data for one of my projects and one of the most interesting handy features that ember data has is polymorphic models. As the name suggests, this is ember data’s implementation of polymorphism. The first time I use ember data however, requires me spending a lot of time researching for the proper implementations. Official documentation on ember data’s polymorphism is pretty scarce that I had to scrounge the internet looking for example usages.
Although there are great examples out there, most of them are quite complicated and can be quite daunting for beginners. Therefore the aim of the article today is to show you the most simplistic implementation of ember data with polymorphism in hope that beginners will have less frustrations or no frustrations at all understanding.
Please note that this article is written with assumptions that you have understood ember data itself.
Case Study and Design
In order to illustrate our explanation today, we will use a very simple case study. Basically we want to display a list of drivers and their “special car”. Keeping things simple, each drivers will only have one car. Each cars have similar characteristics such as the make, drivetrain and image; however since these are “special” cars there are things that are unique to each of them, hence we need to rely on polymorphism so that ember data instantiates the correct car type. You may of course argue that these “special” properties does not require inheritance and polymorphism, but let’s keep that aside as we only want to demonstrate polymorphism.
As usual, the best way to understand what we want to do is by creating a simple design diagram.
Looking at the design, our implementation will be very simple. Each driver has one car and each car can be either an RxSeven (RX7), AeHachiRoku (AE86) or WRX. The three cars derivative will be instantiated by ember data automatically based on the data returned by the server.
Implementations
The libraries that we will use are:
- jQuery
- Handlebars
- Ember
- Ember-data
- jQuery Mockjax (for mocking ajax requests)
We will now go through the javascript codes sections by sections. The full code is available at the end of this article for your learning usages.
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js"></script> <script src="http://builds.emberjs.com/handlebars-1.0.0.js"></script> <script src="http://builds.emberjs.com/ember-latest.js"></script> <script src="https://s3.amazonaws.com/builds.emberjs.com/ember-data-latest.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/1.5.1/jquery.mockjax.js"></script> <script type="text/x-handlebars" data-template-name='index'> <div> <h1>Polymorphism experiment</h1> <p>Experimenting on the generic settings class for driver and cars"</p> <hr /> {{#each content}} <table class="specCard"> <tr> <td> Driver ID: </td> <td> {{id}} </td> </tr> <tr> <td> Driver Name: </td> <td> {{name}} </td> </tr> <tr> <td> Car Make: </td> <td> {{specialCar.make}} </td> </tr> <tr> <td> Drivetrain: </td> <td> {{specialCar.driveTrain}} </td> </tr> {{#if specialCar.powerModifications}} <tr> <td> Power modifications: </td> <td> {{specialCar.powerModifications}} </td> </tr> {{/if}} {{#if specialCar.engineSwap}} <tr> <td> Engine Swap: </td> <td> {{specialCar.engineSwap}} </td> </tr> {{/if}} <tr> <td> Photo: </td> <td> <img {{bind-attr src=specialCar.imgUrl}} /> </td> </tr> </table> <hr /> {{/each}} </div> </script> |
The HTML codes are quite simple. Basically we load up all of our required libraries then render out the template for “index” route. Once the data is received, we loop over the drivers record and display their information and their car. In addition we also check for the specific car attributes such as “engineSwap” or “powerModifications” to see if they exist. These attributes are the “special” attributes that we are talking about for this case study.
JS
Let’s run through the javascript code for our little example application.
1 2 3 4 5 6 7 8 9 10 11 |
App = Ember.Application.create({}); App.ApplicationAdapter = DS.ActiveModelAdapter; App.IndexRoute = Ember.Route.extend({ model: function() { //find all the available drivers return this.store.find('driver'); } }); |
First we create the basic index route so that as soon as the javascript runs, the code will fetch all drivers from the store. The template above will take care of the rendering of the data returned.
Also notice that we are using ActiveModelAdapter, this means our JSON data from the server should be formatted using underscore and not camelCase. To learn more about active model adapter, visit Ember’s official documentation.
Now we define the classes for the drivers and the cars. Let’s start with the driver.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
App.Driver = DS.Model.extend({ name: DS.attr("string"), expertise: DS.attr("string"), specialCar: DS.belongsTo("specialCar", { polymorphic: true, async: true }) }); App.SpecialCar = DS.Model.extend({ driver: DS.belongsTo("driver"), make: DS.attr("string"), driveTrain: DS.attr("string"), imgUrl: DS.attr("string") }); App.AeHachiRoku = App.SpecialCar.extend({ engineSwap: "TRD Group A AE-101", model: "AE-86 Trueno Sprinter" }); App.RxSeven = App.SpecialCar.extend({ powerModifications: "Turbo", model: "RX-7" }); App.Wrx = App.SpecialCar.extend({ powerModifications: "unknown", model: "Impreza WRX STi Coupe Type B" }); |
The most important thing to look at the above is the relationship between Driver and SpecialCar. Notice that we establish the one to one relationship between Driver and SpecialCar through their properties. Driver has a property specialCar which belongs to a SpecialCar object. On the other side, SpecialCar has a property driver which belongs to a Driver object. Think of this as a handshake that establishes the one to one relationship.
In addition to the “DS.belongsTo” relationship, the Driver class also defines that its specialCar property is a polymorphic object. This means when the data is loaded ember-data should instantiate the correct SpecialCar object based on the “type” included in the data. The async settings means that ember-data won’t try to load the data until requested.
Further to the things above, also notice that we have extended the SpecialCar class three times into more specific type of SpecialCars.They are: AeHachiRoku, RxSeven and Wrx classes; each of these classes have non-standard properties.
Let’s continue on with our data, this is probably the most important thing to get this working.
Data
Remember that we are using mockjax, this is simply so that we can fake remote requests. This is a simple example after all so there are no backend API server involved at all.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
$.mockjax({ url: '/drivers', response: function(settings) { var json = { drivers: [ { id: 1, name: 'Fujiwara', special_car: { ///not that the key of the ajax call is using underscore, this is because we are using active model adapter id: 1, type: "aeHachiRoku" } }, { id: 2, name: 'Keisuke', special_car: { id: 2, type: "rxSeven" } }, { id: 3, name: 'Bunta', special_car: { id: 3, type: "wrx" } } ] }; settings.success(json); } }); |
The request above is executed as soon as the application hits the index route. by telling the store to find “driver” ember data automatically looks to find those data from the API since it’s not cached yet.
In terms of initiating the polymorphism feature, take a look at the special_car attribute in the above data for each of the drivers. It is an object with an attribute type. This is the exact attribute that tells ember data what kind of SpecialCar derivatives to instantiate. Note that we use underscore for special_car (due to active model adapter) and the type value always starts with lower-case. Type value also retains the camel-casing as it has to match the name of the the derivative class.
What caught myself and most people when learning about ember-data and polymorphism is the naming convention and choice between camel-case and underscores. Pay attention on what this example is doing.
Continuing on, while looping through the drivers in the template, we are requesting to display car information such as make, drivetrain, etc. This will in turn triggers ember data to fetch more information for each of the specialCar attributes. Thus more remote requests to the API are triggered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
$.mockjax({ url: '/ae_hachi_rokus/1', response: function(settings) { var json = { ae_hachi_roku: { id: 1, make: "Toyota", drive_train: "RWD", img_url: "http://img4.wikia.nocookie.net/__cb20121228172202/initiald/images/thumb/b/b6/Tumblr_md4dpa2DhA1qd1llpo1_500.png/185px-Tumblr_md4dpa2DhA1qd1llpo1_500.png" } }; settings.success(json); } }); $.mockjax({ url: '/rx_sevens/2', response: function(settings) { var json = { rx_seven: { id: 2, make: "Mazda", drive_train: "RWD", img_url: "http://img2.wikia.nocookie.net/__cb20140706184149/initiald/images/thumb/f/f8/Central-anime-initial-d-fifth-stage-02-dvd.jpg/185px-Central-anime-initial-d-fifth-stage-02-dvd.jpg" } }; settings.success(json); } }); $.mockjax({ url: '/wrxes/3', response: function(settings) { var json = { wrx: { id: 3, make: "Subaru", drive_train: "AWD", img_url: "http://img2.wikia.nocookie.net/__cb20121227182505/initiald/images/thumb/3/3f/189705-SubaruImprezaWRX.jpg/185px-189705-SubaruImprezaWRX.webp" } }; settings.success(json); } }); |
The codes above defines what data should be returned when ember-data requests each car types of a particular ID. An interesting thing to note here is that ember-data also takes care of pluralisation in the remote request. Notice that ae_hachi_roku becomes ae_hachi_rokus in the remote request. This is done automatically and for the purpose of the example we assume our API follows ember-data convention 100%.
Results
Below is the result of our codes written in jsFiddle. EmberJS polymorphism is quite simple and easy to use once you understand how to establish the handshake between object relationships to define polymorphic properties as well the results formatting required from the JSON API.
Conclusion
I hope that this short article helps you to start understanding polymorphic features of Ember Data. This is of course just a very small sub-set of the application of polymorphism and object relations. All we are doing in this example is determining one-to-one relationships. Next time I will write more examples using different kinds of object relationships.
Have fun with Ember! As usual, if you have any questions, leave some message. I will do my best to answer them and if I cannot answer them I hope that others more knowledgable will contribute as well.
Full Codes
Here are the JS full codes in JSFiddle.
Tags: emberdata, emberjs, polymorphismCategorised in: Coding, CSS, Javascript