End Point

News

Welcome to End Point's blog

Ongoing observations by End Point people.

RailsAdmin: A Custom Action Case Study

RailsAdmin is an awesome tool that can be efficiently used right out of box. It provides a handy admin interface, automatically scanning through all the models in the project and enhancing them with List, Create, Edit and Delete actions. However, sometimes we need to create a custom action for a more specific feature.

Creating The Custom Action

Here we will create an "Approve Review" action, that the admin will use to moderate user reviews. First, we need to create an action class rails_admin_approve_review.rb in Rails::Config::Actions namespace and place it in the "#{Rails.root}/lib" folder. Here is the template for it:

require 'rails_admin/config/actions'
require 'rails_admin/config/actions/base'

module RailsAdminApproveReview
end

module RailsAdmin
  module Config
    module Actions
      class ApproveReview < RailsAdmin::Config::Actions::Base
      end
    end
  end
end
By default, all actions are present for all models. We will only show the "Approve" action for the models that actually support it and are yet unapproved. It means that they have approved attribute defined and set to false:
register_instance_option :visible? do
  authorized? && !bindings[:object].approved
end
RailsAdmin has a lot of configuration options. We will use one of them to specify that the action acts on the object (member) scope:
register_instance_option :member? do
  true
end
We will also specify a css class for the action (from a grid of icons), so the link will display a little checkmark icon:
register_instance_option :link_icon do
  'icon-check'
end
Now, this is what I call "customized"!

The last step is, perhaps, the most important, because it actually processes the action. In this case, the action sets the approved attribute to true for the object. The code needs to be placed into the controller context. To do so we wrap it in the following block:

register_instance_option :controller do
  Proc.new do
    @object.update_attribute(:approved, true)
    flash[:notice] = "You have approved the review titled: #{@object.title}."

    redirect_to back_or_index
  end
end

Integrating the Custom Action Into RailsAdmin

The action is ready, now it is time to plug it in RailsAdmin. This includes two steps.

First, it should be registered with RailsAdmin::Config::Actions like this:

module RailsAdmin
  module Config
    module Actions
      class ApproveReview < RailsAdmin::Config::Actions::Base
        RailsAdmin::Config::Actions.register(self)
      end
    end
  end
end

This code was placed into config/initializers/rails_admin.rb to avoid the loading issue, that occurred because RailsAdmin config was loaded first and custom action class was not present yet. Next, the custom action needs to be listed in the actions config in config/initializers/rails_admin.rb:

RailsAdmin.config do |config|
  config.actions do
    dashboard
    index
    new

    approve_review

    show
    edit
    delete
  end
end

If your application is using CanCan with RailsAdmin, you also need to authorize the approve_review action:

class Ability
  include CanCan::Ability
  def initialize(user)
    if user && user.is_admin?
      ...
      cannot :approve_review, :all
      can :approve_review, [UserReview]
    end
  end
end

Full custom action can be viewed here

Additional Notes

RailsAdmin has a nice script that can be used for generating custom actions as external gems (engines). In the case of this blog article, the approve_review was integrated directly into the Rails application. RailsAdmin action configuration options can be found here.

End Point has been using RailsAdmin for an ecommerce project that uses Piggybak. Here are a few related articles:

8 comments:

Claudio Moratti said...

Thank You! Great Article!

Jonathan UrzĂșa said...

Many thanks, this was really useful to me!

Richard Peng said...

Thanks for the tutorial. I was stumped for a while why my action wasn't showing up. Turns out you need to require your new file in lib, since Rails no longer autoloads that folder.

Michael Yagudaev said...

I also found that requiring the new class in the initalizer works better than simply declaring it there and then re-opening the declaration.

i.e. in config/initializers/rails_admin.rb put require Rails.root.join('lib/rails_admin_approve_resource')

Cristiano said...

Hy, great article.
i have created my custom action, "remove_roles". With cancan i have: can :remove_roles, Staff, id: user.id

It works but in the index of Staff i see the icon to the remove_roles action for every Staff. If i click in the icon for a Staff that isn't authorized cancan block me. So the behavior is correct but i don't want to see the icon!
Is possible?

Steph Skardal said...

Hi Cristiano,

It's difficult to answer your question without understanding the data model, but if I had to guess, it looks to me like the way you are defining abilities is incorrect. "can :remove_roles, Staff, id: user.id" means that a user can remove roles for staff where the staff.id is equal to the user.id, which doesn't make sense from a data modeling perspective. I don't know why the icon would show but clicking it would be blocked by CanCan - I would check the logs to see if there is something else going on there.

Steph

Girish said...

where should i place register_instance_option ?

Alok Swain said...

@Girish - register_instance_option is to be placed inside the declaration of the action.

module RailsAdmin
module Config
module Actions
class ImpersonateUser < RailsAdmin::Config::Actions::Base
register_instance_option :member? do
true
end

register_instance_option :link_icon do
'impersonate_user'
end

...

end