Here is how to get started with adding ember-cli-rails to your rails app.
We are going to assuming you have a rails app like this one. It’s just a base rails app with one model, Post, that has title and body fields.
Install ember-cli-rails and ams
Add these lines to your Gemfile and run bundle install.
gem 'ember-cli-rails'
gem 'active_model_serializers'
First generate new ember app inside of the directory your rails application lives in. We’re going to call ours “frontend”
$ ember new frontend --skip-git
Next we need to run this command:
$ rails generate ember:init
This will create the initializer file for ember-cli-rails
# config/initializers/ember.rb
EmberCli.configure do |c|
c.app :frontend
end
Add the line to your rails routes file…
# config/routes.rb
Rails.application.routes.draw do
mount_ember_app :frontend, to: "/"
end
Visit “/” and now ember is rendering!
Setting up the ember routes
Now we can set up the routing on the ember side of the app.
You can create the model, route and template in ember by using ember g resource
as follows.
~/src/ember_cli_rails_example/frontend(emberize)$ ember g resource posts title:string body:string
installing model
create app/models/post.js
installing model-test
create tests/unit/models/post-test.js
installing route
create app/routes/posts.js
create app/templates/posts.hbs
updating router
add route posts
installing route-test
create tests/unit/routes/posts-test.js
I made the path for the posts route “/”, just for convenience.
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('posts', {path: '/'});
});
export default Router;
Add the model hook to the post route to hit the rails app for the json data.
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return this.get('store').findAll('post');
}
});
JSONAPI
By default ember uses and expects the server to return json formatted according to the JSONAPI spec.
kebab🍡 case
JSONAPI uses kebab-case (dash-case) instead of snake_case. Rails doesn’t expect json to be formatted this way, so we can use the active-model-adapter ember addon to transform our json payloads.
Create this initializer to have Active Model Serializers render json in JSONAPI format.
# config/initializers/jsonapi.rb
ActiveModelSerializers.config.adapter = :json_api
Install the addon.
$ ember install active-model-adapter
Create this adapter.
// app/adapters/application.js
import ActiveModelAdapter from 'active-model-adapter';
export default ActiveModelAdapter.extend();
Load posts
We need to create a serializer for to tell Active Model Serializers what json to render for Posts.
$ rails g serializer post
create app/serializers/post_serializer.rb
Add the title
and body
attributes to the serializer.
# app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
end
Make the index action for the PostsController in rails look like this:
def index
@posts = Post.all
render json: @posts
end
Visit localhost:3000, you won’t see anything on the page but if you use the Ember Inspector you can see that the data has loaded.
You can fill out the posts.hbs template to view the data.
{{#each model as |post|}}
<h1>{{post.title}}</h1>
<p>
{{post.body}}
</p>
{{/each}}
Create form to save new posts
Lets create a form to save the posts now.
We can create a component to keep the form actions and template in.
$ ember g component post-form
installing component
create app/components/post-form.js
create app/templates/components/post-form.hbs
installing component-test
Add to the posts.hbs template to render it.
<!-- app/templates/post.hbs -->
{{#each model as |post|}}
{{post-form}}
here are the posts
{{#each model as |post|}}
<h1>{{post.title}}</h1>
<p>
{{post.body}}
</p>
{{/each}}
We haven’t added anything to the post-form template yet, so let’s add the input elements and submit button.
<!-- app/templates/components/post-form.hbs -->
{{input value=title}}
<br/>
{{textarea value=body}}
<br/>
<button {{action 'save'}}>Save</button>
When the user clicks the save button the save action will be called in the post-form component. We still have to write that action so let’s do that now. We inject the store as a service to access ember data’s store.
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
actions: {
save(){
let post = this.get('store').createRecord('post', {
title: this.get('title'),
body: this.get('body')
});
post.save();
}
}
});
Calling post.save()
is when our rails app will be hit with a POST with the payload.
Right now the payload looks like this.
{"data":{"attributes":{"title":"asdf","body":"adsf"},"type":"posts"}}
If you’ve worked with rails before you know that rails controllers expect something that looks more like this:
{"post":{"body":"asdf", "title":"asdf"}}
First of all, rails does not understand the application/vnd.api+json
mime type
out of the box. If you try to debug the controller and inspect the parameters
you’ll just see an empty hash. We we should add this code to
config/initializers/mime_types.rb
:
api_mime_type = %W(
application/vnd.api+json
text/x-json
application/json
)
Mime::Type.unregister :json
Mime::Type.register 'application/json', :json, api_mime_type
We can change our post_params
method in our posts controller to parse the
JSONAPI formatted parameters correctly.
def post_params
ActiveModelSerializers::Deserialization.jsonapi_parse(params, only: [:title, :body])
end
The only
option for jsonapi_parse
works similar to the whitelist in rails’
strong parameters, disallowing any parameters that are not explicitly listed to
come through.
Now the form should be working correctly.
Troubleshooting
Build failing with warnings
Ember cli rails seems to treats warnings as errors sometimes, halting the process and your app won’t be served. You can try to fix the warnings to make the build finish properly.
Build completes successfully, but rails server is hanging
Check if the file frontend/tmp/build.lock exists. It might be leftover from a previous build and was not deleted for whatever reason, so you might try deleting it to see if that brings the app back to life.