Writing custom RailsAdmin actions


RailsAdmin is known for not being the most flexible admin framework out there; and while this is true, it is, in fact, possible to extend it’s functionalities by defining custom actions. This feature is under documented (the wiki page in the official repository at GitHub seems to be out of date and pretty incomplete).

This post is not a tentative for extensively documenting custom admin actions, but rather a description of some examples that might be useful to you and maybe presenting the first steps so you can then explore the framework in more dept.

Defining a new custom action

The first step is to define an action class. You can define it wherever you want, under whichever namespace you feel like. It should inherit from RailsAdmin::Config::Actions::Base. For example, let’s define an action that locks a user account:

module MyApp
  module Admin
    module Actions
      class LockUserAccount < RailsAdmin::Config::Actions::Base
      end
    end
  end
end

Register it

You also have to tell RailsAdmin about your action:

Place this line on the RailsAdmin initializer, right at the beginning of the config block:

RailsAdmin.config do |config|
  RailsAdmin::Config::Actions.register(
    MyApp::Admin::Actions::LockUserAccount
  )
end

And in the config.actions block call a method with the same name of the action (in snake_case):

# ...

config.actions do
  index
  new
  # ...
  lock_user_account
end

# ...

Implement it!

Cool! But why does it have to inherit from this specific class? Well, that’s ’cause action classes are not defined like regular objects, but through a DSL (just like the rest of RailsAdmin stuff).

Now, what do you do with this class? Well, one thing you have to define is where it’ll show up: will it act on a member (record) or upon a collection. Our example action acts upon a member (we’re gonna unlock a user account), but it could be an action for unlocking (or doing whatever to) many users at once, creating a new one, etc. That can be achieved by *registering an instance option for this class — *whatever that means:

module MyApp
  module Admin
    module Actions
      class LockUserAccount < RailsAdmin::Config::Actions::Base
        register_instance_option :member { true }
      end
    end
  end
end

If you want the action to be displayed on a collection level just register the :collection option as true.

One thing to note is that actions are not tied to any model object in particular, so you can think of them as global. “What the hell?” you might be thinking. Check this out:

module MyApp
  module Admin
    module Actions
      class LockUserAccount < RailsAdmin::Config::Actions::Base
        register_instance_option :member { true }

        register_instance_option :visible? { true }
      end
    end
  end
end

If you do this, this action will be available for every single record in the admin interface, no matter what type it is (which is a good reasin for not putting too complex logic in that block). OK, that’s interesting, but how do narrow it down to certain records only? Well inside that visible block you can access the subject record like this:

# ...

register_instance_option :visible? {
  subject = bindings[:object]
}

#...

Cool. RailsAdmin will apply this predicate to every record it displays and if it returns true, the action will be displayed for that record. You can define any logic in this block to decide whether to make the action available or not. This is a good place to place authorization logic, for example. In our user account locking example, it would make sense to only display this action for users (remember: these predicates will be applied to every record of every class, so we need a type check), but only when their account is not already locked:

module MyApp
  module Admin
    module Actions
      class LockUserAccount < RailsAdmin::Config::Actions::Base
        register_instance_option :member { true }

        register_instance_option :visible? {
          subject = bindings[:object]

          # if this logic gets complex it can be extracted to private methods in the action object.
          subject.is_a? User && !subject.locked?
        }
      end
    end
  end
end

Nice, how does that look? Well, it doesn’t. You need to define an icon for the action to be displayed in the collection view:

# ...

register_instance_option :link_icon {
  'icon-lock'
}

#...

Woah. Where did that come from? FontAwesome. Pick whichever icon best represents your action and and register the CSS class name as **:link_icon. **Should look something like this:

Beautiful lockBeautiful lock

That’s nice. But enough of banter! How do we actually get something done? Where’s the action. Well, for this you can define the :controller option, which returns a proc; inside it you’ll have access to the @object instance variable, which will hold the record upon which the action is being applied.

# ...

register_instance_option :controller {
  -> { @object.lock! }
}

#...

In this action we’re just unlocking the user, but you can do whatever. redirect method is available but I found it to have no effect as the action will always redirect to the previous page (or the collection index).

The final version of our class looks like this:

module MyApp
  module Admin
    module Actions
      class LockUserAccount < RailsAdmin::Config::Actions::Base
        register_instance_option :member { true }

register_instance_option :visible? {
          subject = bindings[:object]

# if this logic gets complex it can be extracted to private methods in the action object.
          subject.is_a? User && !subject.locked?
        }

        register_instance_option :link_icon {
          'icon-lock'
        }

        register_instance_option :controller {
          -> { @object.lock! }
        }
      end
    end
  end
end

Conclusion

While it’s not really straightforward, defining custom RailsAdmin action is possible and not really difficult (after you get the hang of it). This post only covered a simple use case for member actions, but there’s much more that can be done with both member and collection actions.

The lack of documentation kind of makes it more difficult to get the behavior you want working, but try poking around, check RailsAdmin’s source code and the wiki for more resources.

By the way, if you’re really serious about building full featured, flexible and powerfull admin applications, take a look on thoughbot’s administrate gem, a recent attempt of constructing a more sane and down to the earth admin framework.

We are hiring new talents. Do you want to work with us? become@codeminer42.com