+1

[Rails] A very convenient Query builder

Everyone knows Active Record, it's a a tool that allows us to write semantic and reusable queries. Let's take an example, I have some models like this

# Table name: shops
#
#  id                     :bigint           not null, primary key
class Shop < ApplicationRecord
end

# Table name: products
#  id               :bigint           not null, primary key
#  max_price        :decimal(10, )
#  min_price        :decimal(10, )
#  name             :string(255)
#  brand_id         :bigint           not null
#  category_id      :bigint
#  shop_id          :bigint           not null
class Product < ApplicationRecord
end

I'll build a page that will show the products list, and I may add some filters

  • By shop_id: only show the products that belong to a specific shop
  • By name: input a string and returns products that include the string
  • By price: greater than $100, or less than $150
  • By brand_id
  • By category_id

So if we want to filter products, it may look like this

query = Product.all
query = query.where(shop_id: params[:shop_id]) if params[:shop_id]
query = query.where("name like %?%", params[:name]) if params[:name]
query = query.where("min_price >= ?", params[:price_from]) if params[:price_from]
query = query.where("max_price <= ?", params[:price_to]) if params[:price_to]
query = query.where(shop_id: params[:brand_id]) if params[:brand_id]
query = query.where(shop_id: params[:category_id]) if params[:category_id]

The approach above can solve our need, but in real life we may have many complicated conditions, logics. How about creating a class that can receive a hash as an input and give us the result we want? Therefore, we need a better way to write it. At a time when I was surfing through the tech blogs and I found this Composable Query Builders in Rails with Arel It's a nice idea and I'd like to adjust a little bit to make it simple and easy to understand. Here's the query builder

class ProductQueryBuilder
  attr_reader :params
  FILTERS = %i(store_id brand_id category_id name from_price to_price)

  def initialize(params = {})
    @params = params
  end

  def exec
    query = Product.all
    FILTERS.each do |key|
      next unless params[key]
      query = query.where(send("#{key}_filter"), params[key])
    end

    query
  end

  def store_id_filter(store_id)
    Product.arel_table[:store_id].eq(store_id)
  end

  def brand_id_filter(brand_id)
    Product.arel_table[:brand_id].eq(brand_id)
  end

  def category_id_filter(category_id)
    Product.arel_table[:category_id].eq(category_id)
  end

  def name_filter(name)
    Product.arel_table[:name].matches("%#{sanitize_sql_like(name)}%")
  end

  def from_price_filter(price)
    Product.arel_table[:min_price].gteq(price)
  end

  def to_price_filter(price)
    Product.arel_table[:max_price].lteq(price)
  end
end

Then, it will be short and simple when we use it like this

params = {store_id: 1, brand_id: 3, category_id: }
ProductueryBuilder.new(params).exec

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí