Sonntag, 24. August 2014

Singularize resource routes, if it is singular!

In most cases plural routes (read Simple RESTful Ruby on Rails routes) are needed to deal with the resources. But sometimes it makes sense to have only a singular resource routing.
Whenever a resource is singular (like Singletons) or singular in the current context, its routes should be singular as well. Reasons are:
  1. Expressiveness (to express the resource is singular)
  2. Less routes (singular resources have no index route for returning collections)
  3. Resource ID can be hidden (the object can be found anyway)
For example the original plural routes to the users profile (routes.rb):
resources :profiles, except: [:index, :destroy]
for the model:
class Profile < ActiveRecord::Base
  belongs_to :user
  validates :user, presence: true
end
give access to the RESTful controller (profiles_controller.rb):
class ProfilesController < ApplicationController
  def show
    @profile = Profile.find params[:id]
  end

  def new
    @profile = Profile.new user_id: user.id
  end

  def create
    @profile = Profile.new user_id: user.id
    @profile.attributes = params[:profile]
    render(action: :new) unless @profile.save
  end

  def edit
    @profile = Profile.find params[:id]
  end

  def update
    @profile = Profile.find params[:id]
    @profile.attributes = params[:profile]
    render(action: :edit) unless @profile.save
  end
private
  def user
    @user = User.find session[:user_id]
  end
end
in the view:
  <%= link_to 'Profile', profiles_path(@user.profile) %>
A user can have only one profile. It can created, updated, but not deleted. After the user accounted, the User object is always present. That is why the Profile ID is not required to be parameterized.

Refactoring starts with singularizing the routes (routes.rb):
resource :profile, except: :destroy
to the refactored controller (profiles_controller.rb):
class ProfilesController < ApplicationController
  def show
    @profile = user.profile
  end

  def new
    @profile = user.build_profile
  end

  def create
    @profile = user.build_profile
    @profile.attributes = params[:profile]
    render(action: :new) unless @profile.save
  end

  def edit
    @profile = user.profile
  end

  def update
    @profile = user.profile
    @profile.attributes = params[:profile]
    render(action: :edit) unless @profile.save
  end
private
  def user
    @user = User.find session[:user_id]
  end
end
and the refactored link in the view:
  <%= link_to 'Profile', profile_path %>
Please note the ProfilesController keeps being pluralized.
All its 6 standard routes are listed:
Path HTTP verb Action Behaviour
/profile POST create Creates a new profile
/profile/new GET new Returns the form a new profile
/profile GET show Displays the profile for the accounted user
/profile PUT update Updates the profile for the accounted user
/profile/edit GET edit Returns the form for editing profile for the accounted user
/profile DELETE destroy Deletes the profile for the accounted user (since the route is needless, it was removed from the standard routes)
Please compare the generated singular routes with their plural companions. Concluding singular resources simplify routing in dedicated cases.
Further articles of interest:

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17