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 co…


This content originally appeared on DEV Community and was authored by matt swanson

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

It’s 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) }
  end
end

@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.

Let’s 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)
  end
end

@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 ids. 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)

I’ve 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


This content originally appeared on DEV Community and was authored by matt swanson


Print Share Comment Cite Upload Translate Updates
APA

matt swanson | Sciencx (2021-03-18T13:00:00+00:00) Prefer returning chainable ActiveRecord objects. Retrieved from https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/

MLA
" » Prefer returning chainable ActiveRecord objects." matt swanson | Sciencx - Thursday March 18, 2021, https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/
HARVARD
matt swanson | Sciencx Thursday March 18, 2021 » Prefer returning chainable ActiveRecord objects., viewed ,<https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/>
VANCOUVER
matt swanson | Sciencx - » Prefer returning chainable ActiveRecord objects. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/
CHICAGO
" » Prefer returning chainable ActiveRecord objects." matt swanson | Sciencx - Accessed . https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/
IEEE
" » Prefer returning chainable ActiveRecord objects." matt swanson | Sciencx [Online]. Available: https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/. [Accessed: ]
rf:citation
» Prefer returning chainable ActiveRecord objects | matt swanson | Sciencx | https://www.scien.cx/2021/03/18/prefer-returning-chainable-activerecord-objects/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.