Wiring up CRUD Actions Between Ember and Rails
This is not fully fleshed-out, but we're using it.
There are some tutorials out there to help you get started with pairing the excellent Ember front end javascript framework with a Rails back end API. It can be a little harder to find a condensed source to help you go beyond simple GET requests.
This tutorial is based off of a project called Snowcial, and will cover implementing CRUD actions for an existing groups
model. You can substitute this with whatever model makes sense for your project. Requirements include a working app* with index
and show
actions. We will go over adding create
, update
, and destroy
. I'll also assume that you are comfortable with Ruby and Rails already though you'll only need passing understanding of javascript and Ember.
*App, in this case, means a Rails app providing a JSON API and an Ember front end consuming it. This set of tutorials from Dockyard will get you up to speed.
A note about testing
Testing should be first and foremost on your mind when thinking about implementing new functionality for your apps. Unfortunately it is outside the scope of this tutorial. Test your apps. Ask for help or advice if you need it.
Ember
Step 1: Routes
Routes in Ember are not the same as routes in Rails, but they are also not entirely different. We'll start here. create a new app/routes/groups/create.js
file and add the following. We are adding a create function for the group
model and the action to go along with it.
//app/routes/groups/create.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('group', {
name: ''
});
},
actions: {
create: function() {
var my_model = this.controller.get('model');
my_model.save();
this.transitionTo('groups.show', my_model);
}
}
});
Follow suit for the edit
, index
, and show
.
//app/routes/groups/edit.js
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
submit: function() {
var my_model = this.controller.get('model');
my_model.save();
this.transitionTo('groups.show', my_model);
}
}
});
//app/routes/groups/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('group');
}
});
//app/routes/groups/show.js
import Ember from 'ember';
export default Ember.Route.extend({
actions:{
delete: function() {
this.controller.get('model').destroyRecord();
this.transitionTo('groups.index');
}
}
});
Step 2: Templates
You will need your templates to have forms for your create
and edit
actions. Ember's magic will help you out. Unfortunately, my editor doesn't have handlebars highlighting. That's a thing that you'll want unless you're a total masochist or Swiss, in which case you will prefer Emblem.
//app/templates/groups/create.hbs
<h4 class="create-new-group">Create New Group</h4>
<p>Name: {{input value=name class='group-name'}}</p>
<p>Description: {{input value=description class='group-description'}}</p>
<p><button type='submit' {{action 'create'}} class='commit-group-creation'>Submit</button></p>
//app/templates/groups/edit.hbs
<h4>Edit</h4>
<p>Name: {{input value=name class='group-name'}}</p>
<p>Description: {{input value=description class='group-description'}}</p>
<p><button type='submit' {{action 'submit'}} class='commit-group-change'>Submit</button></p>
//app/templates/groups/index.hbs
<h3>Groups</h3>
<table>
<thead>
<th>Name</th><th>Trips Taken</th>
</thead>
<tbody>
<p>{{#link-to 'groups.create' class="create-group"}}Create{{/link-to}}</p>
{{#each}}
<tr>
<td>
{{~#link-to 'groups.show' this}}
{{name}}{{~/link-to}}</td><td>{{trips.length}}</td>
</tr>
{{/each}}
</tbody>
</table>
//app/templates/groups/show.hbs
<h4>{{name}}</h4>
{{link-to 'Edit' 'groups.edit' model class='edit-group'}}
<button {{action 'delete'}} class="delete-group">Delete</button>
<h5>Group Description:</h5>
<p>{{description}}</p>
<h5>Trips</h5>
<ul>
{{#each trips}}
<li>{{name}}</li>
{{/each}}
</ul>
At this point, you may feel the urge to delete app/templates/groups.hbs
. Go for it. Or don't. It's basically just another partial that can hold your various groups
templates.
Step 3: router.js
Lastly, you'll need to add these new routes to your router.js
//app/router.js
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('demo');
this.resource('groups', function() {
this.route('show', {path: ':group_id'});
this.route('edit', {path: ':group_id/edit'});
this.route('create', {path: 'create'});
});
});
export default Router;
Rails
Step 1: Serializers
If you don't already have a serializer for your model then you'll need to add gem 'active_model_serializers'
to your Gemfile
and generate a new serializer.
$ rails generate serializer group
The most important part of your serializer is adding the correct attributes. I have added a bit more to mine because I want some of the associations to come along as part of the JSON.
#app/serializers/group_serializer.rb
class GroupSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :name, :description
has_many :trips
end
*Note that embed
is deprecated.
Step 2: CSRF Considerations
Rails has a default protection against Cross-Site Request Forgery (CSRF) attacks. While an in-depth analysis of site security is beyond the scope of this tutorial, you can avoid the problem by changing your ApplicationController.
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
end
That should get you up and running. If you have questions or comments then sound off below or feel free to reach out on twitter or GitHub.