4
\$\begingroup\$

I have the following models:

  1. User
  2. Picture
  3. Variant

User has_many pictures, picture has_many variants.

Variant has value price and I am trying to find the price of all pictures of selected (e.g. for user id 15) current user.

First I tried like this:

def value(user)
 total = 0
 user.pictures.each do |picture|
 picture.variants.each do |variant|
 total += variant.price
 end
 end
 total.to_f
end

How could I improve this code so that I can avoid the N+1 problem?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jul 4, 2017 at 16:45
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

You could do this with just one query. There are variations how to construct the query but I would probably do something like this:

picture_ids = user.pictures.select(:id) # Will be used as a subquery if you use select
Variant.where(picture_id: picture_ids).sum(:price)

This will only generate one query, and sum all the prices using sql, so you don't lose performance.

EDIT: As pointed out in the comment below, if you are using MySQL (or have a SQL server which does not handle subqueries that good) you can use pluck instead of select. That will make as separate query to fetch all the picture_ids and everything else will still work the same way.

answered Jul 4, 2017 at 19:36
\$\endgroup\$
1
  • \$\begingroup\$ With MySQL you need to be careful about using sub-selects as their performance is not very good (see dba.stackexchange.com/questions/14565/…) iirc, Oracle and PostgreSQL optimize the query better. \$\endgroup\$ Commented Jul 5, 2017 at 14:58
0
\$\begingroup\$

There are two options:

1) Add a has_many though clause to your user object:

class User
 has_many :pictures
 has_many :variant, through: :pictures
 ...

You could also explicitly join tables into a single query:

Variants.joins(:pictures).where(pictures: {user: user} )

I would also use the sum method to do the query on the database server:

user.variant.sum(:price)
answered Jul 5, 2017 at 15:05
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.