How to Add Custom Filters to Administrate Dashboards

nicholasdill

Nicholas Dill

Posted on November 6, 2021

How to Add Custom Filters to Administrate Dashboards

Administrate is one of my favorites Rails gems.

It gives you a CRUD interface for your entire database in seconds.

This means any admin user can log in and get access to our database records from the browser. They can create, read, update, or delete data without having to write any code, scripts, or SQL. (Don't worry you can limit which tables can be updated or deleted too.)

One problem is Administrate doesn't come with any kind of filtering and it can be hard to find specific records or records that meet certain conditions. You can sort columns alphabetically but when you have large sets of data this really doesn't help much.

There are a couple steps needed to add our own custom filters on top of Administrate, but its just a few new lines of code in a few places and I'll dive deep into why and how it works.

If you want to skip the explanation and just want the code, it's right here.

What We Will Cover

  1. How to apply custom filters to our Administrate data
  2. Storing filters in the user's session
  3. Passing query parameters to update the user's session
  4. Display the Administrate view using the current filters


How to Filter Records in Administrate

When we land on an Administrate page, the request is routed to an admin controller.

Just like any other Rails controller, our admin controller is responsible for querying the database and passing this data to the view to render it on the page.

Administrate gives us a method called scoped_resource. This method pulls our data from the database and we can add additional conditions to filter out data or organize it in a different way. This method allows us to filter our query any way we like.

Inside this method we see a variable called resource_class. This is inferred from the controller you are looking at. For example, the Admin::UsersController references your User model so the resource_class would evaluate to Users.all.

Our solution will add a few lines of logic to this method so that we can filter our Administrate views based on variables that we set in the current user's session.

If you aren't familiar with how session storage works within Rails, fear not. We'll jump into this next.

Plus you can copy the code below and change it for your specific use case and it should work out of the box.

Here's an example of the scoped_resource method you will override in the controller.

This example returns a query result that applies different filters based on the user.

def scoped_resource
  if current_user.super_admin?
    resource_class
  else
    resource_class.where(private: false)
  end
end
Enter fullscreen mode Exit fullscreen mode

In the above code we are saying if you are logged in as a super admin, Administrate should return all records.

BUT if you aren't, only return records where the private: false condition is met.

In this example we're assuming the resource has a field called private and any records marked private are only viewable to super admin users.

This is how you can apply filters to Administrate views. Add your desired conditions to this method and we will exclude any records that don't match.


Storing Filters in the User's Session

The previous example above demonstrates how to filter records in Administrate, but it's not dynamic. Meaning we can't click a button to turn this filter on or off. It's just tied to whether or not we are a super admin.

In order to let users toggle certain filters we need to store some kind of on/off state somewhere.

This is what Rails session storage is really good at.

The Rails session is kind of like a dictionary or hash that every visitor gets. We can store and modify variables specific to each user.

How to Use Session Variables

We can update the data stored in a user's session by accessing the session hash and updating the entry for a certain key or attribute.

session[:filter_published] = true
Enter fullscreen mode Exit fullscreen mode

This would update the filter_published entry and set it to true only for this user.

Since session storage uses a hash, we read from this hash as we would any other normal hash. It should look like this.

session[:filter_published]
=> true
Enter fullscreen mode Exit fullscreen mode

The last point I'll make here is that Rails session storage is a backend mechanism. In other words we have to hit the backend again or make an API call to update the user's session data.

Let's wire up the frontend to set filters on the user's session on the backend.


Passing Query Parameters to Update the User's Session

For a quick recap, we can currently customize our Administrate views by modifying the query in the scoped_resource method of our admin controller.

We can also update the user's session to store and persist any custom filters that are enabled or disabled.

Now we need to tell the backend which of our filters we want to toggle so we can make the expected change in the user's session.

Query Parameter Basics

Query Parameters are the extra attributes added at the end of a URL. You have probably seen a link resembling something like https://testsuite.io/send-emails-from-rails?ref=testsuite&view=focused.

That link will route you to https://testsuite.io/send-emails-from-rails but it also adds ?ref=testsuite&filter=true. The latter portion contains the query parameters.

This tells the server to render the expected page but to take into account the ref=testsuite and filter=true.

We can follow the same approach to pass our desired filters to Administrate so that we can customize our Administrate views.

How to Pass Query Parameters

First, we need a link that appends our desired query parameters.

If we use a Rails link_to helper method it could look something like this:

<%= link_to('Published Filter', admin_blog_posts_path(published: true), class: 'admin-filter') %>
Enter fullscreen mode Exit fullscreen mode

This will generate a link with the text "Published Filter".
The link will point to the admin_blog_posts_path which evaluates to /admin/blog_posts.
Our route helper, the admin_blog_posts_path(published: true) also passes the published: true key/value pair - those are the query parameters!

The resulting HTML would look something like this:

<a href="/admin/blog_posts?published=true" class="admin--filter">
  Published Filter
</a>
Enter fullscreen mode Exit fullscreen mode

And now we have a link that we can click to view our blog posts (or whatever model you desire to add custom filters to in Administrate).

If we want to allow multiple types of filters or add buttons to apply custom ordering, we can add more buttons that pass different query parameters.


Display the Administrate view using the current filters

Now that we are passing our query parameters to the backend, our controller can save our filters in the user's session.

In the admin controller that we are filtering, we need to save our query parameters in the user's session storage. It is as easy as updating the session hash with the desired key and value.

session[:published] = params[:published] unless params[:published].blank?
# Save the published filter to the user's session if it was passed as a query parameter
Enter fullscreen mode Exit fullscreen mode

We can use the same strategy to clear and erase our current filters too:

session[:published] = nil if params[:clear]
# Erase and reset our filter if we pass the clear=true query parameter
Enter fullscreen mode Exit fullscreen mode

Now everything should be wired correctly and we can get back to the scoped_resource method. This is were it all started and now we have the buttons and session storage setup to persist any filters that we want to set.

To apply a custom filter on our Administrate view, we need to check if we saved a value in the user's session storage.

The below example shows how to check the session storage, and modify the resource_class.

def scoped_resource
  if session[:published]
    resource_class.where(published: true)
  else
    resource_class
  end
end
Enter fullscreen mode Exit fullscreen mode

We look up the published attribute in the user's session storage and if session[:published] evaluates to true, then we apply the .where(published: true) condition to our database query. Otherwise, we pull the data normally and nothing is filtered.


Final Solution

Here's all the code without diving into explanations.

1. Add buttons to your Administrate view that append the desired filters as query parameters

<%= link_to('Published', admin_blog_posts_path(published: true), class: 'admin--filter') %>
<%= link_to('Clear Filters', admin_blog_posts_path(clear: true), class: 'admin--filter') %>
Enter fullscreen mode Exit fullscreen mode

2. Save the query parameters to the user's session

This code can live in the admin controller that you are calling. Be sure to call it before scoped_resource so that we update the session before we apply any filters.

session[:published] = nil if params[:clear]
# Erase and reset our filter if we pass the clear=true query parameter

session[:published] = params[:published] unless params[:published].blank?
# Save the published filter to the user's session if it was passed as a query parameter
Enter fullscreen mode Exit fullscreen mode

3. Apply the filters to the scoped_resource method

Inside of our scoped_resource method, we can check for any filters we saved in our session and apply them to our query.

This will apply a custom filter on our Administrate view and only show us resources that have their published field set to true.

def scoped_resource
  if session[:published]
    resource_class.where(published: true)
  else
    resource_class
  end
end
Enter fullscreen mode Exit fullscreen mode

Optional: Style buttons

Typically Administrate interfaces are only used by your internal team.

It's intended for administrative users so the design might not be a high priority, but you can make your filter buttons look good with just a few lines of CSS. Here's a class you can paste into your project.

<style>
  .admin--filter {
    text-decoration: none !important;
    margin: 5px;
    padding: 10px 20px;
    background-color: #f3f6f9;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Your Administrate controller isn't going to import your stylesheet by default since it doesn't extend your normal page layout in views/layouts/application.html.erb.

This layout is typically imported by all your other views and sets up basic things like your meta tags, font imports, and your CSS stylesheets. But since Administrate doesn't extend your application_controller.html.erb it also doesn't inherit this layout. So your normal fonts and styles won't be accessible on any of your Administrate views.

A couple of ways around this are to either explicitly add this layout to your Administrate controllers, or to add these styles directly to your Administrate views.

I find the approach with views faster and easier (granted perhaps not the best long-term solution).

You have to override the default Administrate view anyway to add your filter buttons so sprinkling in a little bit of CSS in the same view isn't a big deal.

💖 💪 🙅 🚩
nicholasdill
Nicholas Dill

Posted on November 6, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related