Cleaning up Rails Controllers the REST way

Learn how you could clean up your controllers in such a way that only the default CRUD actions are used and the advantages of doing so.

Example Application

Let’s say you have an application displaying various users, a very simple social network.One could imagine that users could have many attributes like name, email, pictures, password, biography and so on. One user can have many pictures so we will create an association for that.

We will be using Paperclip to manage file attachment. No authentication will be created to keep it simple. Rails 5 will be used so your mileage may vary.

Lets start by creating a brand new Rails application:

rails new cocktails

Add Paperclip to your Gemfile and bundle up:

echo 'gem "paperclip", "~> 5.0.0"' >> Gemfile
bundle install

We can use rails scaffold to produce a skeleton for our app.

rails generate scaffold User name:string email:string biography:text

To setup Paperclip, lets create a new model with a association to the User model and generate the migration to create the attachment column.

rails generate model UserPicture user:references
rails generate paperclip user_picture picture

Lets add paperclip to the right model and the user association.

class UserPicture < ApplicationRecord
belongs_to :user

has_attached_file :picture, styles: { medium: "800x800>",
small: "150x150>",
thumb: "100x100>" },
default_url: "/images/:style/missing.png"
validates_attachment_content_type :picture, content_type: /\Aimage\/.*\z/
end

class User < ApplicationRecord
has_many :user_pictures, dependent: :destroy
end

Finally we run all the migrations.

rails db:migrate

What usually happens

With all the setup done we could start hacking away and create each feature related to users. Overtime our controller would grow quite fat and we would probabily create new actions in addition to the rails default index, show, new, create, edit, update and destroy actions.

Imagine that we create a new action to show just a the first user picture(avatar) or biography in order to present it in a hover card on a users listing or a similar scenario.

One could then create a new action on the UsersController like the following:

class UsersController < ApplicationController
  (...)
  def show_avatar
    # Just an example, should be treated way more properly.
    @user_avatar = UserPicture.where(user_id: params[:user_id]).first
  end
  (...)
end

From the users listing page we would write an AJAX request for this new action in order to show the response in a hover card just like Facebook previews.

I suppose the route would look something like this:

get 'users/:user_id/showavatar', to 'users#show_avatar', as: 'show_avatar'

Then we would have all the other features.. and a couple more actions.

Ok. This is where I would say “No, no, wait a minute…”, can we please clean up this Users controller?

The REST cleanup

Where’s what I would do instead. Looking at an user as an object how can we divide his attributes/components, and also can we group any?

Hum… Name, email and biography could relate to a user information, and pictures, are just that user pictures.

So let’s get visual and define a tree-like view.

User

  • information
    • name
    • email
    • biography
  • pictures
    • avatar
    • all the other pictures

From idea to practice

Starting from the routes file (which in my opinion is one of the fastest way to judge an application’s code quality) one would define:

Rails.application.routes.draw do

  # Public routes go here, every user that we visit must be identified
  # The rails routes output below will provide 
  # more infomation for the reader of this article
  resources :users do
    scope module: "users" do
      resources :pictures, only: [:index, :show]
      resource  :information, only: [:show]
    end
  end

  # All the current_user management routes go here
  # Those don't need an id to identify the user
  # in question as we have a current_user method or a session which saves it
  scope module: "users" do
    resources :pictures, only: [:new, :create, :edit, :update, :destroy]
    resource  :information, only: [:edit, :update]
  end

end

Breaking it down, by using resources we define all CRUD actions routes and by using the singular resource we define all the CRUD actions except the index one. That’s because a singular resource doesn’t neeed to be listed as it’s only one of those. Read more about this in Rails Guides.

The scope module: "users" is used in order to let us use namespaced controllers without the explict prefixed route and also inside users folder. This way we can GET /pictures/edit instead of /users/pictures/edit. More on that here.

Namespaced controllers

Now to create the new controller we run:

rails generate controller users/pictures

Rails will generate a new folder and create a PicturesController inside:

➜ tree app/controllers
app/controllers
├── application_controller.rb
├── concerns
├── users
│   └── pictures_controller.rb
└── users_controller.rb

2 directories, 3 files

As an example lets build the create action.

class Users::PicturesController < ApplicationController

  def create
    @user = User.find(params[:user_id])

    if @user.save
      if params[:user_pictures]
        params[:user_pictures].each { |pic|
          @user.user_pictures.create(picture: pic)
        }
      end
      format.html { redirect_to @user, notice: t('.success') }
      format.json { render json: @user, status: :created, location: @user }
    else
      format.html {
        flash.now[:notice] = t('.failure')
        render action: "new"
      }
      format.json { render json: @user.errors, status: :unprocessable_entity }
    end

  end

Advantages

By using this method to organize your controllers the following advantages take place:

Simpler code

Ever heard that something when you fully understand you should be able to explain it in a simple fashion?

This technique can shine when your controller has more actions than the default CRUD ones or even when your controller is heavy and each action is big and complicated.

One can then split those logic units like I described previously into smaller controllers in order to follow the SR principle.

Also it’s still just Rails, no service classes, no weird patterns and not a complicated solution. And each small controller should provide a new developer enough information to understand what is going on.

Code that everyone can agree upon

This approach also benefits teams because there’s no more wondering on “where should I place this code” or “how should this be named?”. Moving between projects which apply the same technique should also be very easy and productive.

Something where Rails lacks at is in a concise skeleton. But this technique is simple and even the less experienced developers can understand it right away.

The end

As always, one should take any reading with a grain of salt. But don’t fear and go ahead and start testing this technique in that side project or even show your colleagues and ask for opinions.

I hope this helps you improve your codebase. If there’s anything you need help with feel free to ask. Would love to know your opinion about this, if you feel like it drop me a comment down below.

Written on August 23, 2017