Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails - eager loading detected

I have 3 associated tables: Elment, Measurement and Image.

Element has_many Measurements

Measurements has_many Images, also Images has_many Measurements (N-N association)


How can I minimize number of queries in

images = @element.measurements.map {|e| e.images}.reject!(&:blank?)
images.flatten.map {|i| i.file.url}

For this code, there are N queries for every measurement. Can I minimize this with something like this:

@element.measurements.joins(:images).select('images.file')

Query: `

SELECT "images.file" FROM "measurements" 
INNER JOIN "images" ON "element_images"."measurement_id" = "measurements"."id" 
INNER JOIN "images" ON "element_images"."id" = "images"."image_id" 
WHERE "measurements"."element_id" = $1`

1 Answers

Here is step by step replacing:

images = @element.measurements.map {|e| e.images}.reject!(&:blank?)

For this line of code it is better to produce next relation

has_many :images, through: :measurements
# OR for better naming
has_many :measurement_images, through: :measurements, source: :images

Then you can just do:

images = @element.measurement_images

Which will execute next SQL

SELECT * 
FROM images 
INNER JOIN measurements 
ON measurements.id = images.measurement_id
WHERE measurements.element_id = $1

So no need to reject blank images as INNER JOIN just do exactly that, throws away non-matching records, check out the doc

After creating this has_many through relation, next code won't produce any N+1 issues:

images.map {|i| i.file.url}

Also you can minimize the output by adding #select, but I don't advise you to do that, as it complicates code a lot and heavily coupled on image library implementation, so in case of updating library you can break the code. But for note, if you use paperclip gem with default URL generation pattern you can do something like this:

image_ulrs = @element
  .measurement_images
  .select(
    :id,
    :type, # if you have STI model
    :<your_file_field>_file_name, 
    :<your_file_field>_updated_at
  ).map { |i| i.file.url }

<your_file_field> is the first argument which is passed to has_attached_file for example

has_attached_file :attachment

will result in

  ...  
  .select(
    :id,
    :type, # if you have STI model
    :attachment_file_name, 
    :attachment_updated_at
  )

but again I do NOT suggest to do that as it does not give a lot of performance improvement but can be a headache in future.

like image 122
mpospelov Avatar answered Feb 25 '26 20:02

mpospelov