Taming Administrate — A gentle monkey patch
2023-12-22 3 min
Administrate is a framework made for Rails that allows to build admin dashboards. One of the most interesting aspects of Administrate is the customization features. We’re able to customize the dashboards, the views, add attribute partials.
Administrate also allows us to extract the templates so that we can adapt the code to our needs. This is actually good when the changes are related to the style or structure of the page. But I have seen many times extracting views only for changes related to attribute manipulation such as normalizing data or just filtering which attributes are displayed.
There is no need to extract and customize the view for data manipulation. We can recur to a gentle monkey patch.
Administrate uses the following classes to display data according to the page template type.
Administrate::Page::Collection
Administrate::Page::Form
Administrate::Page::Show
When thinking about monkey patching what usually comes to mind is opening the class and changing the code. In my case, I just want to remove some attributes from the show page, so it will look like this.
Administrate::Page::Show.class_eval do
def initialize(dashboard, resource, rejected_attributes)
super(dashboard)
@resource = resource
@rejected_attributes = rejected_attributes
end
def attributes
dashboard.show_page_attributes.map do |attr_name|
attribute_field(dashboard, resource, attr_name, :show)
end.reject do |attr|
@rejected_attributes.include?(attr.name)
end
end
end
There are many reasons why this is not a good approach, but I won’t list them all here. Let’s just say that we don’t know how these changes will affect the rest of the library.
💡 If you’re interested in learning more, Eileen Uchitelle has a great article against monkey patching.
This particular example will end up with the “wrong number of arguments” error when opening the corresponding model show page.
This time the catch was quick and easy enough to know what the problem is and make a fix by adding a default value to that 3rd argument in the constructor signature.
def initialize(dashboard, resource, rejected_attributes = [])
So instead of using brute force, a cleaner (and wiser) method would be to extend the Administrate::Page::Show
class more or less as follows.
module Administrate
module Page
class CustomShow < Administrate::Page::Show
def initialize(dashboard, resource, rejected_attributes = [])
super(dashboard, resource)
@rejected_attributes = rejected_attributes
end
def attributes
super.reject do |attr|
@rejected_attributes.include?(attr.name)
end
end
end
end
end
Notice the call to super
? Now we are making use of the power of OOP: inheritance. There is no need to modify the internals of administrate.
This code can be saved at lib/administrate/page/custom_show.rb
. I’m using the lib folder because I want to keep framework and application changes separate.
Now this new class can be used in the model controller as shown below without the need to extract the view or resort to a monkey patch.
module Admin
class WhateverController < Admin::ApplicationController
def show
page = Administrate::Page::CustomShow.new(
dashboard,
requested_resource,
hidden_attributes
)
render(locals: { page: page })
end
private
def hidden_attributes
return [] if super_role?
['foo', 'bar']
end
end
end
Voilà! Much more elegant and maintainable.
This is how to make a gentle monkey patch for administrate. Of course, this is a specific example, but I hope you get the gist.