Rails internals

Safely extend a Ruby on Rails controller

October 17, 2022 08:46

Photo by Matt Artz on Unsplash

With Avo, being a Ruby on Rails engine, we have a few controllers where we "chew up" the request, fetch the data, and display the results. This includes our own ApplicationController.

Over the weekend, one of our customers reported an issue after they updated to Avo 2.17. After a bit of digging I figured that one helper we introduced in the ApplicationController was missing from their setup. Fear came over me; "did we forget to add that helper?", "Was it somehow removed in a merge?", "is this happening to everyone?".
I started digging in. I opened the gem using bundle open avo and checked. We didn't forget about it. It was there.

Hmmm. Weird. I started to think 🤔 "could it be? could they have overriden the ApplicationController class?". Yes they did! They did it ot add a concern that deals with some multitenancy scenario.

# Copied from Avo to `app/controllers/avo/application_controller.rb`
module Avo
  class ApplicationController < ::ActionController::Base
    include Pagy::Backend
    include Avo::ApplicationHelper
    include Avo::UrlHelpers

    protect_from_forgery with: :exception
    around_action :set_avo_locale
    before_action :multitenancy_detector

    # ... more Avo::ApplicationController methods

    def multitenancy_detector
      # your logic here
    end
  end
end

That fixed their business need but introduced a a very fragile piece of code that broke as soon as we updated the application controller.
Fortunately there's a nice way to fix that.

Create a concern that holdes your piece of code.

# app/controllers/concerns/multitenancy.rb
module Multitenancy
  extend ActiveSupport::Concern

  included do
    before_action :multitenancy_detector
  end

  def multitenancy_detector
    # your logic here
  end
end

Include it in the controller from the outside

# configuration/initializers/avo.rb
Rails.configuration.to_prepare do
  Avo::ApplicationController.include Multitenancy
end

Now, the concern, the multitenancy_detector method, and the before_action are safely included in Avo::ApplicationController.

More on this on our docs about the ApplicationController ✌️

Bonus

If you'd like to add a before_action before all of Avo's before actions, use prepend_before_action instead. That will run that code first and enable you to set an account or do something early on.