Long-term Benefits from RailsAdmin
Sometimes setting up an admin tool with user authorization at the onset of a new Rails project can take a bit of work, and it’s not until later that long-term benefits of this framework can be realized. This came up recently when I was asked to write an export method to allow website admins to export customer and order data (for a specific date range) on a platform that was already using RailsAdmin in combination with CanCan for user authorization.
A normal Rails development process for this (from scratch) might look like this:
- Create controller.
- Create route to map to controller.
- Create method in controller to render initial view and send data.
- Create view which allows user to select date range.
- Add user authorization to allow specific users to access functionality.
This isn’t that much work, but here’s how the same functionality was built using RailsAdmin:
First, I create Rails Admin action, which inherits from RailsAdmin::Config::Actions. Inheriting from RailsAdmin::Config::Actions includes many class methods such as defining the actions http_methods (get, post, etc.), defining if the action is applicable to a single instance or a set of instances (which influences whether the action shows as a tab or individual item listings), and defining the icon that a user will see in the admin for this action. This bit of code also contains the controller method. In my case, this includes this method checks if the request is a get or post to determine whether to render the view or send the exported data. Here’s what the code looks like:
module RailsAdmin module Config module Actions class SpecialExport < RailsAdmin::Config::Actions::Base register_instance_option :collection do true end register_instance_option :http_methods do [:get, :post] end register_instance_option :controller do Proc.new do if request.post? # generate CSV data send_data csv_data, :filename => "export.csv", :type => "application/csv" end # renders view otherwise end end register_instance_option :link_icon do 'icon-share' end end end end end
Next, I register and activate the action in the RailsAdmin configuration. Here’s what this looks like:
module RailsAdmin module Config module Actions class SpecialExport < RailsAdmin::Config::Actions::Base RailsAdmin::Config::Actions.register(self) end end end end RailsAdmin.config do |config| config.actions do # existing actions special_export end end
Next up, I modify CanCan’s abilities to specify that the action can be performed on a specific class, and not others. Here’s what my code looks like for this:
class Ability include CanCan::Ability def initialize(user) if user && user.is_admin? #existing user authentication cannot :special_export, :all can :special_export, Order end end end
After the above steps, I create a view to allow the user to select a date range. This is fairly simple, and uses the already included jQuery UI date select functionality for the calendar UI.
Although line for line, the amount of code in the latter code infrastructure with RailsAdmin may be about the same to the former option, the API for hooking custom functionality into RailsAdmin is a win in terms of inherited functionality and maintenance. My “Special Export” method shows as a tab that can be performed on Orders only in the admin, and I wrote very little code for that specific behavior. Another maintenance gain here is that my custom methods are all consistent, which makes it easier for another developer to jump in and make changes, especially someone who is already familiar with RailsAdmin.
Example “Special Export” tab in RailsAdmin interface.
I’m not married to RailsAdmin in particular, but it happens to be a tool I’ve become familiar with and it integrates well with existing user authorization and user authentication tools. But I do always make a suggestion to use an existing Admin interface in new Rails applications, whether it’s RailsAdmin, ActiveAdmin, or another. These tools have a great foundation and have settled on a consistent API that encourage efficient customization and maintenance.