Tuesday, February 3, 2015

Splitting a Rails controller

Splitting a controller into separate files may be a good idea in certain cases.

Let me start with the cases, when it's NOT a good idea. In case of a simple CRUD controller, this may be an overkill. The same applies to simple, scaffold-like controllers.

Some controllers are more resource-oriented - those may benefit from keeping the actions together in one file. People often use filters for reusing certain rules of accessing the resources here.

Other controllers may be more action-oriented (as opposed to resource-oriented). It's typical for the your-main-model-controllers. In those controllers, you will see more than the standard actions. They will contain interesting logic for 'update', 'create', 'destroy'. Those actions may have different logic. Often, they don't share that much.

The action-oriented controllers may benefit from splitting them into several files. I call them SingleActionControllers.

Please, note that the action-oriented controllers can contain a more complicated set of rules in the controller filters. Often, they create a full algebra of filters, where you need to find your way through the :except clauses and dependencies between filters. Refactoring those places requires a really precise way of working.

Apart from the filters problem, extracting a SingleActionController is easy and consists of the following steps:

1. A new route declaration above the previous (first wins)
post 'products' => 'create_product#create' 
resources :products, except: [:create]

2. Create an empty controller CreateProductController which inherits from the previous
3. Copy the action content to the new controller
4. Remove the action from the previous controller
5. Copy the filters/methods that are used by the action to the new controller
6. Make the new controller inherit from the ApplicationController
7. Change routes to add 'except: [:foo_action]'

You can also see a simple example of this refactoring here:



Anonymous said...

I enjoy the post. However, I do not really see the benefit. Let's saying it's not a CRUD action-- call it MetricProductsController with action metric.

What's the benefit of abstracting these methods to another controller with essentially the same name? Instead of hunting and pecking within the controller space, now you are hunting and pecking through out the controller folders. For someone that'd be new to the code base, this could add another level of complexity.

Open to any answers.

Andrzej Krzywda said...

One of the reasons to move the action to another controller is to decouple those actions from each other.

If your actions are fully isolated already, then you don't gain much. However, if the actions share some private methods or filters, you've got coupling.

Sometimes, such coupling is not bad on its own. It's bad, when you're scared of changing code, because too many actions rely on it. In such cases, splitting a controller may make sense.