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:&nbsp;{{input value=name class='group-name'}}</p>

<p>Description:&nbsp;{{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:&nbsp;{{input value=name class='group-name'}}</p>

<p>Description:&nbsp;{{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>&nbsp;  
{{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.