[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