Implementing Filters in Rails 7: A Step-by-Step Guide

Coding Posted on Jul 01, 2024
Hello, fellow Rails enthusiasts! Today, we're diving into an essential feature for many web applications: implementing filters for a list or table. We'll walk through a practical example of how to add dynamic filtering to a Rails 7 application. Imagine we're building a library management system where we need to filter books by author, publication date, genre, and availability. I'll guide you through each step, explaining the setup, the code, and its purpose.

Introduction


Before we get started, let's talk about the tools and gems we'll be using:

  • Rails 7: The latest version of Rails, which includes Turbo, Stimulus, and Importmap, making it easier to build modern, interactive web applications.
  • Simple Form: A gem that simplifies form building in Rails. It provides a cleaner syntax and more powerful form components. You can find more about it here.
  • Stimulus: A JavaScript framework that works perfectly with Rails to create interactive applications. This is now the convention with Rails 7+. Learn more about Stimulus here.
  • Turbo: Part of the Hotwire stack, Turbo makes building reactive applications easy by handling partial page updates. More on Turbo here.

With these tools, we'll create a seamless and efficient filtering system.

Step 1: Setting Up the Rails Project


First things first, make sure you have Rails 7 installed. If not, you can install it with:


gem install rails

Let's create a new Rails project:

rails new LibraryManager --skip-javascript --skip-webpack-install
cd LibraryManager

Add Simple Form to your Gemfile:


# Gemfile
gem 'simple_form'

bundle install
rails generate simple_form:install

Step 2: Adding the Filterable Concern


We'll create a `Filterable` module to manage our filtering logic. This module can be included in any model that needs filtering capabilities.

# app/models/concerns/filterable.rb
module Filterable
  extend ActiveSupport::Concern

  module ClassMethods
    def filter(filtering_params)
      results = self.where(nil) # creates an anonymous scope
      filtering_params.each do |key, value|
        results = results.public_send("filter_by_#{key}", value) if value.present?
      end
      results
    end
  end
end

This `Filterable` module defines a `filter` method that iterates over the provided parameters and dynamically applies the corresponding scope if the parameter is present.

Step 3: Updating the Book Model


Next, we'll include the `Filterable` module in our `Book` model and define the necessary filter scopes.

# app/models/book.rb
class Book < ApplicationRecord
  include Filterable

  scope :filter_by_author, -> (author) { where(author: author) }
  scope :filter_by_start_date, -> (start_date) { where("publication_date >= ?", start_date) }
  scope :filter_by_end_date, -> (end_date) { where("publication_date <= ?", end_date) }
  scope :filter_by_genre, -> (genre) { where(genre: genre) }
  scope :filter_by_availability, -> (availability) { where(available: availability) }
end

Step 4: Creating the Controller Actions


We'll update our controller to handle the filtering parameters and pass them to the model.

# app/controllers/books_controller.rb
class BooksController < ApplicationController
  def index
    @filter_params = filter_params
    @books = Book.filter(@filter_params)
  end

  private

  def filter_params
    params.permit(:author, :start_date, :end_date, :genre, :availability)
  end
end

In this code, we define a private `filter_params` method to permit the filtering parameters and use them to filter the books in the `index` action.

Step 5: Setting Up the Views


Let's create a form in the view to capture the filtering inputs and display the filtered list of books. We'll use Simple Form to make our form building easier.

<!-- app/views/books/index.html.erb -->
<h1>Library Collection</h1>

<%= simple_form_for :filter, url: books_path, method: :get, html: { class: 'form-inline' } do |f| %>
  <%= f.input :author, label: "Author", input_html: { class: 'form-control' } %>
  <%= f.input :start_date, as: :date, label: "Published After", input_html: { class: 'form-control' } %>
  <%= f.input :end_date, as: :date, label: "Published Before", input_html: { class: 'form-control' } %>
  <%= f.input :genre, label: "Genre", input_html: { class: 'form-control' } %>
  <%= f.input :availability, collection: [["Available", true], ["Checked Out", false]], include_blank: true, label: "Availability", input_html: { class: 'form-control' } %>
  <%= f.button :submit, "Filter", class: 'btn btn-primary' %>
<% end %>

<table class="table">
  <thead>
    <tr>
      <th>Author</th>
      <th>Title</th>
      <th>Publication Date</th>
      <th>Genre</th>
      <th>Availability</th>
    </tr>
  </thead>
  <tbody>
    <% @books.each do |book| %>
      <tr>
        <td><%= book.author %></td>
        <td><%= book.title %></td>
        <td><%= book.publication_date %></td>
        <td><%= book.genre %></td>
        <td><%= book.available ? 'Available' : 'Checked Out' %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Step 6: Adding Stimulus for Enhanced UX


To enhance the user experience, we'll use Stimulus to handle form submission without page reloads.

First, install Stimulus:

rails turbo:install stimulus:install

Create a Stimulus controller to handle form submissions:

// app/javascript/controllers/filter_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = ["form"]

  submit() {
    this.formTarget.requestSubmit()
  }
}

Connect the Stimulus controller to the form:

<!-- app/views/books/index.html.erb -->
<%= simple_form_for :filter, url: books_path, method: :get, html: { class: 'form-inline', data: { controller: "filter", action: "change->filter#submit" } } do |f| %>
  ...
<% end %>

Step 7: Final Touches


Make sure you have run all necessary migrations and seeded the database with some data for testing. Then, start the Rails server and navigate to the books page to see the filtering in action.

rails db:migrate
rails db:seed
rails server

Conclusion


In this guide, we've implemented a dynamic filtering system in a Rails 7 application using a library management system as our example. We covered creating a `Filterable` concern, updating the model with scopes, handling filtering in the controller, setting up the views, and enhancing the user experience with Stimulus. This pattern can be easily adapted to various models and use cases, providing a flexible and powerful way to manage filtered data in your Rails applications. Happy coding!

Leave a comment:

Comments (0)