Editor's Note: The Ember.js team has shifted to an expedited release schedule and as of this publication date are on version 1.2.0. This tutorial was written pre-v1.0 but many of the concepts are still applicable. We do our best to commission timely content and these situations happen from time-to-time. We'll work to update this in the future.
In part 3 of my Ember series, I showed you how you can interact with data using Ember's Ember.Object
main base class to create objects that define the methods and properties that act as a wrapper for your data. Here's an example:
App.Item = Ember.Object.extend(); App.Item.reopenClass({ all: function() { return $.getJSON('http://api.ihackernews.com/page?format=jsonp&callback=?').then(function(response) { var items = []; response.items.forEach( function (item) { items.push( App.Item.create(item) ); }); return items; });
In this code, we subclass Ember.Object
using the "extend()
" and create a user-defined method called called "all()
" that makes a request to Hacker News for JSON-formatted results of its news feed.
While this method definitely works and is even promoted by Ember-based Discourse as their way of doing it, it does require that you flesh out and expose the API that you'd like to reference the data with. Most MVC frameworks tend to include ORM-like capabilities so if you're used to Rails, for example, you'd be very familiar with the benefits of ActiveRecord which helps to manage and do the heavy lifting of interacting with data.
The Ember team has wanted to do the same thing but their main focus has been to get a stable v1 release of their core framework out first to ensure that complementary components could be built on a stable foundation. I actually applaud this and I actually made mention of the fact that you should hold off on using Ember Data because of this.
Now that Ember RC8 is out and v1 seems to be coming around the corner, I felt it was a good time to start exploring Ember Data and see what it offers.
Ember Data
The first thing I want to stress is that Ember Data is a work in progress and in much the same way as Ember started, will probably see a number of breaking API changes over the next several months. While that's not ideal, it's important to begin looking at how you would structure your apps using the library. To give you a good description of what Ember Data provides, I've copied in the well-written description from the GitHub page:
Ember Data is a library for loading data from a persistence layer (such as a JSON API), mapping this data to a set of models within your client application, updating those models, then saving the changes back to a persistence layer. It provides many of the facilities you'd find in server-side ORMs like ActiveRecord, but is designed specifically for the unique environment of JavaScript in the browser.
So as I mentioned, it's meant to abstract out a lot of the complexities of working with data.
Using Ember Data
If you've read my previous tutorials, you should be very familiar with how to set up a page to leverage Ember. If you haven't done so, you should go to the Ember.js home page and grab the Starter Kit. You can find it right in the middle of the page as it's displayed via a big button. This will give you the most up-to-date version of Ember which you'll need in order to work with Ember Data. The easiest way to get a downloadable version of Ember Data is to go to the API docs for models
, scroll to the bottom and download the library. Additionally, you can go to the builds
page to pull down the latest builds of any Ember-related library.
Adding Ember Data is as simple as adding another JavaScript file to the mix like this:
<script src="js/libs/jquery-1.9.1.js"></script> <script src="js/libs/handlebars-1.0.0.js"></script> <script src="js/libs/ember-1.0.0-rc.8.js"></script> <script src="js/libs/ember-data.js"></script> <script src="js/app.js"></script>
This now gives you access to Ember Data's objects, method and properties.
Without any configuration, Ember Data can load and save records and relationships served via a RESTful JSON API, provided it follows certain conventions.
Defining a Store
Ember uses a special object called a store
to load models and retrieve data and is based off the Ember DS.Store
class. This is how you'd define a new store:
App.Store = DS.Store.extend({ ... });
If you remember from my previous articles, "App"
is just a namespace created for the application level objects, methods and properties for the application. While it's not a reserved word in Ember, I would urge you to use the same name as almost every tutorial and demo I've seen uses it for consistency.
The store you create will hold the models you create and will serve as the interface with the server you define in your adapter. By default, Ember Data creates and associates to your store a REST adapter based off the DS.RestAdapter
class. If you simply defined the code above, you would have an adapter associated to it by default. Ember magic at its finest. You can also use a Fixture adapter as well if you're working with in-memory-based data (for example, JSON you're loading from code) but since this is about making API calls, the REST adapter is more appropriate.
You can also define your own adapter for those situations where you need more custom control over interfacing with a server by using the adapter
property within your store declaration:
App.Store = DS.Store.extend({ adapter: 'App.MyCustomAdapter' });
Defining Models
The code I listed at the top of this tutorial was an example of how to use Ember.Object
to create the models for your application. Things change a bit when you define models via Ember Data. Ember Data provides another object called DS.Model
which you subclass for every model you want to create. For example, taking the code from above:
App.Item = Ember.Object.extend();
It would now look like this:
App.Item = DS.Model.Extend()
Not much of a difference in terms of appearance but a big difference in terms of functionality since you now have access to the capabilities of the REST adapter as well as Ember Data's built-in relationships like one-to-one, one-to-many and more. The main benefit, though, is that Ember Data provides the hooks for interacting with your data via your models as opposed to you having to roll your own. Referencing the code from above again:
App.Item.reopenClass({ all: function() { return $.getJSON('http://api.ihackernews.com/page?format=jsonp&callback=?').then(function(response) { var items = [];</p> response.items.forEach( function (item) { items.push( App.Item.create(item) ); }); return items; });
While I had to create my own method to return all of the results from my JSON call, Ember Data provides a find()
method which does exactly this and also serves to filter down the results. So in essence, all I have to do is make the following call to return all of my records:
App.Item.find();
The find()
method will send an Ajax request to the URL.
This is exactly what attracts so many developers to Ember; the forethought given to making things easier.
One thing to keep in mind is that it's important to define within the model the attributes you plan on using later on (e.g. in your templates). This is easy to do:
App.Post = DS.Model.extend({ title: DS.attr('string') });
In my demo app, I want to use the title property returned via JSON so using the attr()
method, specify which attributes a model has at my disposal.
One thing I want to mention is that Ember Data is incredibly picky about the structure of the JSON returned. Because Ember leverages directory structures for identifying specific parts of your applications (remember the naming conventions we discussed in my first Ember article?), it makes certain assumptions about the way that the JSON data is structured. It requires that there be a named root which will be used to identify the data to be returned. Here's what I mean:
{ 'posts': [{ 'id': 1, 'title': 'A friend of mine just posted this.', 'url': 'http://i.imgur.com/9pw20NY.jpg' }] }[js] <p>If you had defined it like this:</p> [js]{ { 'id': '1', 'title': 'A friend of mine just posted this.', 'url': 'http://i.imgur.com/9pw20NY.jpg' }, { 'id': '2', 'title': 'A friend of mine just posted this.', 'url': 'http://i.imgur.com/9pw20NY.jpg' }, }
Ember Data would've totally balked and thrown the following error:
Your server returned a hash with the key id but you have no mapping for it.
The reason is that since the model is called "App.Post"
, Ember Data is expecting to find a URL called "posts" from which it will pull the data from. So if I defined my store as such:
App.Store = DS.Store.extend({ url: 'http://emberdata.local' });
and my model like this:
App.Post = DS.Model.extend({ title: DS.attr('string') });
Ember Data would assume that the Ajax request made by the find()
method would look like this:
http://emberdata.local/posts
And if you were making a request for a specific ID (like find(12)), it would look like this:
http://emberdata.local/posts/12
This issue drove me batty, but doing a search found plenty of discussions on it. If you can't set up your JSON results in this way, then you'll have to create a custom adapter to massage the results to properly serialize them before being able to use it. I'm not covering that here but plan to explore more of that soon.
The Demo App
I purposely wanted to keep this tutorial simple because I know Ember Data is changing and I wanted to give a brief overview of what it provided. So I whipped up a quick demo app that uses Ember Data to pull JSON data from my own local server. Let's look at the code.
First I create my application namespace (which you would do for any Ember app):
// Create our Application App = Ember.Application.create({});
Next, I define my data store and I declare the url
from where the model will pull the data from:
App.Store = DS.Store.extend({ url: 'http://emberdata.local'; });
In the model, I specify the attribute: title
, which I'll use in my template later on:
// Our model App.Post = DS.Model.extend({ title: DS.attr('string') });
Lastly, I associate the model to the route via the model hook. Notice that I'm using the predefined Ember Data method find()
to immediately pull back my JSON data as soon as the app is started:
// Our default route. App.IndexRoute = Ember.Route.extend({ model: function() { return App.Post.find(); } });
In the template for the root page (index), I use the #each
Handlebars directive to look through the results of my JSON data and render the title of each of my posts:
<script type="text/x-handlebars" data-template-name="index"> <h2>My Posts</h2> <ul> {{#each post in model}} <li>{{post.title}}</li> {{/each}} </ul> </script></p>
That's it! No Ajax call to make or special methods to work with my data. Ember Data took care of making the XHR call and storing the data.
Fin
Now, this is incredibly simplistic and I don't want to lead you to believe it's all unicorns and puppy dogs. As I went through the process of working with Ember Data, I found myself wanting to go back to using Ember.Object
where I had more control. But I also realize that a lot of work is going on to improve Ember Data, especially in the way it manages diverse data results. So it's important to at least kickstart the process of understanding how this thing works and even offering constructive feedback to the team.
So I urge you to jump in and begin tinkering with it, especially those that have a very strong ORM background and could help shape the direction of Ember Data. Now is the best time to do that.
Comments