Removing magic with magic

… and check why 5600+ Rails engineers read also this

Removing magic with magic

Some gems, like InheritedResources, help us by reducing the lines of code we have to write by providing definitions automatically. However, depending on how the gem is written, it can be done “magically”. In some cases, we want to remove such gems.

By “magic” I obviously mean defining methods, or in this case, controller actions, without any explicit call to the functions provided by this gem. Such implicit behaviour makes life on legacy codebases harder. It’s harder to remove the feature (or model), because how do we know whether it is used or not? Similarly, it is harder to add feature correctly, because it’s easy to overlook some dependency or usecase which is available in our program. That can lead to sad bugs, where we have overlooked something and our code breaks.

That said, I wanted to remove the gem from one of ours applications. There was only one explicit usage…

class Admin::BaseController < InheritedResources::Base
  ...
end

And BaseController was a parent for every controller used in admin panel, so the implicit actions could be in ANY of these controller. Well, that’s not easy to clean up. How do you safely remove such gem, if you don’t know where it is even used? Obviously one could just remove the gem and watch production burn, but you don’t have to be that brutal. You can fight magic with magic :)

This is the moment where you are actually thankful for having open classes in Ruby. I’ve written following code and dynamically extended InheritedResources gem with additional behaviour, which would notify me where it is used.

module InheritedResourcesRemoval
  InheritedResourcesUsed = Class.new(StandardError)

  def index(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end

  def show(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end

  def new(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end

  def edit(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end

  def create(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end

  def update(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end

  def destroy(options = {}, &block)
    Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
    super(options, &block)
  end
end

Now we only need to open one specific module and prepend our hack into it:

module InheritedResources
  module Actions
    prepend(::InheritedResourcesRemoval)
  end
end

As you can see, each time one of the methods is used, I get notification on Honeybadger, with exact controller and action where it is used. One could now use automatic rewrites using parser gem to add the necessary code, but in my case it was only a few actions where it was used, so it was not worth it. I’ve just manually wrote couple lines of controller code and respectively changed the views in order to not use byzantine @resource variable name.

After few weeks of this code in production, I’ve removed the code and safely removed the gem :)

Enjoyed reading?

If you enjoyed that story, subscribe to our newsletter. We share our every day struggles and solutions for building maintainable Rails apps which don’t surprise you. You might also like:

You might also like