DEV Community

Kirill Usanov
Kirill Usanov

Posted on

Simple ActiveRecord filtering with tiny_filter 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

Top comments (0)