Reputation: 2047
I have a simple controller method:
products = Product.where(name: name, color: color, size: size, available: true)
Which returns a Product::ActiveRecord_Relation object. I want to take the first object and pull out a field e.g. products.first.product_code
But this and some other methods that I tried re-query the database. I've tried:
products[0].product_code
products.take.product_code
All of which re-query the database, hitting the database twice. Once for the where and one to take the field. Is there a simple solution that won't hit the db?
What does work is converting the ActiveRecord to an array (products.to_a.[0].product_code
) but this seems inefficient.
Below is the server log to show the two separate hits:
Here is my controller method for reference:
def update_selection
size = params[:size]
color = params[:color]
name = params[:product_name]
products = Product.where(name: name, color: color, size: size, available: true)
product_code = products.empty? ? 'sold out' : products.first.product_code
respond_to do |format|
format.json { render json: { count: products.length, code: product_code}}
end
end
Upvotes: 1
Views: 693
Reputation: 546
My best guess is that you're not actually hitting the database when assigning to products
. ActiveRecord relations are lazy, meaning they don't query the database until the moment a result of that query is needed.
To put it in terms of your issue - this line:
products = Product.where(name: name, color: color, size: size, available: true)
doesn't require any data to actually be pulled from the db, so the query isn't carried out.
products.first.product_code
, on the other hand, will cause a query to fire.
EDIT:
I believe the issue is coming from using .exists?
(which is an ActiveRecord method that always fires a query). If you're checking to see if the query returned any results, try using .present?
or .any?
instead of .exists?
.
EDIT 2:
OK, thanks for posting the code. You've got a couple options here.
.to_a
:products = Product.where(name: name, color: color, size: size, available: true).to_a
This loads all of the products
into memory, which makes sense if you expect there to be a small number of products. This only fires one query.
product_code = products.first.try(:product_code) || 'sold out'
This is more memory-efficient (since you're loading a maximum of one product
into memory), but uses two queries (products.count is the other one
).
Upvotes: 6
Reputation: 52386
You might find this to be efficient, as it gets a SQL-based count, and then reruns a product query if required to get a single product.
Do you really want the "first" in the sense of the one with the lowest id? If so then use first
, otherwise use take
.
def update_selection
size = params[:size]
color = params[:color]
name = params[:product_name]
products = Product.where(name: name, color: color, size: size, available: true)
product_count = products.count
product_code = product_count.zero? ? 'sold out' : products.take.product_code
respond_to do |format|
format.json { render json: { count: product_count, code: product_code}}
end
end
Upvotes: 1