Robert Mosolgo

Happy Refactoring by Keeping ApplicationController to a Minimum

Extending ActionController::Base once, in ApplicationController, is a great Ruby on Rails practice. However, if ApplicationController is your only abstract controller, it’s likely to become a maintenance challenge. To avoid this, you should extend ApplicationController as needed and move as much code as possible into its subclasses.

Feeling ApplicationController Pain

So, our app is live. We’ve dutily extended ApplicationController in all our other controllers, giving us an inheritance tree like this:

Fortunately, our app is a success and our customers want us to open an API. Let’s use API::BaseController as the superclass of all our API controllers:

As our user base grows, we need a more robust permissions system. We tighten up restrictions:

1
2
3
class ApplicationController < ActionController::Base
  before_action :authenticate!
end

Since some actions are public, we skip the restrictions:

1
2
3
4
5
6
7
class ReportsController < ApplicationController
  skip_before_action :authenticate!
end
# ...
class ProfilesController < ApplicationController
  skip_before_action :authenticate!
end

We’ve forgotten about our API, and when we deploy, we’ll be quickly reminded that ApplicationController is involved in those requests too. Since ApplicationController touches every request, it’s hard to be sure about exactly what will be affected by changes there.

ApplicationController Gains Weight

Left alone, ApplicationController can bloat for many reasons:

  • Authentication logic, perhaps with complex branching based on what the user is accessing, builds up little-by-little as the application is extended.
  • Before-actions & helpers which are used often but not always tend to accrue in ApplicationController, since they’re “used more than once.”
  • Oddball routes might be implemented in ApplicationController because no other existing controller seems like the right place.

In JavaScript development, filling the global namespace with application code is a no-no. Similarly, ApplicationController is a near-global namespace, so each addition to it should be considered very carefully. When we add to (and remove from) ApplicationController, we’re potentially altering every request that our application serves; how can we be sure we aren’t breaking something?

Isolating “Parts” of the App

Returning to the example above, I think this inheritance tree is better:

We’ve introduced abstract classes for each “part” of the app. (I use quotes because I don’t know a technical term for it!) Now, logged-in authentication would be handled by a subclass of ApplicationController, perhaps named BaseController. A logged-in controller would extend BaseController. For example:

1
2
3
class ItemsController < BaseController
  # ...
end

Similarly, public controllers would be in a namespace of their own, with their own base controller. For example:

1
2
3
class Public::ProfilesController < Public::BaseController
  # ...
end

This is good because:

  • You can refactor with more confidence, since you only have to load part of the app into memory when working on abstract controllers.
  • Stable parts of the app are more likely to remain stable (since they won’t be affected by other parts).

The corresponding file structure looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
controllers/
  api/
    base_controller.rb
    items_controller.rb
    profiles_controller.rb

  public/
    base_controller.rb
    reports_controller.rb
    profiles_controller.rb

  staff/
    base_controller.rb
    stats_controller.rb

  application_controller.rb
  base_controller.rb
  items_controller.rb
  profiles_controller.rb
  reports_controller.rb

(I’ve left some controllers in the root namespace. If you like, you could put logged-in actions in a namespace too!)

And the routes might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace :api do
  resources :items, :profiles
end

namespace :public do
  resources :profiles, :reports
end

namespace :staff do
  resources :stats
end

resources :items, :profiles, :reports

When should we extend ApplicationController?

I’d say it’s good to extend ApplicationController for each “part” of the app. It’s a bit subjective, but here are some clues:

  • Actions rendered with a different layout (or lack thereof). Your webservice, administration and public views are distinct parts of your app.
  • Actions using different authentication strategies. Keep API endpoints, public pages, and staff-only actions in separate sections. If a staff member goes rogue, you’ll be able to tighten up that part of the app confidently :)
  • Actions with different frequently-used helpers or before-actions. If there’s a before-action that’s often skipped, Rails wants to tell you something: these controllers are different! Similarly, if you have controller-level helper methods, perhaps the controllers who depend on that helper should be in a “part” of their own.

I hope a pattern like this will give you more freedom & confidence when refactoring important parts of the request-response cycle!