I'm all about Ember.js recently

Two-way Symmetric Relationships in Ember With JSON API - Part 2

In Part 1 of this series, we saw what symmetric relationships are, how to model them on the back-end (using Rails) and how to implement a simple app that displays such relationships in Ember.js, adhering to the JSON API specification for serializing data.

The solution we came up with worked but was a very naive one. For N relationships a resource had, it made 2N+1 queries to the back-end to display them. We should do better and in this post we’ll see how.

(If you haven’t read the previous post, this one will not make much sense, so I recommend to at least skim it before you delve into this one.)

One request to rule them all

We’d observed that for each person whose friendships we wanted to observe, our application made 2N+1 requests. One to grab the friendships themselves, and then two requests for each friendship in that collection, one to fetch the friender, one to fetch the friended person.

Too many XHRs

We also noticed that those people (instances of the Person model) had already been fetched at that point so the extra requests were for nothing. Our mission is thus to reduce the 2N+1 requests to a single one, the one that fetches the friendships.

How would Ember (Data) know?

If we take a look at the data returned by the request for friendships, you can see that both the friended and friender end of each are just links with no type or identity information (like /friendships/1/friended). This is all that’s needed for an asynchronous relationship, since the client (Ember Data, in this case) can just fetch the relationship data when/if it needs it.

No linkage data

The solution, then, might be to include some data about the resource that the relationship refers to. In the JSON API vocabulary this is called resource linkage:

Resource linkage in a compound document allows a client to link together all of the included resource objects without having to GET any URLs via links.

Digging around in the jsonapi-resources source, we find a relationship option called always_include_linkage_data that seems to do what we need. Let’s add that to the relationships of the friendship resource and see:

1
2
3
4
5
6
# app/resources/friendship_resource.rb
class FriendshipResource < JSONAPI::Resource
  has_one :friender, always_include_linkage_data: true
  has_one :friended, always_include_linkage_data: true
  attributes :strength
end

If we now reload our Ember app, we see how a data key was added to each relationship in the response, uniquely identifying the person resource that is the friender (or friended) in the friendship relationship:

Relationship with linkage data

Furthermore, the extra XHRs we wanted to eliminate are now indeed gone as Ember Data is smart enough to just use the referred resources that are already in the store:

Just the XHRs we need

Let’s just be friends (not friendeds or frienders)

We have now achieved what we’d wanted and only have to make one request per person to fetch and display their friendships.

It looks a bit weird, though, that when a person’s friendships are displayed, we also display the person’s name, too:

Mike McCready's friendships - Part 1

Let’s fix that by transforming the friendships of the person to an array where each item only contains the friend’s name (and the strength of the friendship):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/controllers/people/show.js
import Ember from 'ember';

const { Controller, computed } = Ember;

export default Controller.extend({
  friendships: computed('model.friendships.[]', function() {
    let person = this.get('model');
    let friendships = this.get('model.friendships');
    return friendships.map((friendship) => {
      let friend;
      if (friendship.get('friended.id') === person.get('id')) {
        friend = friendship.get('friender');
      } else {
        friend = friendship.get('friended');
      }
      return {
        friend,
        strength: friendship.get('strength')
      };
    });
  })
});

Nothing fancy going on, we check which end of the relationship the person in question (the model) is, and then only return the other end.

We should now use friendships in the template instead of model.friendships:

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Friends of {{model.name}}</h3>
  </div>
  <div class="panel-body">
    {{#if friendships.length}}
      <ul class="friend-list">
        {{#each friendships as |friendship|}}
          <li class="friend-list-item">
            <span class="name">{{friendship.friend.name}}</span>
            <span class="badge">{{friendship.strength}}</span>
          </li>
        {{/each}}
      </ul>
    {{else}}
      <div class="empty-list">
        <p class="empty-message">{{model.name}} has no friends :(</p>
      </div>
    {{/if}}
  </div>
</div>

It works, we indeed only see the friend’s name, not the person’s:

Relationship with linkage data

Resources

Hopefully you can now implement a symmetric relationship with relative ease, the next time you encounter it.

I made the source code of both the Ember app and the Rails API available on Github. If you want to learn more about the jsonapi-resources gem, I suggest you visit the documentation site.

Finally, if you’d like to receive the series as a pdf, fill out the form below and I’ll send it to you right away!

Two-way Symmetric Relationships in Ember With JSON API - Part 1

Definition

In data modelling, a symmetric relationship is a special kind of relationship where the description of the relationship from the perspective of one end of the relationship is identical to looking at it from the perspective of the other end.

Friendship between people is a good example. If Megan is Selma’s friend, it follows that Selma is Megan’s friend, too. On the other hand, the “knows” relationship between two people is not a symmetric one. I might know Danny Carey (the drummer of Tool), but that does not imply he knows me.

Historical background

My research into how to model and implement such a relationship in an Ember application was sparked by this Stack Overflow question that was posed by a reader of my book. It was more difficult than I thought it would be so I was intrigued to find the (an) answer.

My solution turned out to have a fairly large API component, too, so the following post will show both the server-side implementation (in Rails) and the client-side one (in Ember).

If you don’t speak Rails, fear not. The code is straightforward and easy to understand without any substantial Rails knowledge, thanks in most part to the gem that makes it extremely easy to serialize data models and relationships to json:api format, jsonapi-resources.

Data modelling

We’ll start with the data modelling part, which is the Rails side.

To be able to model our problem in the data layer, let’s say that Friendships have a friender and a friended end of the relationship and a strength attribute that measures how strong their friendship is.

We should create a (data) migration that will create a database table when run:

    $ rails g migration create_friendships

Let’s fill in the generated migration with the above attributes:

1
2
3
4
5
6
7
8
9
10
class CreateFriendships < ActiveRecord::Migration
  def change
    create_table :friendships do |t|
      t.integer :friender_id
      t.integer :friended_id
      t.integer :strength
      t.timestamps null: false
    end
  end
end

A Friendship, then, is between two people (Persons), so let’s define that in the corresponding model file:

1
2
3
4
5
# app/models/friendship.rb
class Friendship < ActiveRecord::Base
  belongs_to :friender, class_name: Person
  belongs_to :friended, class_name: Person
end

We’ll want to list all the friendships a person has so a friendships method needs to be added to the Person class:

1
2
3
4
5
6
# app/models/person.rb
class Person < ActiveRecord::Base
  def friendships
    Friendship.where("friender_id = ? OR friended_id = ?", id, id);
  end
end

We select the friendships where either the friender or the friended is the person we query it for. This is where the symmetric aspect of the relationship is implemented. We don’t care if the person friended somebody or if that somebody friended him, they are friends.

Note that modelling it this way, we could split up the symmetric relationship into the two constituent parts. We could return only the friendships where the person in question “initiated” it (was the friender), or “let himself be friended” (was the friender).

Server endpoints, resources, serializing relationships

We could now turn our attention to setting up the endpoints and serializing the model, and relationship data for the client application to consume. First, let’s install the jsonapi-resources gem:

    $ gem install jsonapi-resources

This gives us a jsonapi:resource generator that we can use to create both the endpoints and the serializer for our resources.

    $ rails generate jsonapi:resource person
    $ rails generate jsonapi:resource friendship

The created resources are placed in the app/resources folder. Let’s add the attributes we want to serialize for each one:

1
2
3
4
5
# app/resources/person_resource.rb
class PersonResource < JSONAPI::Resource
  attributes :name
  has_many :friendships, class_name: "Friendship"
end
1
2
3
4
5
6
# app/resources/friendship_resource.rb
class FriendshipResource < JSONAPI::Resource
  has_one :friender
  has_one :friended
  attributes :strength
end

Creating the endpoints is no more work than adding a jsonapi_resources call for each resource in the router configuration:

1
2
3
4
5
# config/routes.rb
Rails.application.routes.draw do
  jsonapi_resources :people
  jsonapi_resources :friendships
end

The gem also provides a controller generator so let’s use it to create controllers for our resources:

    $ rails generate jsonapi:controller person
    $ rails generate jsonapi:controller friendship

They can be left empty but they need to be created in a way that they are descendants of JSONAPI::ResourceController (the generator takes care of that):

1
2
3
# app/controllers/people_controller.rb
class PeopleController < JSONAPI::ResourceController
end
1
2
3
# app/controllers/friendships_controller.rb
class FriendshipsController < JSONAPI::ResourceController
end

The back-end is now done, we can switch our focus to the Ember app.

The front-end

We want a list of people (rock stars, of course) and then have a list of their friendships on the person details page.

Mike McCready's frienships - Part 1

The first step is to set up the routes:

1
2
3
4
5
6
7
8
(...)
Router.map(function() {
  this.route('people', { path: '/' }, function() {
    this.route('show', { path: '/people/:person_id' });
  });
});

export default Router;

The model hooks for these routes are the classic, “fetch’em all” and “fetch the one that matches the id” methods of Ember Data’s store:

1
2
3
4
5
6
7
8
// app/routes/people.js
import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.findAll('person');
  }
});
1
2
3
4
5
6
7
8
// app/routes/people/show.js
import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.store.findRecord('person', params.person_id);
  }
});

Before we move on to writing the templates, let’s define the models:

1
2
3
4
5
6
7
8
9
10
11
// app/models/person.js
import DS from 'ember-data';

const { Model, attr, hasMany } = DS;

export default Model.extend({
  name: attr(),
  friendships: hasMany(),
  frienderFriendships: hasMany('friendship', { inverse: 'friender' }),
  friendedFriendships: hasMany('friendship', { inverse: 'friended' }),
});
1
2
3
4
5
6
7
8
9
10
// app/models/friendship.js
import DS from 'ember-data';

const { Model, attr, belongsTo } = DS;

export default Model.extend({
  strength: attr('number'),
  friender: belongsTo('person', { inverse: 'frienderFriendships' }),
  friended: belongsTo('person', { inverse: 'friendedFriendships' }),
});

This is rather standard Ember Data stuff, possibly with the exception of the inverse definitions. Since we have two relationships between Person and Friendship we need to specify the other end of each relationship and that’s what we do with the inverse option.

With the models and routes in place, we can now see what the templates should look like.

The top-level people route is again fairly straightforward:

// app/templates/people.hbs
<div class="col-md-4">
  <div class="list-group">
    {{#each model as |person|}}
      {{link-to person.name 'people.show' person.id class="list-group-item"}}
    {{/each}}
  </div>
</div>
<div class="col-md-8">
  {{outlet}}
</div>

The each loop iterates through each person and renders a link for each of those that will take us to the person details page, which will display the person’s friendships.

List of people

Listing a person’s friendships

// app/templates/people/show.hbs
<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Friends of {{model.name}}</h3>
  </div>
  <div class="panel-body">
    <ul class="friend-list">
      {{#each model.friendships as |friendship|}}
        <li class="friend-list-item">
          <span class="name">{{friendship.friender.name}}</span>
          <span class="name">{{friendship.friended.name}}</span>
          <span class="badge">{{friendship.strength}}</span>
        </li>
      {{/each}}
    </ul>
  </div>
</div>

There is nothing fancy going on here, either. The model is the person retrieved in the route. For each friendship that he has, the friender’s and the friended’s name are rendered along with the strength of the relationship. (Either friender or friended will be the person itself, but we can ignore that in the first version.)

This naive approach works, the friendships for the selected person are listed correctly:

Mike McCready's friendships - Part 1

A 2N+1 problem

However, looking at the requests to the backend for just one page, one gets the impression that we’re not done yet:

Too many XHRs

For each friendship the person has, two requests are sent to the backend. One to fetch the friender and another one to fetch the friended person. This is not an N+1 query problem, this is worse, a 2N+1 query problem!

On top of that, those requests are sent for no good reason as we’d previously loaded the people referred by those friended and friended relationships.

In the next part, we’ll see how these wasteful requests can be eliminated and we’ll also make the person details page less perplexing by not displaying the person whose page we’re looking at in the relationships. Stay tuned!

UPDATE: Part 2 is now available!

Enable FastBoot in Your Ember App

What is FastBoot and why should you use it?

FastBoot is the awesome add-on that adds server-side rendering to your Ember app. This will make users of your application see the content of your page before any of the javascript is downloaded. In other words, the “time to first tweet” of your app is greatly reduced, which is a big win where and when people have slow or unstable network connections.

Another advantage is that search engine crawlers will have an easier job indexing your site, which brings SEO benefits.

Furthermore, your site will be readable with Javascript disabled which is convenient for screen readers.

I recently went through the process of enabling the demo version of the Rock and Roll application to run in Fastboot. Below, I’m going to tell you about the challenges I encountered and how I overcame them in the hope that my journey will prove valuable when you do the same for your app.

Installing the add-on

FastBoot is a regular Ember add-on, so installing it is piece of cake:

$ ember install ember-cli-fastboot

I could then run

$ ember fastboot

from the project’s directory and had the node server serving my application at port 3000. It’s important to note that you should refresh your browser tab each time you make changes to your code as FastBoot doesn’t (yet) auto-refresh the way ember server does.

I then disabled JavaScript in my browser and then directed my browser to http://localhost:3000.

Disabling JavaScript in Chrome is most easily done by expanding the context menu of Developer Tools and then clicking on Settings:

Disable JavaScript

Mirage is disabled in FastBoot mode

My first obstacle turned out to be Mirage.

Mirage is a great tool for mocking server responses and even prototyping your Ember app. I used it in development, too, and it turned out that since it turns itself off when your app is running in FastBoot mode, the requests hitherto handled by Mirage now went out and were thus unhandled.

The fix here was to disable Mirage in development (and, in my case, production, too) and to make the requests against an actual API.

You also have to add the hosts that will serve your Ember app in FastBoot mode to a list called hostWhitelist in your app’s configuration.

In my case, it contains the host I wanted to deploy it to and any localhost port:

1
2
3
4
5
6
7
8
// config/environment.js
module.exports = function(environment) {
  var ENV = {
    (...)
    fastboot: {
      hostWhitelist: ['demo.rockandrollwithemberjs.com', /^localhost:\d+$/]
    }
  };

Serving assets

When I restarted the ember fastboot and looked at the server-rendered version of my app, I saw that the dynamic data was now correctly rendered on the page. However, it did not have any styling.

A quick glance at the documentation made me realize I needed to pass the serve-assets option to the command so that it serves the css (and other asset) files:

$ ember fastboot --serve-assets

document is not defined

So now the main page, with the list of bands rendered fine but when I selected one of the bands to have their songs displayed, I got the following error:

Error while processing route: bands.band.songs document is not defined

Since Fastboot runs your Ember app in a node environment, not in the browser, document is not present. In my case, I accessed document (through jQuery) to set the document title, which does not work in FastBoot mode.

The user guide suggested to use ember-cli-document-title, a FastBoot compatible way to set document titles. So my next step was to install that add-on:

$ ember install ember-cli-document-title

Armed with this great add-on, I only had to define a title (method) in the corresponding route:

1
2
3
4
5
6
7
8
9
10
// app/routes/bands/band/songs.js
import Ember from 'ember';

export default Ember.Route.extend({
  (...)
  title() {
    let bandName = this.modelFor('bands.band').get('name');
    return `${bandName} songs - Rock and Roll`;
  },
});

Missing dynamic content

The next thing that did not work was that the songs for a specific band did not load in FastBoot mode, the list of songs was empty each time.

Adolfo Builes and Jonathan Jackson helped me out by pointing out that songs are loaded asynchronously. The request to fetch the songs was only fired when the template rendered each song belonging to the band. FastBoot does not know when the page is fully rendered and thus relies on the beforeModel, model and afterModel route hooks having finished their work. When that happened, the songs were not yet fetched and rendered on the screen yet:

Songs missing

The way to fix this was to block rendering in the afterModel hook, by returning a promise that fetched the songs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// app/routes/bands/band/songs.js
import Ember from 'ember';

export default Ember.Route.extend({
  fastboot: Ember.inject.service(),

  afterModel() {
    if (this.get('fastboot.isFastBoot')) {
      let band = this.modelFor('bands.band');
      return band.get('songs');
    }
  },
  (...)
});

As you can see, I only pre-fetch the songs in FastBoot mode. In the browser, I let rendering start earlier, with a “pop-in” effect (which can be remedied in several ways in the browser, too).

The songs now appeared in the FastBoot “view” of the app, too:

Songs present

You can read more about this in the “Use Model Hooks to Defer Rendering” section of the guide.

Fastboot-enabled hosting

It’s fine to have FastBoot working in development but nobody actually needs it to work in that environment. It has to work when deployed to a server.

The guide has a whole page on deployment, listing several deployment options, from which I chose Heroku as it seemed the easiest option. And it really was.

All I had to do was to set the buildpack URL from my project:

$ heroku buildpacks:set https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/emberjs.tgz -a rarwe-demo

I then added a static.json file to the root of my project, to disable forcing https requests, as the domain is not (yet) SSL-supported:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// static.json
{
  "root": "dist/",
  "https_only": false,
  "clean_urls": true,
  "routes": {
    "/**": "index.html"
  },
  "headers": {
    "/**": {
      "Cache-Control": "private, no-store, no-cache, must-revalidate, proxy-revalidate",
      "Pragma": "no-cache",
      "Expires": "Sat, 05 Nov 1955 00:00:00 PST",
      "Strict-Transport-Security": "max-age=31536000; includeSubDomains;",
      "X-Download-Options": "noopen",
      "X-Content-Type-Options": "nosniff",
      "X-Frame-Options": "SAMEORIGIN",
      "X-XSS-Protection": "1; mode=block"
    },
    "/assets/**": { "Cache-Control": "public, max-age=512000" },
    "/robots.txt": { "Cache-Control": "public, max-age=512000" },
    "/crossdomain.xml": { "Cache-Control": "public, max-age=512000" }
  }
}

This step is really only needed to change the default https_only setting. If you have SSL set up for your domain, you don’t need the static.json file.

The next time I pushed to the remote set up by Heroku, it just worked, and my app was now FastBoot enabled. Hooray!

Acknowledgements and further resources

I would like to thank Adolfo and Jonathan for their help in pointing me at Ember Weekend, an Ember app that runs in FastBoot and whose source code is publicly available, and also for overcoming the above mentioned “missing dynamic content” problem.

My app does not use many of Fastboot’s features. If you’re looking to see a more complex use case, check out the Ember Weekend source code.

If you want to learn more about FastBoot’s architecture and rationale, I recommend checking out Tom Dale’s great presentation he gave at Global Ember Meetup.

Finally, the Rock and Roll demo app that this post describes is available on Github at balinterdi/rarwe-demo and deployed to http://demo.rockandrollwithemberjs.com.

Continuous Visual Integration for Ember Apps

I recently watched Mike Fotinakis’s presentation at the San Francisco Ember meetup, Continuous Visual Integration with Ember, and I’ve become really enthusiastic about visual diffs, and Percy.

It’s really worth the time and I highly recommend to watch it. I could say “watch it now and then come back” but it’s a 50-minute long video while this post takes 5 minutes to read, so I recommend to do it the other way around: read this post first and then go and watch the presentation where he goes into more details on what visual diffs and CI are, how Percy works and answers to questions from the audience.

Ready? Cool. Start the timer and let’s go.

So what is continuous visual integration?

I will give you the high-level definition and rationale of visual diffs and continuous visual integration as the post otherwise wouldn’t make sense.

When we change code, the intangible stuff apps are made of, we risk breaking it. To prevent this, we write tests that guard against regression (a nice way of saying breaking stuff) and do code reviews where someone goes over the code diffs, and potentially after a series of edits, confirms it.

However, that’s just the code part. There are other ways to break a web application, a major one among them is for the app to look differently than before, in an unintended way. The background color of a button might change, a link could have become hidden, or a css file could no longer be downloaded which all lead to a degraded visual experience for visitors of the app.

Our (textual) tests could pass with flying colors and yet the users of the app could be running away screaming from our site. Visual diffs guard against that scenario by showing a visual diff, what certain scenarios looked before and after the code changes under test.

Visual diffs as part of the review process

The idea is to have screenshots made at certain points in our user flows and have a review process that approves the changes, just as we do for code.

To automate the process, we integrate the generation of visual diffing in the automated testing process. This will produce the “before” and “after” screenshots that will be used to generate the diffs. Let’s see how this is done in practice.

Making all this a breeze with Percy

Percy is a visual diffing tool that automates all of this and integrates smoothly into the Github pull request process. To get started with Percy, install the Ember addon:

$ ember install ember-percy

If you use Mirage for your tests, like I do, make sure to define a passthrough rule for Percy on top of your Mirage config, before any other rules (see the docs):

1
2
3
4
5
// mirage/config.js
export default function() {
  this.passthrough('/_percy/**');
  (...)
})

NOTE Starting from ember-percy version 1.2.4, you no longer need to do the above as ember-percy doesn’t use Mirage anymore.

To demo how the development flow changes to accommodate visual diffing, I prepared a PR to make a dropdown in the Rock and Roll with Ember demo nicer.

Before implementing the change, I wrote a test that verifies if the songs for a particular band are listed correctly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// tests/acceptance/songs-test.js
import Ember from 'ember';
import { test } from 'qunit';

(...)

test('List songs for a band', function(assert) {
  visit('/bands');
  click('a:contains("Tool")');
  percySnapshot('list-songs-for-band');

  andThen(function() {
    assert.equal(Ember.$('.song').length, 3, "All songs for the selected band are displayed");
    assert.equal(Ember.$('.song:contains("46 & 2")').length, 1, "The first song is displayed");
  });
});

The thing to note here is percySnapshot. If Percy is set up in the environment where the test runs, it generates a screenshot identified by the name we pass it (or auto-generates one). To set up Percy, the PERCY_TOKEN and PERCY_PROJECT env vars need to be set in the CI tool of your choice. The Percy docs explain how.

Once we have a percySnapshot, the next time we push and our CI builds the project, Percy will have a new snapshot for us:

First screenshot - No diff yet

We can now implement our feature on a branch and create a pull request for it on Github. As you can see on the following screenshot, approving the visual diffs is now part of the flow:

Github before Percy approval

Clicking “Details” takes us to the visual diffs where we must explicilty approve the changes:

Percy visual diffs

Once the visual diffs are approved, the PR is all green and we can lean back in our chairs and merge it, safe in the knowledge we did not visually break the app.

Github after Percy approval

Just as with “normal” tests, the more snapshots we have, the higher the chance we did not introduce any regressions with our latest changes. Mike recommends a “breadth first” approach, making screenshots from tests that verify different scenarios instead of a “depth first” approach, creating several screenshots for the same scenario.

Closing words

I’m all sold on visual diffing and I think there is tremendous value in securing our apps this way.

I encourage you to watch Mike’s presentation and sign up for Percy to become a better steward of your applications.

Refactoring Promise Patterns

I’m sure you have written a lot of code that looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/controllers/band.js
actions: {
  save() {
    let band = this.get('model');
    this.set('isLoading', true);
    return band.save()
      .then((result) => {
        this.set('successMessage', 'Band has been saved.');
      })
      .catch(() => {
        this.set('error', 'Band is too unruly to be saved.');
      })
      .finally(() => {
        this.set('isLoading', false);
      });
  }
}

Setting back isLoading to false is conveniently placed in a finally handler so that it happens both if the promise resolves successfully (the band is saved) and if it is rejected (there is an error during the save).

The isLoading is then used in the corresponding template to show a spinner and/or disable the save button while the save is in flight:

// app/templates/band.hbs
<button type="button" onclick=(action "save") disabled={{isLoading}}>Save band</button>

I know I have written this hundreds of times by now and it has increasingly disturbed me that setting and unsetting the loading flag is boilerplate code, something that could be refactored.

One nice thing about promises is that they are easy to compose. Here is how we could define a function that adds the boilerplatey parts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/controllers/band.js

function saveWithLoadingFlag(operation, loadingProperty) {
  this.set(loadingProperty, true);
  return operation.finally(() => {
    this.set(loadingProperty, false);
  });
}

actions: {
  save() {
    let band = this.get('model');
    return saveWithLoadingFlag.call(this, band.save(), 'isLoading')
      .then((result) => {
        this.set('successMessage', 'Band has been saved.');
      })
      .catch(() => {
        this.set('error', 'Band is too unruly to be saved.');
      });
  }
}

You might go a tiny step further and use a default value for the loading flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/controllers/band.js

function saveWithLoadingFlag(operation, loadingProperty='isLoading') {
  this.set(loadingProperty, true);
  return operation.finally(() => {
    this.set(loadingProperty, false);
  });
}

actions: {
  save() {
    let band = this.get('model');
    return saveWithLoadingFlag.call(this, band.save())
      .then((result) => {
        this.set('successMessage', 'Band has been saved.');
      })
      .catch(() => {
        this.set('error', 'Band is too unruly to be saved.');
      });
  }
}

Since saveWithLoadingFlag returns a promise, we can add our own handlers to it, including other finally handlers.

You can find a working example here, where you can play around with it.

Rock and Roll With Ember.js 2.8 Is Released

Yesterday I published an updated version of the Rock and Roll with Ember.js book. The app now runs on 2.8 (Ember CLI, Ember and Ember Data) and, as usual, there are a few other changes.

The biggest of these was updating the Deployment chapter. PageFront seems defunct so I replaced it with Surge. Above that I also updated the section on deploying (to S3) with ember-cli-deploy as things have changed quite a bit.

You can check out the full list of changes here or the book itself here.


PSA: Ember 2.9 will bring the long-awaited new rendering engine, Glimmer 2!

I already went ahead and updated the app to 2.9.beta to give it a whirl.

The results were pretty jaw-dropping:

I encourage you to try switching your app to Glimmer 2 and see if you encounter any problems.

Using Dependency Injection to Write Better Tests

Testing is given much emphasis in the Ember.js community, and testing tools have showed steady progress to reduce the cost of writing tests of all types.

Lauren Tan wrote a great post about how Dependency Injection (DI) can be used to decouple a parent component from the internals of its child components. One of the gains of doing so is that the parent component becomes more focused and thus easier to test.

In this post, I’m doing something similar, although much simpler. I want to show you how to use DI in a simple helper function to make it easier to test.

Just your ordinary, run-of-the-mill function

Although the helper is an Ember (template) helper, the concepts could be very easily transferred to other frameworks, libraries and even languages.

I recently had to modify a normalizeText helper function that looked like this:

1
2
3
4
5
6
7
8
// tests/unit/helpers/normalize-text-test.js
import Ember from 'ember';

export function normalizeText([text]) {
  let normalizedEOLs = text.trim().replace(/(?:\r\n|\r|\n)/g, '</p><p>');
  let noEmptyParagraphs = normalizedEOLs.replace(/(<p><\/p>)/g, '');
  return Ember.String.htmlSafe("<p>" + noEmptyParagraphs + "</p>");
}

(I realize the above code does not handle a text value of undefined or null. The real code does but I want to keep the code examples to the minimum necessary to get my point across.)

Comparing objects to objects

Its test was quite simple and straightforward:

1
2
3
4
5
6
7
8
9
10
// tests/unit/helpers/normalize-text-test.js
import { normalizeText } from '../../../helpers/normalize-text';
import { module, test } from 'qunit';

module('Unit | Helper | normalize-text');

test('it works', function(assert) {
  let normalizedText = normalizeText(["The brown fox\r\njumped over the quick rabbit.\n"]);
  assert.equal(normalizedText, "<p>The brown fox</p><p>jumped over the quick rabbit.</p>");
});

The problem with that test is that we compare two Handlebars.SafeString instances (returned by Ember.String.htmlSafe) which are different even if the strings they wrap, their value, is the same:

1
2
3
let s1 = Ember.String.htmlSafe("sid transit gloria mundi");
let s2 = Ember.String.htmlSafe("sid transit gloria mundi");
s1 === s2 // => false

We’re, however, interested in the equality of the strings. If only there was a way to replace that pesky Ember.String.htmlSafe call from the call site…

DI to the rescue

This is exactly what Dependency Injection can help us do. Instead of hard-coding that “sanitizer” function dependency, the function could take it as a parameter so that callers could inject it. Usually DI examples use (and thus inject) class names or object instances but it is important to realize that the injected param could be very “primitive”, like a simple function.

So here is how I rewrote the function:

1
2
3
4
5
6
7
8
9
10
11
// app/helpers/normalize-text.js
import Ember from 'ember';

export function normalizeText([text], params={}) {
  let { sanitizer=Ember.String.htmlSafe } = params;
  let normalizedEOLs = text.trim().replace(/(?:\r\n|\r|\n)/g, '</p><p>');
  let noEmptyParagraphs = normalizedEOLs.replace(/(<p><\/p>)/g, '');
  return sanitizer("<p>" + noEmptyParagraphs + "</p>");
}

export default Ember.Helper.helper(normalizeText);

Notice how easy ES2015 destructuring makes the assignment of the sanitizer function:

1
let { sanitizer=Ember.String.htmlSafe } = params;

If no sanitizer key was present in params, then it will have a value of Ember.String.htmlSafe, the default behavior.

The call from the test can now override the default behavior of sending the normalized text through Ember.String.htmlSafe by passing in a “no-op” sanitizer function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tests/unit/helpers/normalize-text-test.js
import { normalizeText } from '../../../helpers/normalize-text';
import { module, test } from 'qunit';

function leaveAsIs(text) {
  return text;
}

module('Unit | Helper | normalize-text');

test('it works', function(assert) {
  let normalizedText = normalizeText(["The brown fox\r\njumped over the quick rabbit.\n"], { sanitizer: leaveAsIs });
  assert.equal(normalizedText, "<p>The brown fox</p><p>jumped over the quick rabbit.</p>");
});

We’re now comparing simple strings which place nicely with assert.equal (with ===), and our test now passes.

Non-testing benefits

Code modifications introduced for the sake of testing usually also improve the non-testing aspect. Here, we made it possible to pass any function before we return the normalized text. We could, for example, use this to replace the <p> tags with <span>s, if we so wish.

Rock and Roll With Ember.js 2.6 Is Released

With some delay, but I just sent an update to all of the Rock and Roll with Ember.js customers. The book brings the app in sync with Ember, Ember Data and Ember CLI 2.6. Among a few other improvments it:

  • Makes sure the new Ember welcome page is removed before generating the application template: #196
  • Adds a version string to the book’s title and also in the Preface so that the reader knows whether they are reading the latest version: #174

The list of all accomplished things can be seen here