DEV Community

Kirill Usanov
Kirill Usanov

Posted on • Edited on

5 1 1 1 1

Simple ActiveRecord filtering with TinyFilter gem

I would like to introduce you to my new gem, the basic idea of which I have used in many commercial projects over the past 3 years. And, since the gem is now done, it's time to make it public and open-source.

tiny_filter provides an abstraction layer for filtering collections with a convenient helper that makes your ActiveRecord method chaining look like this:

Post.filter_by(tag_id: 1, from: 2.days.ago).order(:created_at)
Enter fullscreen mode Exit fullscreen mode

All you have to do is implement a filter class with whatever logic you want. The gem provides you with a pretty DSL that makes your code look well:

# app/filters/post_filter.rb
PostFilter < ApplicationFilter
  filters :tag_id do |scope, value|
    scope.joins(:tags).where(tags: { id: value })
  end

  filters :from do |scope, value|
    scope.where("created_at >= ?", value)
  end
end
Enter fullscreen mode Exit fullscreen mode

Common use cases:
1) Filtering collections with user input.

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  private

  def filter_params
    return {} unless params.key?(:filter)

    params.require(:filter).permit(*permitted_filters)
      .to_h.symbolize_keys
  end

  def permitted_filters
    []
  end
end

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Posts.filter_by(filter_params).order(:created_at)
  end

  private

  def permitted_filters
    %i[tag_id from]
  end
end
Enter fullscreen mode Exit fullscreen mode

2) Simplifying queries.
tiny_filter gives you a flexible abstraction where the interface doesn't depend on the DB structure, so you can use it anywhere instead of ActiveRecord scopes and wheres.

# filter_args - a hash with filter params that you can collect dynamically.
posts = Post.filter_by(filter_args)

# versus where:
posts = Post.all
posts = posts.where("created_at >= ?", from) if from
posts = posts.joins(:tags).where(tags: { id: tag_id }) if tag_id
posts

# and versus scope:
posts = Post.all
posts = posts.from(from) if from
posts = posts.with_tag(tag_id) if tag_id
posts
Enter fullscreen mode Exit fullscreen mode

More links:
Documentation
RubyGems

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

The Most Contextual AI Development Assistant

Pieces.app image

Our centralized storage agent works on-device, unifying various developer tools to proactively capture and enrich useful materials, streamline collaboration, and solve complex problems through a contextual understanding of your unique workflow.

👥 Ideal for solo developers, teams, and cross-company projects

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay