An Interest In:
Web News this Week
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
- March 26, 2024
Prefer returning chainable ActiveRecord objects
One of the best parts about ActiveRecord is the chainable query interface:
Post.includes(:comments) .where(published: true) .where(author: Current.user) .order(:name)
To take advantage of this strength and give you flexibility in your code, always try to return chainable objects when querying data.
Usage
Its common to extract complex queries as your application grows.
class SpecialOffer def self.find_eligible_products(store, shopper) return [] if store.restricted? store.products .where('price >= ?', 100) .select{ |p| shopper.can_order?(p) } endend@products = SpecialOffer.find_eligible_products(store, shopper)#=> [#<Product:0x00007fb1719b7ec0>, #<Product:0x00007fb174744de8>, ...]
While this code may work, what happens if you need to order the @products
in a certain way? Or add additional logic? Or lazy-load some associations?
In this case, the return type of our SpecialOffer
method are arrays. We would have to switch to using Ruby array methods like sort
and select
and maybe accidentally introduce an N+1 bug if we need more data.
Lets refactor this code to make it return chainable objects.
class SpecialOffer def self.find_eligible_products(store, shopper) return Product.none if store.restricted? product_ids = store.products .where('price >= ?', 100) .select{ |p| shopper.can_order?(p) } .map(&:id) Product.where(id: product_ids) endend@products = SpecialOffer.find_eligible_products(store, shopper)#=> Product::ActiveRecord_Relation
First, we make use of the none
query method: this returns an empty (but still chainable!) result. You can call ActiveRecord methods like order
, includes
, or where
on this empty relation and it will simply return no results.
Second, instead of returning the result of our complex product query directly, we collect up the right products and then return fresh results for just those id
s. While this does incur an additional database query, we can also manipulate the results as needed.
If we want to sort the results or load an association, we can do it in the database and not be worried about any existing conditions that were run as part of the computations.
@products = SpecialOffer.find_eligible_products(store, shopper) .includes(:variants) .order(:price)@products = SpecialOffer.find_eligible_products(store, shopper) .joins(:sales) .where("sales.count > 15") .order(:sku)
Ive found this pattern to be extremely helpful for pulling out complex queries, while still maintaining flexibility to massage the data into the correct shape.
Additional Resources
Rails API: ActiveRecord::QueryMethods#none
Rails Docs: Active Record Query Interface
Original Link: https://dev.to/swanson/prefer-returning-chainable-activerecord-objects-60p
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To