My basic query object pattern is super simple building on what many people seem to do nowadays.
- Never use method missing - always an explicitly defined interface.
- expose #relation and #all
- always return self to keep methods chainable
Example:
class CustomerQuery
attr_reader :relation
delegate :all, to: :relation
def initialize(relation=Customer)
@relation = relation
end
# e.g. using kaminari but perhaps incoming params in different format
def paginate(page_param)
unless page_param.blank?
page_number = page_param[:number]
per_page = page_param[:size]
@relation = relation.page(page_number).per(per_page)
end
self
end
# use it to add include to reduce n+1 queries
def with_addresses
@relation = relation.includes(:addresses)
self
end
# or just typical scope type things
def without_test_orders
@relation = relation.where(test_order: false)
self
end
end
Use:
$ query = CustomerQuery.new
$ query.without_test_orders.with_adress.paginate({ number: 4, size: 10 }).all
$ query.active.relation.where(market_id: 1).first
$ new_query = CustomerQuery.new(Customer.where(suspended: true))
$ new_query.with_address.all
Can add authorization using some kind of authorization policy object something like this:
class CustomerQuery
delegate :all, to: :authorized_relation
def initialize(relation=Customer, authorization: nil)
@unauthorized_relation = relation
@authorization = authorization
end
def authorized_relation
@authorized_relation ||= @unauthorized_relation.where(group_id: permitted_customer_groups)
end
def active
@authorized_relation = authorized_relation.where(active: true)
self
end
private
def permitted_customer_groups
authorization.permitted_customer_groups if authorization
end
end
Use:
$ customers_query = CustomerQuery.new(authorization: current_authorization)
$ active_customers = customers_query.active.all