Building an App with Ember App Kit - Part 3

Mar 11, 2014

In this installment, we'll go through the steps needed to create, update, and destroy contacts.

Demo | Ember Code | Rails Code

In part 1, we got started with the Rails API side of things and in part 2, we got started with the ember side. In this part, we’ll go through the steps needed to create, update, and destroy contacts.

Part 3 - CRUD Implementation

Ok, let’s move on to creating a new contact. Add the new route in the ember app:

 1 var Router = Em.Router.extend({
 2   location: 'history'
 3 });
 4 
 5 Router.map(function(){
 6   this.resource('contacts', function() {
 7     this.route('new');
 8     this.route('show', { path: '/:id' });
 9   });
10 });
11 
12 export default Router;

Add the template in app/templates/contacts/new.hbs:

 1 <div class="contact">
 2   <form {{action 'save' on='submit'}}>
 3     <h1>{{input type="text" placeholder="Name" value=name}}</h1>
 4     <div class="clear"></div>
 5     <h3>{{input type="text" placeholder="Twitter" value=twitter}}</h3>
 6     <div class="notes">
 7         {{textarea placeholder="Notes" value=notes}}
 8     </div>
 9     <div class="controls">
10     <button type="submit" class="btn">Create</button>
11     <button type="button" class="btn red" {{action 'cancel'}}>Cancel</button>
12      </div>
13   </form>
14 </div>

Next, we add the corresponding route at app/routes/contacts/new.js:

1 var ContactsNewRoute = Ember.Route.extend({
2   model: function() {
3     return this.store.createRecord('contact');
4   }
5 });
6 
7 export default ContactsNewRoute;

Pretty simple so far. We just need to provide newly built contact as the route model.

At this point, let’s go fill in the stubbed links in the header of application.hbs:

1 <div id="header">
2   {{#link-to 'contacts' class="left" id="logo"}}Contacts{{/link-to}}
3   {{#link-to 'contacts.new' class="right btn"}}New Contact{{/link-to}}
4 </div>
5 <div id="main">
6   {{outlet}}
7 </div>

If you open up your browser and try hitting the new route, everythign should work but you’ll notice an empty contact shows up in the master list on the left. This isn’t ideal, so let’s go ahead and filter out new records from the master view. Update the contacts route to the following:

1 var ContactsRoute = Ember.Route.extend({
2   model: function() {
3     return this.store.filter('contact', {}, function(contact) {
4       return !contact.get('isNew');
5     });
6   }
7 });
8 
9 export default ContactsRoute;

The next thing that needs to be done is persisting the the new record. We need to implement the save action. This could be done on the controller but we will do it on the route here. I tend to only implement actions on the controller if they are strictly related to display logic, e.g. flipping a flag to reveal some part of the view. Anything else, data related, routing related, etc.. belongs on the route. I won’t get into the reasons behind this too much here but take a look at Matthew Beale’s great post on the subject.

 1 var ContactsNewRoute = Ember.Route.extend({
 2   model: function() {
 3     return this.store.createRecord('contact');
 4   },
 5 
 6   actions: {
 7     save: function() {
 8       var route = this;
 9 
10       this.currentModel.save().then(function(contact) {
11         route.transitionTo('contacts.show', contact);
12       });
13     },
14 
15     cancel: function() {
16       this.transitionTo('contacts.index');
17     }
18   }
19 });
20 
21 export default ContactsNewRoute;

The cancel action is simple. We just just transition to the index route. The save action is a little more involved. This is the first example of the new promise-based interface of Ember Data 1.0.0-Beta. We get the newly built record with this.currentModel and call save directly on it. Notice there is no concept of a transaction here. Calling save returns a promise that will resolve with the persisted contact which we will then navigate to. In order for this to work, we need to implement the create action on the server:

 1 class ContactsController < ApplicationController
 2   def index
 3     render json: Contact.all
 4   end
 5 
 6   def show
 7     contact = Contact.find(params[:id])
 8     render json: contact
 9   end
10 
11   def create
12     contact = Contact.new(contact_params)
13 
14     if contact.save
15       render json: contact, status: :created
16     else
17       render json: contact.errors, status: :unprocessable_entity
18     end
19   end
20 
21   private
22   def contact_params
23     params.require(:contact).permit(:name, :twitter, :notes)
24   end
25 end

This is completely standard with the exception of contact_params This is just us utilizing the new strong parameters in Rails 4. However, strong params are included in ActionController::Base and being that we are using Rails API, our controllers inherit from ActionController::API. So we are gonna need to manually add in strong params support. This will be done for you in the next release of Rails API but for now we can fix this by just adding it to our ApplicationController:

1 class ApplicationController < ActionController::API
2   include ActionController::StrongParameters
3 end

Ok, everything should be working now. Let’s try it out in the browser:

Working Create

Beautiful, it created the new contact and then transitioned to the show route as expected. Let’s move onto deleting a contact.

This doesn’t require any new route, we just need to add the action in show.hbs:

 1 <div class="contact">
 2   <h1>{{name}}</h1>
 3   <div class="clear"></div>
 4   <h3>{{twitterDisplay}}</h3>
 5   <div class="notes">
 6     {{notes}}
 7   </div>
 8   <div class="controls">
 9     <a href="#" class="btn">Edit</a>
10     <button type="button" class="btn red" {{action 'deleteContact'}}>Delete</button>
11   </div>
12 </div>

and then add the action handler in the show route:

 1 var ContactsShowRoute = Ember.Route.extend({
 2   model: function(params) {
 3     return this.store.find('contact', params.id);
 4   },
 5 
 6   actions: {
 7     deleteContact: function() {
 8       var contact = this.currentModel,
 9           route = this;
10 
11       contact.deleteRecord();
12       contact.save().then(function() {
13         route.transitionTo('contacts');
14       });
15     }
16   }
17 });
18 
19 export default ContactsShowRoute;

This action should be familiar now after seeing the save action earlier. We call deleteRecord directly on the contact and then call save which will return a promise. Once this resolves we simply transition to the contacts.

Now we need to implement the correpsonding server action:

 1 class ContactsController < ApplicationController
 2   def index
 3     render json: Contact.all
 4   end
 5 
 6   def show
 7     contact = Contact.find(params[:id])
 8     render json: contact
 9   end
10 
11   def create
12     contact = Contact.new(contact_params)
13 
14     if contact.save
15       render json: contact, status: :created
16     else
17       render json: contact.errors, status: :unprocessable_entity
18     end
19   end
20 
21   def destroy
22     contact = Contact.find(params[:id])
23     contact.destroy
24     head :no_content
25   end
26 
27   private
28   def contact_params
29     params.require(:contact).permit(:name, :twitter, :notes)
30   end
31 end

Pretty standard stuff here. Go ahead and try deleting the contact we just created:

Working Delete

Nice, it worked. We are back to our lone original seed contact.

We can now move on to the last of our CRUD actions: updating a contact. This will require a new edit route:

 1 var Router = Em.Router.extend({
 2   location: 'history'
 3 });
 4 
 5 Router.map(function(){
 6   this.resource('contacts', function() {
 7     this.route('new');
 8     this.route('show', { path: '/:id' });
 9     this.route('edit', { path: '/:id/edit' });
10   });
11 });
12 
13 export default Router;

And now the route itself in app/routes/contacts/edit.js:

 1 var ContactsEditRoute = Ember.Route.extend({
 2   model: function(params) {
 3     return this.store.find('contact', params.id);
 4   },
 5 
 6   deactivate: function() {
 7     this.currentModel.rollback();
 8   },
 9 
10   actions: {
11     save: function() {
12       var route = this;
13 
14       this.currentModel.save().then(function(contact) {
15         route.transitionTo('contacts.show', contact);
16       });
17     },
18 
19     cancel: function() {
20       this.transitionTo('contacts.show', this.currentModel);
21     }
22   }
23 });
24 
25 export default ContactsEditRoute;

The first thing to point out here is that the model hook and save actions are identical to those on our new route. The cancel action is slightly different as in this case we want to navigate back to the contact. Really, the only new thing here is the deactivate hook. When we cancel or transition away from this route, we want to discard any changes we made to the contact. This can be done by simply calling rollback on the record.

Lastly, we need to add the edit template:

 1 <div class="contact">
 2   <form {{action 'save' on='submit'}}>
 3     <h1>{{input type="text" placeholder="Name" value=name}}</h1>
 4     <div class="clear"></div>
 5     <h3>{{input type="text" placeholder="Twitter" value=twitter}}</h3>
 6     <div class="notes">
 7       {{textarea placeholder="Notes" value=notes}}
 8     </div>
 9     <div class="controls">
10       <button type="submit" class="btn">Update</button>
11       <button type="button" class="btn red" {{action 'cancel'}}>Cancel</button>
12     </div>
13   </form>
14 </div>

At this point, we can fill in our last stubbed out link. Update show.hbs to:

 1 <div class="contact">
 2   <h1>{{name}}</h1>
 3   <div class="clear"></div>
 4   <h3>{{twitterDisplay}}</h3>
 5   <div class="notes">
 6     {{notes}}
 7   </div>
 8   <div class="controls">
 9     {{#link-to 'contacts.edit' this class='btn'}}Edit{{/link-to}}
10     <button type="button" class="btn red" {{action 'deleteContact'}}>Delete</button>
11   </div>
12 </div>

Finally, we can implement the server update action:

 1 class ContactsController < ApplicationController
 2   def index
 3     render json: Contact.all
 4   end
 5 
 6   def show
 7     contact = Contact.find(params[:id])
 8     render json: contact
 9   end
10 
11   def create
12     contact = Contact.new(contact_params)
13 
14     if contact.save
15       render json: contact, status: :created
16     else
17       render json: contact.errors, status: :unprocessable_entity
18     end
19   end
20 
21   def update
22     contact = Contact.find(params[:id])
23 
24     if contact.update_attributes(contact_params)
25       render json: contact
26     else
27       render json: contact.errors, status: :unprocessable_entity
28     end
29   end
30 
31   def destroy
32     contact = Contact.find(params[:id])
33     contact.destroy
34     head :no_content
35   end
36 
37   private
38   def contact_params
39     params.require(:contact).permit(:name, :twitter, :notes)
40   end
41 end

Alright, it should be ready to try out in the browser:

Working Update

Sweet! Our app is fully functional! Why don’t we stop here for now. Part 4 will be short and sweet. We’ll DRY up some Ember code using an Ember.Component and conclude this series.