Sharing code between ActiveAdmin resources
22 February 2015Recently I’ve been working with ActiveAdmin a lot, since we use it as an application framework to build an internal admin-like tool. It serves us very well most of the time but the level of magic it introduces is considerable.
The other day I had to implement the same functionality for two resources1 and as every well-groomed Rails developer would want it, I wanted it DRY. Like super dry.
I created a new ActiveSupport::Concern
and dropped it in app/admin/concerns
and of course it didn’t work. I tried the obvious way to include it but it turns out that ActiveAdmin resources are a special kind of animals.
The ActiveAdmin.register
block runs in the context of an instance of ActiveAdmin::ResourceDSL
and somehow the included
block threw a MultipleIncludedBlocks
error. I couldn’t figure out why, but if include
is not working we just try extend
and bingo! it works like a charm.
I ended up with something like this:
# in app/admin/concerns/confirmable.rb
module Confirmable
def self.extended(base)
base.instance_eval do
action_item :confirm do
link_to 'Confirm', url_for(action: :confirm)
end
# omitting other gory details for brevity
end
end
end
And then
# in app/admin/post.rb
ActiveAdmin.register Post do
extend Confirmable
end
This worked pretty well on my local machine but then I pushed it to origin
and the CI build blew up. It could not load the Confirmable
module. I was baffled because according to the source code ActiveAdmin simply loads everything in the app/admin
folder. I thought maybe there is more to code loading in ruby that I know of. So I ran off and read some pretty good articles about the topic:
- Ways to load code
- Rails autoloading — how it works, and when it doesn’t
- 5 Reasons to Avoid Bundler.require
These were very good but ultimately none of them helped. I moved the confirmable.rb
file around but nothing seemed to help2. And the most infuriating part was that I could not reproduce it on my local machine. It didn’t matter which environment I used, which gems were installed.
As my last resort I turned to the GitHub issues page of ActiveAdmin: there must have been somebody else who wanted to do the same thing. And yes! finally something usable. It turns out that you should not put your modules into app/admin
, so I ended up creating an app/admin_shared
folder. It’s not particularly nice but at least it works.
UPDATE: It was brought to my attention that include
does actually work, but it is another piece of ActiveAdmin magic. You cannot use an ActiveSupport::Concern
, you have to roll your own self.included
hook.