Rails API has been merged into Rails master branch.

There is a how-to on building a Rails API app and integrating it with a simple Ember app. There also is a redux from one year before the merge, which explains how an Ember and Rails API app could be built before the Rails API merge.

In this post, we are going to build a simple Rails API app and an Ember app from scratch and give the Ember app access to the Rails API. This consolidates the approaches described in the two posts linked above.

Build the Backend

For a more in-depth explanation of what is happening here, consult this post.

Generate Rails API only application

Rails 5 at the time of writing requires Ruby 2.2.2 or newer. For instance, in case you are using rvm, do rvm install 2.2.3. To use this version as your system default, rvm use 2.2.3 --default.

Since Rails 5 is not yet released, we have to clone the Github repository and generate our application from the source code.

git clone git://github.com/rails/rails.git
cd rails
bundle
bundle exec railties/exe/rails new ../my_api_app --api --edge

Scaffold Users resource

We create a simple scaffold using rails generate scaffold and perform the migration:

cd ../my_api_app
bin/rails g scaffold user name
bin/rake db:migrate

Adjust JSON serialization format

Ember’s REST adapter requires that the root object’s key is present in the JSON payload. This is achieved by setting the json adapter instead of Active Model Serializer’s default flatten_json adapter.

Create a new initializer file config/initializers/ams_json_adapter.rb including the following line:

1
ActiveModel::Serializer.config.adapter = :json

Test Rails API

To see if the API works as expected, start the server with bin/rails s. Let us try to create the first User using curl:

curl -H "Content-Type:application/json; charset=utf-8" \
-d '{"user": {"name":"Hans"}}' http://localhost:3000/users

Looking at localhost:3000/users, we now expect to see the newly created user shown in the format we specified above.

Set up CORS

Whenever the Rails app and the Ember app have different origins, we need to configure Cross Origin Resource Sharing (CORS).

We will test the backend in localhost:3000 and the frontend in localhost:4200.

In brief, we have to uncomment rack-cors in the Gemfile, run bundle, and put the following code in config/initializers/cors.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Avoid CORS issues when API is called from the frontend app
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests

# Read more: https://github.com/cyu/rack-cors

 Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
   allow do
     origins 'localhost:4200'

     resource '*',
       headers: :any,
       methods: [:get, :post, :put, :patch, :delete, :options, :head]
   end
 end

Build the frontend

This instruction will enable the Ember app to use a simple Users resource it receives from the Rails API. It will present the API data as a Users index page. For additional detail (e.g. show User pages), we can refer to the post this is based on.

Install Ember CLI and create frontend

For installing the Ember CLI, we use --no-optional, since it is less error-prone. Using nvm, we do not need to run npm as root, thus avoiding possible permission problems:

npm install -g ember-cli --no-optional

Create the Ember.js app:

cd ..
ember new my_ember_frontend
cd my_ember_frontend

At the time of writing, this yields a reminder even with the current stable Node.js: Future versions of Ember CLI will not support v4.1.0. Please update to Node 0.12 or io.js. Do not worry: the fix for this misleading message has been announced and is likely to find its way into future versions of Ember CLI.

Generate application adapter

To enable the Ember app to communicate with our Rails app, we generate an adapter:

ember generate adapter application

Now we adjust app/adapters/application.js:

1
2
3
4
5
import DS from 'ember-data';

export default DS.ActiveModelAdapter.extend({
  host: 'http://localhost:3000'
});

Add Users resource to router.js

Edit app/router.js:

1
2
3
4
5
6
7
8
9
10
11
12
import Ember from 'ember';
import config from './config/environment';

var Router = Ember.Router.extend({
  location: config.locationType
});

Router.map(function() {
  this.resource('users');
});

export default Router;

Generate User model

Firstly,

ember generate model user

Then edit app/models/user.js:

1
2
3
4
5
import DS from 'ember-data';

export default DS.Model.extend({
    name: DS.attr('string')
});

Generate Users/index route

Now,

ember generate route users/index

Edit app/routes/users/index.js:

1
2
3
4
5
6
7
import Ember from 'ember';

export default Ember.Route.extend({
    model: function() {
        return this.store.findAll('user');
    }
});

Write Users/index template

Edit app/templates/users/index.hbs:

<ul>

    {{#each model as |user|}}

    <li> {{user.name}} </li>

    {{/each}}

</ul>
{{outlet}}

Test the application

To test the whole application, run from the Rails app’s directory

bin/rails s

and from the Ember app’s directory

ember serve

Browse to localhost:4200/users to try it out!

Many thanks go out to Moritz for proofreading this post!