BinaryWebPark

Part 5 – Creating, Reading, Updating, and Deleting Tasks

July 15, 2013

From part 4 of the tutorial, we have set up our polymorphic associations. Now it’s time to be able to give users the ability to CRUD (create, read, update, and delete) some tasks.

Sidebar: Using Git

I’ll be using the git subversion control system throughout this tutorial, but you don’t have to. Michael Hartl talks uses it throughout the Rails tutorial (available for free online) or you can search for it.

git checkout -b part-5-crud

Getting Ready to Create a Task

First, we need to migrate the database. So at the command prompt, type:

bundle exec rake db:migrate

You should see something like the following:

== CreateTasks: migrating ====================================================
-- create_table(:tasks)
-> 0.0526s
-- add_index(:tasks, [:taskable_id, :taskable_type])
-> 0.0005s
== CreateTasks: migrated (0.0533s) ===========================================

== CreateFolders: migrating ==================================================

-- create_table(:folders)
-> 0.0006s
== CreateFolders: migrated (0.0006s) =========================================

We need a controller with some methods to create and display tasks

At the command prompt type:

rails g controller tasks index new

Because we want to be able to let a user (or folder) create tasks, we need to modify our routes.rb file so that tasks are a nested resource under todousers and folders.

I comment out all the “get” tasks lines and add nested resources so that the config/routes.rb file looks as follows:

Tododemo::Application.routes.draw do<br />
#get "tasks/index"

#get "tasks/new"

devise_for :todousers

get "pages/home"

get "pages/about"

resources :todousers do
  resources :tasks
end

resources :folders do
  resources :tasks
end

root :to=>"pages#home"
:
end

By nesting the routes this way, we are telling our rails application to direct actions to the TasksController we generated.

To display a task, we start by remembering that a task could have been created by a folder or Todouser

So how do we locate tasks to display in the TasksController index method?

At the top of our TasksController, we add a beforefilter method, :findtaskable.

def find_taskable
  klass = [Todouser, Folder].detect { |x| params["#{x.name.underscore}_id"]}
  @taskable = klass.find(params["#{klass.name.underscore}_id"])
end

Explanation of find_taskable

The detect method is a ruby method (see the Ruby docs on the Enumerable class) that returns the first item in the [Todouser, Folder] array for which the block (i.e., {…}), is not false. Essentially, we’re looking for the class to which a task belongs when we call our index controller action.

Then we use that result to find the class object instance that corresponds to the id returned by the params hash.

More fun with Twitter Bootstrap

We’re going to need to start displaying some information soon, so we might as well modify our application layout file with a Twitter Bootstrap layout to display things more nicely. So we’ll borrow a template from the railsapps site and paste the following code in app/views/layouts/application.html.erb.

<p>Tododemo</p>
<p><%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %&gt;&lt;br />
<%= csrf_meta_tags %>

<header class="navbar navbar-fixed-top">
  <nav class="navbar-inner">

  <div class="container">
    <%= render "layouts/navigation" %>
  </div>
  </nav>
</header>

<div id="main">
  <div class="container">
    <div class="content">
      <div class="row">
        <div class="span12">
          <%= render "layouts/messages" %><br /> <%= yield %><br /> <%= params.inspect %>
        </div>
      </div><footer></footer>
    </div>
  </div>

  <p>
    <!--! end of .container -->
  </p>
</div>

<!--! end of #main -->

You’ll notice we’re changing the application layout file a bit from part 3 by moving our flash messages code into a rails view partial. This will help us tidy things up a bit in our view code.

Adding the flash messages partial

Create the file app/views/layouts/_messages.html.erb. Paste in the following code:

<% flash.each do |name, msg| %>

<%= contenttag :div, msg, class: “#{flashclass(name)}” %>

<% end %>

Add a navigation link to the task controller index view

Now a task can be created by a user or a folder. For simplicity’s sake, we’lll modify our navigation menu to allow the user to navigate to a page showing the tasks they created.

Make sure your app/views/layouts/_navigation.html.erb file looks like below with the bold code added to the original navigation code:

<ul class="nav">
  <% if todouser_signed_in? %></p>
  <li>
    <%= link_to("Logout", destroy_todouser_session_path, :method=>"delete") %>
  </li>
  <li>
    <%= link_to("Show Tasks", [current_todouser, :tasks]) %>
  </li>
</ul>

:

Let the user add a new task from the task controller index view

Paste the following code into app/views/tasks/index.html.erb

# Tasks#index

<div>
  <% @tasks.each do |task| %>
</div>

<div>
  <%= simple_format task.description+" "+task.duedate %>
</div>

<% end %>

<%= link_to "New Task", [:new, @taskable, :task] %>

The linkto_ helper allows rails to dynamically generate the path link for a new task action since @taskable can be a Todouser or Folder type. The @tasks.each block lists out the tasks associated with the user. simpleformat_ is a rails helper function that transforms text into html using simple formatting rules.

Modify new and add a create action

Clicking the “New Task” link will take us to the TasksController’s new action so here is the TasksController code to create a new task via the tasks association:

class TasksController < ApplicationController
before_filter :find_taskable

def index
  @tasks = @taskable.tasks
end

def new
  @task = @taskable.tasks.new
end

def create
  @task = @taskable.tasks.new(params[:task])
  if @task.save
    redirect_to [@taskable, :tasks], notice: "Task created."
  else
    render :new
  end
end

def edit
  @task = @taskable.tasks.find(params[:id])
end

def update
  @task = @taskable.tasks.find(params[:id])
  if @task.update_attributes(params[:task])
    redirect_to [@taskable, :tasks], notice: "Task updated."
  else
    render :edit
  end
end

def destroy
  @task = @taskable.tasks.find(params[:id])
  if @task.destroy
    redirect_to root_path, notice: "Task deleted."
  else
    redirect_to [@taskable, :tasks], notice: "Could not delete task for some reason."
  end
end

private
  def find_taskable
    klass = [Todouser, Folder].detect { |x| params["#{x.name.underscore}_id"]}
    @taskable = klass.find(params["#{klass.name.underscore}_id"])
  end
end

Add the new view

Paste or type the following code in app/tasks/views/new.html.erb:

# New Task

<%= form_for [@taskable, @task] do |f| %>

<% if @task.errors.any? %>
<div class="#{flash_class(:error)}">
  <h2>
    Please correct the following errors.
  </h2>
  <ul>
    <% @task.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
  </ul>
</div>
<% end %>

<div class="field">
  <%= f.label "Task Description" %>
  <%= f.text_area :description %>
</div>

<div class="field">
  <%= f.label "Due Date" %>
  <%= f.text_field :duedate %>
</div>

<div class="actions">
  <%= f.submit %>
</div>

<% end %>

This is a fairly standard form for a rails application. One thing to note is the array passed in to the form_for helper to let rails know how to generate the URL correctly for the polymorphic association. Whew, all that work just to let the Todouser create a new task.

Editing a todouser task

To our TasksController, we need to add edit and update methods so that we can edit our task descriptions and due dates.

def edit
  @task = @taskable.tasks.find(params[:id])
end

def update
  @task = @taskable.tasks.find(params[:id])
  if @task.update_attributes(params[:task])
    redirect_to [@taskable, :tasks], notice: &#8220;Task updated.&#8221;
  else
    render :edit
  end
end

Create the edit view

Paste or type all the code from app/tasks/views/new.html.erb into a new file app/tasks/views/edit.html.erb

Change the <h1> tag up at the top to:

<h1>Edit Task</h1>

You’re done.

Modify the index view to let the user edit a task

Open app/tasks/views/index.html.erb and edit your code as follows by adding a link_to helper underneath the task description:

<h1>Tasks#index</h1>
:
<%= simple_format task.description+" "+task.duedate %>
<%= link_to "Edit Task", [:edit, current_todouser, task] %>

:

Reading a task

Now in most rails applications, you can have the ability to show an individual task all by itself. This means we would add a show method in our controller. In the context of our application though, it doesn’t make sense since most of our task descriptions will be relatively short and don’t require an entire page to show each individual task. So we’ll skip adding a show method for now.

Delete a task

To our TasksController, we need to add a destroy method so we can delete our task.

def destroy
  @task = @taskable.tasks.find(params[:id])
  if @task.destroy
    redirect_to root_path, notice: "Task deleted."
  else
    redirect_to [@taskable, :tasks], notice: "Could not delete task for some reason."
  end
end

Modify the index view to allow a user to delete a task

Open app/tasks/views/index.html.erb and edit your code as follows by adding a link_to helper underneath the task description:

<h1>Tasks#index</h1>
:
<%= simple_format task.description+" "+task.duedate %>
<%= link_to "Edit Task", [:edit, current_todouser, task] %>
<%= link_to "Delete Task", [current_todouser, task], :confirm=>"Are you sure?", :method=>:delete %>
:

Sidebar: Using Git

Finally, we commit our work to git and merge it into the master branch.

git add .

git commit -m “added polymorphic associations”

git checkout master

git merge part-5-crud

Summary

We’ve now given a user the ability to create, update, and delete tasks. We turn our attention now to writing Cucumber specs.

Resources:

I’m keeping a public repo of this application at my github account. You can download the source code there.

Sources:
  1. http://railsapps.github.com/twitter-bootstrap-rails.html by Daniel Kehoe