Ruby on Rails | Hotwire | RubyUI - Applying Complex Filters to a List of Complex Records
ActiveRecord Extending

Ruby on Rails | Hotwire | RubyUI - Applying Complex Filters to a List of Complex Records

Imagine you're working on a supplier management system that handles supplier approvals. Each supplier has a range of associated data:

  • Company name + Tax ID (unique identifier in the database)
  • The user who initiated the approval
  • Approval status
  • Documentation status
  • Category assigned to the supplier
  • Whether the supplier is connected or not
  • Whether the supplier is blocked or not
  • Whether the supplier follows alternative approval flows
  • Whether the supplier is undergoing a regularization process
  • Supplier risk score
  • Custom tags assigned to the supplier

Article content
A small list of Suppliers
Article content
Advanced filters
Article content
A filter with an array of options

As you can see, we’re dealing with several types of filters: string, boolean, and multi-string selects.

Sure, you could write your SQL manually with a bunch of conditional IFs in your code—or even use ActiveRecord in a less structured way to get the result. And you'd probably make it work. But the result would be hard to read and even harder to maintain.

So, what’s the best way to solve this problem?

Let me introduce you to the extending method from the ActiveRecord::QueryMethods module. It helps you keep your filtering logic clean, modular, and maintainable. Below is a simple example showing how to handle each filter type.

# MAIN QUERY
Supplier
  .where(buyer_id: user.buyer_id) # FILTERED SCOPE
  .extending(SupplierScopes) # EXTENDING THE MODULE
  .by_name_or_cnpj(name_or_cnpj: filters[:name_or_cnpj]) # TEXT
  .by_started_by_me(user:, started_by_me: filters[:started_by_me]) # BOOLEAN
  .by_status(status: filters[:status]) # ARRAY OF STRINGS        
# SUPPLIER SCOPES MODULE
def by_name_or_cnpj(name_or_cnpj:)
  name_or_cnpj.blank? ? self : fsearch(name_or_cnpj)
end 

def by_started_by_me(user:, started_by_me:)
  started_by_me.blank? ? self : where(started_by_email: user.email)
end

def by_status(status:)
  status.blank? ? self : where(status:)
end        

In production, the real implementation spans many lines, handling multiple filters and complex conditions. But for learning purposes, this example is more than enough to help you refactor your filters for better readability, scalability, and maintainability.

Article content
Final result


Russell Rosario

Cofounder @ Profit Leap and the 1st AI advisor for Entrepreneurs | CFO, CPA, Software Engineer

3w

Jean Pierry, omg this is SO needed for our db queries rn... wouldve saved me hours of headaches last week

To view or add a comment, sign in

More articles by Jean Pierry

Insights from the community

Others also viewed

Explore topics