Sonntag, 30. März 2014

Enhancing the Rails controller API RESTfully

If the 7 standard RESTful actions (read A simple Ruby on Rails route) are not enough, this one is for you.
The following example will show how, to add an additional RESTful route.
The original controller code example only lists all people:
class TasksController < ApplicationController
  def index
    @tasks = Task.all
  end
end
and the routes.rb:
resources :tasks, :only => :index
and finally the index.html.erb view:
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <% @tasks.each do |task| %>
      <tr>
        <td><%= task.name %></td>
        <td><%= task.duration %></td>
      </tr>
    <% end %>
  </tbody>
</table>
The new requirement is to add a personalized filter to the tasks collection. That's why I add a second action to the controller:
class TasksController < ApplicationController
  def index
    @tasks = filter_tasks
  end

  def search
    session[:duration] = params[:duration]
    @tasks = filter_tasks
    render :action => :index
  end
private
  def filter_tasks
    Task.duration(session[:duration]).all
  end
end
The new search action stores a filter parameter (duration) into the personalized session and finds all tasks depending on their duration. The finder was extracted into the private method (filter_tasks), because the same query is also needed in the index action for consistency reasons.
A model class method (duration) was used as a scoped finder.
Therefore the model:
class Task < ActiveRecords::Base
  def self.duration duration=nil
    return scoped if duration.nil?
    where :duration => duration
  end
end
The search action ends with rendering the same template as the index action.
The next step is to add the custom route into the routes.rb:
resources :tasks, :only => :index do 
  post 'search', :on => :collection
end
Please note, the new route is a collection route. It returns a collection of tasks. And it uses the HTTP verb 'POST' for posting the filter parameters to the server.
Doing rake routes in the console:
tasks GET /people/:person_id/tasks(.:format) tasks#index
search_tasks POST /tasks/search(.:format) tasks#search
In the index.html.erb the search URL helper method is used to generate the filter form:
<%= form_tag search_patients_path do %>
  <%= text_field_tag :duration %>
<% end %>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Duration</th>
    </tr>
  </thead>
  <tbody>
    <% @tasks.each do |task| %>
      <tr>
        <td><%= task.name %></td>
        <td><%= task.duration %></td>
      </tr>
    <% end %>
  </tbody>
</table>
I admit, sometimes the world is not as simple as examples and tutorials often pretend. That's why there are situations that seem to force you to extend the controller API with a new REStful action. But those cases are rare. Often times there is an option to stick to the 7 standard actions. So always think twice before you want to extend a controller API and always keep the REST approach in mind when doing it, because:
Every additional public method means accepting the responsibility for it.

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.3

Sonntag, 23. März 2014

Reduce the routes! ... have as less as required

If your Ruby on Rails application contains too much routes, you should reduce them. For example this routes.rb (taken from my previous post Simple RESTful Ruby on Rails routes):
resources :people
offers those routes (doing rake routes in the Rails console):
people GET /people(.:format) people#index
POST /people(.:format) people#create
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
PUT /people/:id(.:format) people#update
DELETE /people/:id(.:format) people#destroy
But if the people_controller.rb looks like:
class PeopleController < ApplicationController
  def index # GET
  end

  def show # GET
  end
end

there are 5 routes too much. Offering them anyway is misleading and improper.
Those would be sufficient:

people GET /people(.:format) people#index
person GET /people/:id(.:format) people#show
That is why the routing generator 'resources' has the option :only: And it is easy to achieve. Refactoring the routes.rb to:
resources :people, :only => [:index, :show]
generates the 2 required routes to PeopleController#index and PeopleController#show.
Just to mention it, the opposite to :only is :except.
For example:
resources :people, :except => :destroy
offers all standard RESTful routes to the people resources except 'destroy':
people GET /people(.:format) people#index
POST /people(.:format) people#create
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
PUT /people/:id(.:format) people#update
Offering routes to a resource and not having the appropriate actions in stock is malicious.
Reduce them to the required amount!

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17

Sonntag, 16. März 2014

A nested route in Ruby on Rails.

Often times routing isn't that easy like Simple RESTful Ruby on Rails routes, e. g. there are some resources in the scope of other resources.
As an example a person has many tasks:
class Person < ActiveRecord::Base
  attr_accessible :name
  has_many :tasks
end
and every task belongs to a person:
class Task < ActiveRecord::Base
  attr_accessible :name, :person_id
  belongs_to :person
end
The original config/routes.rb contains the routes to the resources:
resources :people
resources :tasks
and in the view people/show.html.erb there is a link to the form for a new task, which is for a very specific person:
<%= link_to "New task for #{@person.name}", 
    new_task_path(:person_id => @person.id) %>
The generated result looks like:
New task for David
The additional parameter is required for setting the association to the person in the tasks_controller.rb, but has to be commited:
class TasksController < ApplicationController
  def new
    @task = Task.new :person_id => params[:person_id]
  end
end
Well, the parameter passing works, but is awful.
Please note the URL "/tasks/new?person_id=1".
It is not resourceful.
It does not clarify the association between Person and Task.
But an URL like "/people/1/tasks/new" definitely would give a clear intention about the association. In Ruby on Rails there is a way to achieve just that: nested routes.
The refactored config/routes.rb:
resources :people do
  resources :tasks
end
Doing "rake routes" in the console responds with:
tasks GET /people/:person_id/tasks(.:format) tasks#index
POST /people/:person_id/tasks(.:format) tasks#create
new_person_task GET /people/:person_id/tasks/new(.:format) tasks#new
edit_person_task GET /people/:person_id/tasks/:id/edit(.:format) tasks#edit
task GET /people/:person_id/tasks/:id(.:format) tasks#show
PUT /people/:person_id/tasks/:id(.:format) tasks#update
DELETE /people/:person_id/tasks/:id(.:format) tasks#destroy
and that's why the refactored view people/show.html.erb should look like:
<%= link_to "New task for #{@person.name}", 
    new_person_task_path(@person) %>
The URL helper method generates the link:
New task for David
The intention of the link is clear: a new task resource belonging to the person having the id "1".
Nested routes also can be generated by polymorphic routes:
<%= link_to "New task for #{@person.name}", 
    [:new, @person, :task] %>

Finally I highly recommend not to nest the routes deeper than 1 nesting. URLs like "/company/1/buildings/2/places/2/people/5/tasks/2" are nasty likewise.

Supported by Ruby 1.9.3 and Ruby on Rails 3.2.3

Sonntag, 9. März 2014

Simple RESTful Ruby on Rails routes.

Understanding the REST theory (read "REST in piece!"), Ruby on Rails routes are part of the praxis. Writing the routes to the resource and its RESTful API (actions) manually is definitely not an option.
First imagine the people_controller.rb once again:
class PeopleController < ApplicationController
  def index # GET
  end

  def show # GET
  end
 
  def new # GET
  end
 
  def edit # GET
  end
 
  def create # POST
  end
 
  def update # PUT
  end
 
  def destroy #DELETE
  end
end
Adding the following line to your config/routes.rb:
resources :people
the command "rake routes" will show you the available routes to the actions:
people GET /people(.:format) people#index
POST /people(.:format) people#create
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
PUT /people/:id(.:format) people#update
DELETE /people/:id(.:format) people#destroy
The entry into the routes.rb not only generates the available routes, it also generates some helpful helper methods.
Those helper methods can be used in the several view templates like:
<%= link_to "All people", people_path %>
<%= link_to "New person", new_person_path %>
<%= link_to "Show person", person_path(@person) %>
<%= link_to "Edit person", edit_person_path(@person) %>
<%= link_to "Delete person", person_path(@person), :method => :delete %>
<%= form_tag people_path do %>
  Create new person
<% end %>
<%= form_tag person_path(@person) do %>
  Update person
<% end %>
The responding HTML result:
All people
New person
Show person
Edit person
Delete person
Create new person
Update person
Please notice the link href and form action pathes. They are exactly the expected pathes to the REST actions, but without writing them manually. Though PUT and DELETE are injected by a Ruby on Rails trick.
Of course there is space for improvement. The same HTML result can be achieved using polymorphic routes behind the scenes:
<%= link_to "All people", :people %>
<%= link_to "New person", [:new, :person] %>
<%= link_to "Show person", @person %>
<%= link_to "Edit person", [:edit, @person] %>
<%= link_to "Delete person", @person, :method => :delete %>
<%= form_for Person.new do |f| %>
  Create new person
<% end %>
<%= form_for @person do |f| %>
  Update person
<% end %>
At this point the "Person.new" call is made only for illustrating that a new record is demanded.
Polymorphic routes are especially reasonable for views shared between various resources.
Awesome!

Supported by Ruby 1.9.3 and Ruby on Rails 3.2.3

Sonntag, 2. März 2014

REST in piece! ...a Ruby on Rails perspective

REST means Representational State Transfer and there is also a comprehensive REST dissertation.
This is just a short summary from the Ruby on Rails point of view.
Hence:
  1. A resource service is represented by an URL (Uniform Resource Locator) (e.g. /rails_app/people)
  2. The exact resource itself is represented by the URI (Uniform Resource Identifier) (e.g. /rails_app/people/1)
  3. HTTP verbs are used for addressing the operation (in Ruby on Rails only: GET, POST, PUT and DELETE)
  4. A resources operation can provide various documents (HTML, XML, JSON or others)
This could be a typical RESTful Rails controller, having the Rails standard actions (REST operations):
class PeopleController < ApplicationController
  # HTTP verb: GET
  def index
    respond_to do |format|
      format.html
      format.xml
    end
  end

  # HTTP verb: GET
  def show
    respond_to do |format|
      format.html
      format.json
    end
  end

  # HTTP verb: GET
  def new
    respond_to do |format|
      format.html
    end
  end

  # HTTP verb: GET
  def edit
    respond_to do |format|
      format.html
    end
  end

  # HTTP verb: POST
  def create
    respond_to do |format|
      format.html
    end
  end

  # HTTP verb: PUT
  def update
    respond_to do |format|
      format.js
    end
  end

  # HTTP verb: DELETE
  def destroy
    respond_to do |format|
      format.js
    end
  end
end
In that example the response documents vary from simple HTML to Javascript and were elaborated extensively by the ActionController#respond_to.
RESTful controllers and the pathes to their actions are well structured:
Path HTTP verb Action Behaviour
/people GET index Returns a collection of people
/people POST create Creates a new person
/people/new GET new Returns the form a new person
/people/1 GET show Displays the person having ID: 1
/people/1 PUT update Updates the person having ID: 1
/people/1 DELETE destroy Deletes the person having ID: 1
/people/1/edit GET edit Returns the form for editing the person having ID: 1
To be explicit: there are only a few cases, where your controllers should have other or more than those 7 actions. They already cover the resources lifecycle in terms of CRUD (Create Read Update Delete). And they never should behave different than the mentioned behaviour.
Create your Ruby on Rails controllers in a RESTful style!
It helps you to establish well structured interfaces depending on the path, the HTTP verb and the MIME type.

Supported by Ruby 1.9.3 and Ruby on Rails 3.2.1