This article is also available in Japanese. すごい!

In a recent interview with Full Stack Radio DHH explains how he organizes Rails controllers in the latest version of Basecamp. Here is an enhanced transcript:

What I’ve come to embrace is that being almost fundamentalistic about when I create a new controller to stay adherent to REST has served me better every single time. Every single time I’ve regretted the state of my controllers, it’s been because I’ve had too few of them. I’ve been trying to overload things too heavily.

So, in Basecamp 3 we spin off controllers every single time there’s even sort of a subresource that makes sense. Which can be things like filters. Like, say, you have this screen and it looks in a certain state. Well, if you apply a couple of filters and dropdowns to it, it’s in a different state. Sometimes we just take those states and we make a brand new controller for it.

The heuristics I use to drive that is: whenever I have the inclination that I want to add a method on a controller that’s not part of the default five or whatever REST actions that we have by default, make a new controller! And just call it that.

So let’s say you have an InboxController and you have an index that shows everything that’s in the inbox; and you might have another action where you go like “Oh I wanna see like the pending. Just show me the pending emails in that or something like that”. So you add an action called pendings:

class InboxesController < ApplicationController
  def index
  end

  def pendings
  end
end

That’s a very common pattern right? And a pattern that I used to follow more.

Now I just go like “no no no”. Have a new controller that’s called Inboxes::PendingsController that just has a single method called index:

class InboxesController < ApplicationController
  def index
  end
end
class Inboxes::PendingsController < ApplicationController
  def index
  end
end

And what I found is that the freedom that gives you is that each controller now has its own scope with its own sets of filters that apply […].

So we’ve had great controller proliferation and especially controller proliferation within namespaces. So let’s say we have a MessagesController and below that controller we might have a Messages::DraftsController and we might have a Messages::TrashesController and we might have all these other subcontrollers or subresources within the same thing. That’s been a huge success.

So basically he says that controllers should only use the default CRUD actions index, show, new, edit, create, update, destroy. Any other action would lead to the creation of a dedicated controller (which itself only has default CRUD actions).

What I think about it

I have been happily using this approach for years. The examples he mentions are only about filtering though, and are probably overkill for simple controller logic. A common way to filter in REST is by using query parameters (e.g. GET /inboxes?state=pending), so I would stick to that when the code is short and simple (once it gets long or complicated or there are too many mixed actions/concerns, I would do the same as him).

But I totally agree with the general idea of splitting controllers. I like it for several reasons.

It encourages you to produce simpler code

With this technique you can create as many controllers as you want. Use your judgment though: If the controller only has default CRUD actions and is relatively short and simple (like the ones scaffolded by Rails), there is probably no need to prematurely extract each index/ show / etc into their own controller.

Where the controller splitting technique becomes very cool is when the controller is getting heavier, even when it only has default CRUD actions. What to do then? Well, just put the heavy code in its own dedicated controller!

For example, here is what our most complicated controller looks like at my current company (we are using “relatively skinny models and fat controllers” so YMMV). It allows you to purchase a product in the API part of our app:

class Api::V1::PurchasesController < Api::V1::ApplicationController
  rescue_from Stripe::StripeError, with: :log_payment_error

  def create
    load_product
    load_device
    load_or_create_user

    create_order
    create_payment
    authorize_payment
    confirm_address

    render json: @order, status: :created
  end

  private

  def load_product
    @product = Product.find_by!(uuid: params[:product_id])
  end

  # ...
end

It makes your code more uniform

Knowing that there can only be so many CRUD actions in a controller is quite cool. No more guessing/spelunking/endlessly scrolling in long controllers to find that one weird action. No more wondering how/if a custom controller method maps to a route.

I don’t like to be surprised when doing mundane organization. I like uniform code, and heavy convention over configuration is one of the many reasons why I prefer Rails compared to other Ruby frameworks. Everything is organized the same, so you spend less time making mundane decisions, and more time on what really matters for the business.

In theory, it also means that you can move from one codebase to another and be 100% productive in a very short time. In practice there are many non-standard Rails apps in the wild: one company may use architectural patterns such as observers (not my cup of tea), another might use a whole additional architecture layer such as Trailblazer (not my cup of tea either but it has some interesting ideas), another company will use yet another tool, another one will use its own custom sauce, etc.

All of this in part because people seem unhappy with the so-called “lack of structure” in vanilla Rails apps. So they look for additional structure elsewhere. But the solution has been right under our nose all along! Split your controllers, and only use the default CRUD actions. Simple as pie, and junior developer friendly.

Rails could possibly do a better job of promoting this controller splitting technique. Their doc only briefly says “[…] you should usually use resourceful routing […]”. But the idea of CRUD actions and RESTful routes has prominently been featured in their doc for a long time, and if you’ve ever read it, it has probably crossed your mind that adding custom actions (apart from the CRUD ones) is not very “Rails way”. Splitting controllers is a good answer to that uneasy feeling.

It makes you think in terms of REST

A lot of people like REST because it is uniform and simple. Once you understand a (truly) RESTful API, it is easier to understand another one. The business logic obviously differs between applications so you have to understand it, but how you consume that logic is the same: you create a charge in Stripe (i.e. you take someone’s money), you create a text message in Twilio (i.e. you send it), you get a repository in Github, etc.

You have to bend your mind a little at first to use REST nouns instead of actions: you don’t “pay”, you “create a payment”. You don’t “add funds to your balance”, you “create a fund in a balance”. Et cetera. Maybe a bit weird at first, but I would pay this small price any day of the week rather than going back to SOAP, WSDL and all that jazz (ex-Java/JEE developers will know what I’m talking about).

As a bonus, I think that having your whole business logic interface (not necessarily implementation) dictated by REST makes for cleaner and simpler business logic. You can only have objects with so many actions: no more, no less. Yet you know you can express anything with REST, and it will be sound and dependable. It is a liberating constraint.

Here are some example RESTful Rails routes that map to splitted controllers that only use CRUD actions.

resources :purchases, only: :create

resources :costs_calculations, only: :create

namespace :company do
  resource :account_details, only: :update
  resource :website_details, only: :update
  resource :contact_details, only: :update
end

namespace :balance do
  resources :funds, only: :create
end

resource :bank_account, only: :update

For best REST design (especially when subresources are involved), I usually write down the REST action+resource first on a temporary note (example: POST /balance/funds) without worrying about the implementation. Then when I am happy with the naming, I translate it to a Rails route, which is easy since Rails has very good REST support.

Wrapping up

Splitting your Rails controllers when they have a very specific scope, too much logic, or too many mixed concerns can have a lot of good side effects in your code.

The more your app grows, the more time you will need to spend to understand it, no matter how nicely organized the code is. But splitting your controllers makes things easier.