📜 ⬆️ ⬇️

Various methods for loading associations in Ruby on Rails

Rails provides us with 4 different ways to load associations: preload, eager_load, includes and joins. Consider each of them:

Preload


This method loads associations in a separate request:
User.preload(:posts).to_a # => SELECT "users".* FROM "users" SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1) 

Since Since the preload always creates two separate requests, we cannot use the posts table in the selection condition:
 User.preload(:posts).where("posts.desc='ruby is awesome'") # => SQLite3::SQLException: no such column: posts.desc: SELECT "users".* FROM "users" WHERE (posts.desc='ruby is awesome') 

A table users - we can:
 User.preload(:posts).where("users.name='Neeraj'") # => SELECT "users".* FROM "users" WHERE (users.name='Neeraj') SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (3) 



Includes


By default, includes acts in the same way as preload, but if there is a condition on the associated table, it switches to creating a single query with LEFT OUTER JOIN.
 User.includes(:posts).where('posts.desc = "ruby is awesome"').to_a # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE (posts.desc = "ruby is awesome") 

If for some reason you need to force the use of this approach, then you can use the references method:
 User.includes(:posts).references(:posts).to_a # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" 

')

Eager_load


This method loads associations in a single query using Left Outer Join, just as includes includes works in conjunction with references.

 User.eager_load(:posts).to_a # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" 


Joins


Creates a query using INNER JOIN.
 User.joins(:posts) # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" 

At the same time, data is loaded only from the users table. In addition, this query may return duplicate entries:
 def self.setup User.delete_all Post.delete_all u = User.create name: 'Neeraj' u.posts.create! title: 'ruby', desc: 'ruby is awesome' u.posts.create! title: 'rails', desc: 'rails is awesome' u.posts.create! title: 'JavaScript', desc: 'JavaScript is awesome' u = User.create name: 'Neil' u.posts.create! title: 'JavaScript', desc: 'Javascript is awesome' u = User.create name: 'Trisha' end 


The result of running User.joins (: posts) in the database with the following data:
 #<User id: 9, name: "Neeraj"> #<User id: 9, name: "Neeraj"> #<User id: 9, name: "Neeraj"> #<User id: 10, name: "Neil"> 

We can avoid repetitions using distinct:
 User.joins(:posts).select('distinct users.*').to_a #   ,    : User.joins(:posts).uniq 

If we want to additionally get any data from the posts table, we need to include them in the select clause:
 records = User.joins(:posts).select('distinct users.*, posts.title as posts_title').to_a records.each do |user| puts user.name puts user.posts_title end 

It is worth noting that after executing the joins method, calling user.posts will result in creating another request.

Original article

Source: https://habr.com/ru/post/191762/


All Articles