Nicholas Dill
Posted on November 6, 2021
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
- How to apply custom filters to our Administrate data
- Storing filters in the user's session
- Passing query parameters to update the user's session
- 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
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
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
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') %>
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>
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
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
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
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') %>
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
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
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>
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.
Posted on November 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.