Lesson 7

Ember Data

27

⚠️ This tutorial uses an old version of Ember. Some things might have changed!

What is Ember Data?

Ember Data is a library that comes bundled with Ember CLI, and helps you manage the data of your app.


When you think of "data", you might instinctively be reminded about databases or ORMs. But remember that Ember is a front-end only framework, and that goes for Ember Data as well. It makes no direct contact with any database whatsoever.


So what does it do? In a nutshell, Ember Data stores your models in the browser's local storage, pulls them out when you need them and sends AJAX requests that are based on a set of conventions for REST APIs.


Don't worry if the words above sound really confusing right now, we're going to take it one bit at a time in this chapter!

Creating our models

Let's first ask ourselves: what data do we actually need in our app? In Chirper, there are really only two things we have to store and retrieve:

  1. Users

  2. Chirps

Seems easy enough. Let's start with the User model:

We'll make it very simple for now and say that a user only consists of four things: a username, the number of chirps (that the user has written), the number of followers, and the number of followings. Open the generated model-file and add the following attributes:

app/user/model.js

As you can see, we use DS.attr() to specify the data type of the model attributes. You can find all the available types in the Ember Data Documentation.

Installing Ember CLI Mirage

Alright, so we have a model, but we still don't have any actual data. Normally, this data should come from a server-side JSON API, but for now, we want to keep our project front-end only in order to keep experimenting and making quick iterations.


Luckily, we can actually add fake data for now by using a great add-on called ember-cli-mirage. So let's install it!


Note: We're not using the newest version of Mirage, since 0.2.0 introduced a few breaking changes. I'll update this course once I've gone through them all.

Restart your Ember server after the installation is done. Now you should see a new folder: app/mirage. This is where we will put all of our mock data.


Alright, now let's specify how Ember Data should behave in order to fetch our mock data. First of all, we will specify what kind of requests Ember should make, and how the returned data is formatted. This is specified through an adapter. For this tutorial, we're going to use Ember's simple and predefined RESTAdapter throughout the whole application, so let's generate that:

In the adapter.js file, we specify that it is a RESTAdapter we want, and also that we want all data request URLs to be prefixed with /api (otherwise they might collide with our existing route URLs):

app/application/adapter.js

This prefix will also need to be specified in our mirage config.js file:

app/mirage/config.js

Okay, so we have a model and we've specified our adapter. Now, we just need to tell Ember when and where in our app we want this data to be loaded.

Fetching mock data

In our case, we want to load the user data as soon as the "home"-screen is launched, and then populate the profile-glance component with that data. This is what the route.js-files are for: besides connecting to a URL, they also sets up the initial state of the page they correspond to.


When retrieving a data record for a route, we use this.store.findRecord(), where the first argument is the name of the model and the second argument is the id of the record that we want to load. So in the code below, we're loading the user with id 1 and assigning it to the user-key:

app/home/route.js

If we go back to the browser now you should see a blank page. Uh-oh!


Don't worry, this is normal!


Let's bring up the JavaScript console (View > Developer > JavaScript Console) and check if we can see what happened:


Aha!


As you can see, Ember is doing the right thing, trying to fetch some data from our mock server by making a request to /api/users/1. The problem is that we haven't defined our mock data yet, so let's do that now!


We're going to use fixtures for our mock data, so the first thing you need to do is delete the folder app/mirage/scenarios. Then we'll create a folder called fixtures in our new mirage-folder, and in it we'll create a file called users.js:

app/mirage/fixtures/users.js

Finally, we specify the URL that we want to make available in the Mirage config-file. Ember CLI Mirage is smart enough to follow the RESTAdapter's conventions and will return the requested user automagically through one simple line of code!

app/mirage/config.js

Now go back to the browser, and the page should work as usual again, with a console message informing you that the request was successful!



Although the console log tells us that our model is loaded now, there's no way for us to really see it on the page. Let's open our "home"-template and try to print out the username of the user we loaded:

app/home/template.hbs


Yay! It works!


Passing the data to our component

We now know that our data fetching works, but it's rendering on the wrong place, so let's remove the markup we just added to the template page. We want the data to be rendered inside of the profile-glance-component instead!


One thing we didn't mention in the last chapter is that you can actually pass data down to your components by adding attributes to it. Let's see how that works!

app/home/template.hbs
app/components/profile-glance/template.hbs


It works!


Adding the chirp-data

Alright, now that you have a rough understanding of how data is passed down, let's also replace our static list of chirps with mock data!


We want this to be populated with fixture data!


Let's structure the model. We'll say that a chirp contains: a text, a user (the author) and the date it was posted.

app/chirp/model.js

Here we stumble on a new kind of attribute: we've added a one-to-one relationship linking a chirp with its user (the author) through DS.belongsTo('user'). The only data we need then is the id of the user (which in this case is 1).


This is one of those awesome things with Ember Data; it not only stores your models but also remembers how they're linked together. We're going to take advantage of that in the upcoming chapters.


Let's not forget to add our mock data, so let's create a chirps.js-file in the fixtures-folder:

app/mirage/fixtures/chirps.js

Since we want to fetch all these chirps, we'll add the following line to the app/mirage/config.js-file (right after the one that fetches the user):

app/mirage/config.js

Now that we have the structure and data of our chirps ready, let's follow the same process as we did for the user data:

  1. Fetch the data via the `home`-route

  2. Pass the data down to the component that renders the list

  3. Render the data by doing some small edits to the Handlebars-template

If you feel adventurous, you could try passing down the data and render it on your own and then come back to this lesson to see how you did!


Alright, let's get to it. We'll start by fetching the data in the same route (home) as the one we used before:

app/home/route.js

Next, we pass the data down to our chirps-list-component under the key chirps.

app/home/template.hbs

Then, we loop over the data one record at a time using each as (if you are a Ruby developer you'll probably recognize this syntax). This allows us to get a separate chirp-variable for every record.


The data of each chirp-record is then passed down one step further to the chirps-list/chirp-message-component: that's where we'll actually render the data!

app/components/chirps-list/template.hbs

Notice that we have an else-statement in our each. This is a nifty Handlebars-feature: the else-part will only be rendered if the list we're looping over is empty!


Now there's only the easy part left: replace the static data in the chirps-list/chirp-message-component with the data we're passing down:

app/components/chirps-list/chirp-message/template.hbs

Notice how good it is that we defined that one-to-one relationship earlier?


Instead of passing down two separate data-sets of a chirp and a user, we only need to pass down the chirp object. We can still access the username-attribute from the user model by typing chirp.user.username. Super simple!


Go back to the browser. There's our data!


Great! There are just two noticeable things to fix:

  1. That raw timestamp is ugly, the time should be relative (e.g. "5 minutes ago"). Don't worry, this will be fixed in an upcoming chapter with an external library.

  2. The order is wrong! If we want to simulate the way Twitter works, it should show the latest chirp ("Hello again!") before the first one ("Hello world!"). Let's fix that now!

In order to fix the order, we're going to create a computed property called sortedChirps (right after the classNames) which will sort out the array in the chirps-list-component.

app/components/chirps-list/component.js

Now we can just edit our template so that it uses our new computed array instead of the old one:

app/components/chirps-list/template.hbs


Tada! We've got our data, and it's now sorted. Good job!


This is how you fetch and render your records with Ember Data. In the upcoming chapters we will also show you how to add, remove and edit records!


Our Home-page is starting to get a little lonely, so in the next chapter we'll put together everything we have learned so far by creating the profile page!

Comments

Kirk Israel

<p class="p1">I'm getting a "Mirage: The route handler for /api/users/1 is requesting data from the users collection, but that collection doesn't exist. To create it, create an empty fixture file or factory." despite having this in place:


cat app/mirage/fixtures/users.js </p> <p class="p1">// app/mirage/fixtures/users.js</p> <p class="p2">


</p> <p class="p1">export default [</p> <p class="p1"> {</p> <p class="p1"> id: 1, </p> <p class="p1"> username: 't4t5', // Put your username here</p> <p class="p1"> numberOfChirps: 2,</p> <p class="p1"> numberOfFollowing: 5,</p> <p class="p1"> numberOfFollowers: 5</p> <p class="p1"> }</p> <p class="p1">];</p>

profile/avatar/default
Włodek

Needs JSON API fixture: Error while processing route: home Assertion Failed: normalizeResponse must return a valid JSON API document:

Kirk Israel

Sorry, forgot to remove mirage/scenarios/ folder! Might want to emphasize PS how do I change my icon? it didn't grab my FB one...

profile/avatar/default
Michael Degli-Angeli

The data is being logged but I'm still getting an error...Assertion Failed: normalizeResponse must return a valid JSON API document:

Tristan Edwards

@michael: Hey there! Are you sure that you changed your adapter.js file so that it uses DS.RESTAdapter instead of the JSONAPIAdapter?

profile/avatar/default
Michael Degli-Angeli

I fixed this by following this guide: http://www.ember-cli-mirage.com/docs/v0.1.x/working-with-json-api/ and changing the mock data attributes to use dashes instead of camalcase, IE "number-of-chirps" instead of "numberOfChirps"

Christian Abdelmassih

Shouldn't it be mirage/config.js as it's not in app ?

Christian Abdelmassih

Yep it's mirage/config.js nowadays:

Kirk Israel

Can you explain the pipe syntax?

Andrés BG

If I use:


{{#each chirps as chirp}} it doesn't work...

Andrés BG

I have an extra chirp with all fields blank... :/

Andrés BG

You can teach how to create this model, like this: ember g model user username:string numberOfChirps:number numberOfFollowing:number numberOfFollowers:number

Micha? Czy?

+1

profile/avatar/default
Shannon Swanson

+1

profile/avatar/default
Andreas

Installing ember-cli-mirage@0.1.13 failed on my machine with SyntaxError: Invalid regular expression: /"predef": [ /: Unterminated character class

profile/avatar/default
Andreas

A fix can be found here: https://github.com/ember-cli/ember-cli/commit/b64ffaaf913bf25ffa7f56c4c14de24bb2081586

profile/avatar/default
Andreas

set up

profile/avatar/default
Michael Degli-Angeli

{{chirp.user.username}} is not working for me, all I have access to is: chirp.user which is "1"

Micha? Czy?

From what I noted on mirage page, they are recommending of using factories over fixtures - 0.2.x (yeah I know it still in beta)

Micha? Czy?

I've prepared factory ember g factory user and I also need scenario to preload app with some users data - why did you recommend to delete scenarios?

Micha? Czy?

Thanks for your reply.


I'm doing RoR development since 2007 - we have already been there, and factories in long run it didn't work for us. This might be the reason why I'm trying to avoid them (although front end apps have it's own rules, so not everything can be map one to one).

Tristan Edwards

@michaczy: Scenarios aren't needed if you use fixtures. Personally, I've found fixtures easier to deal with, but I would say that if you want to mock, say, 100s of data rows, you're better off using factories. It comes down to what the best choice for your particular app is.

Micha? Czy?

I's very helpful to generate it at once ember g model chirp text:string user:belongsTo createdAt:date

Daniel Jeffery

Good tip

profile/avatar/default
Louie Solomon

Can you point me to the documentation of creating an adapter this way?

Tristan Edwards

@louiesolomon: Check the Ember-CLI website: http://ember-cli.com/extending/#detailed-list-of-blueprints-and-their-use :)

Adam Marinelli

If you get the error Unknown blueprint: adaptor check your spelling of adapter. Uses 'e' instead or 'o.'

profile/avatar/default
Chris

I'm getting model data in the browser console but its not being displayed on the page. my syntax looks correct and I restarted the Ember server. Any ideas?

Tristan Edwards

@chrisr: Hm, not sure what the error could be tbh. If the problem persists and you can't find a solution, you could upload your code to GitHub and I'll try to find what the problem is.

profile/avatar/default
Chris

@tristan: I'm not sure what the problem was but I noticed my npms were out of date. I updated everything and started over and now its working.

Kirk Israel

Thanks for updating to this in this brave new post-fixture world

profile/avatar/default
donmeuse

The build failed after installing ember-cli-mirage because it could not find the config file in the mirage folder it expected. I think this has to do with version differences since this course was published. I finally had to delete any references to it. Is there any update to this command for current Ember versions?

Kirk Israel

Do you have to be careful of lazy-loading issues and accidentally downloading the universe?

Manvendra Singh

Same question..

Tristan Edwards

@kirkisrael: @manvendrasingh: Yes, if you have many relationships, you want to make sure you don't fire hundreds of unwanted AJAX requests. This is why there's the concept of sideloading in Ember data (which is mentioned in a later chapter).

Andy Pickler

I think this is actually a many-to-one relationship of "many chirps to 1 user".

Visva DevWorks

I understand your point of view based on the relational databases model, but in Ember theory this is indeed declared as a one-to-one relationship.


http://guides.emberjs.com/v2.0.0/models/defining-models/#toc_defining-relationships


There is also an example of one-to-many relationship, that it can be also seen as a many-to-one, if you look at it from the other end of the relationship. :-)

Daniel Jeffery

I would also say that it is a many-to-one relationship because a user can have many comments, but a comment will only belong to one user.


If a comment could belong to more than one user, I'd consider it a many-to-many.


In the documentation you linked to it says that one-to-one has two belongsTo relationships, whereas a one-to-many has a belongsTo and a hasMany.


If you look in the next chapter where the user model is updated there is this line


chirps: DS.hasMany('chirp', { async: true })


So that fits the criteria for a one-to-many


If I have overlooked something, would happy to hear other peoples thoughts!

Mike Sorum

I was looking at the Ember 2.0 guides and wondering why there is nothing about how to use fixtures this way, but it exists in the 1.10 guides under the models section.

Alex Molina

do you mean app/home/template.hbs ?

Tristan Edwards

@alexmolina: Woops, yes of course I do. Fixed it now, thanks!

Andrés BG

If this course is about ember 2.0 http://guides.emberjs.com/v2.0.0/models/

Tristan Edwards

@andrsbg: You're right, thanks! Updated the link.

J Stan Cox

Hi. Can you describe the significance of { async: true } and why it is necessary here? (when using the "belongsTo" relationship)

Tristan Edwards

@jstancox: Hi there! When using a REST backend, { async: true } will make a separate AJAX request in order to fetch the corresponding user, unless it has already been sideloaded. This will actually be the default behaviour in the stable Ember 2.0, so the whole async-thing can soon be removed.

Lynn Alexander

ditto here

Lynn Alexander

get unexpected token with 'chirps: this.store.findAll('chirp')

Lynn Alexander

thks will try that

Michael Bernal

why are we using ".findAll" vs ".findRecord"? I'm confused. :(

Lynn Alexander

tired other addon, still get error: yntaxError: chirper/home/route.js: Unexpected token (10:6) 8 | // user: this.store.findRecord('user', 1) 9 | user: this.store.find('user', 1) > 10 | chirps: this.store.findAll('chirp') | ^ 11 | }); 12 | } 13 | });

Tristan Edwards

@lynnalexander: Oh, a syntax error? Sounds like there's a problem in your code. Could you email me at tristan@ludu.co?

Tristan Edwards

@lynnalexander: Hmm. It could be that the Ember team has removed the FixtureAdapter from the preinstalled adapters. Try installing this addon: https://github.com/emberjs/ember-data-fixture-adapter I'll update the lesson soon to see what has changed!

Tristan Edwards

@michaelbernal: Ah sorry, this is explained in more detail in the chapter "Consuming the REST API". "FindRecord" will fetch a single chirp (from an id), while "findAll" will fetch ALL the chirps that exist. In this case we want to display multiple chirps and not just one, so we use "findAll".

Bill Pullen

file name is wrong here should be profile-glance

Tristan Edwards

@billpullen: Thanks! Fixed.

Lynn Alexander

not working for me v0.2.7??

Lynn Alexander

switched to latest version 1.13.8 from 0.2.5 and now working

Bill Pullen

shgshould this be store.find()

Lynn Alexander

had to use store.filter() in mine v0.2.7

Tristan Edwards

@billpullen: store.find is getting phased out in favor of findRecord + other methods it seems: http://emberjs.com/blog/2015/06/18/ember-data-1-13-released.html#toc_new-adapter-hooks-for-better-caching

Dea Brunner

Unnecessary semicolon at line 9