Rails提供了四种不同的方式来加载关联数据。 接下面,我们将看看它们分别是如何工作的。

> Rails Version - 5.1.2

Preload -预加载

预加载在单独的查询中加载关联数据。

pry(main)> User.preload([:articles]).to_a

  # >> User Load (0.3ms) SELECT "users".* FROM "users" Article Load (4.0ms) 
  SELECT "articles".* FROM "articles" WHERE "articles"."user_id" IN ($1, $2) [["user_id", 3], ["user_id",2]]

这是包含在默认情况下加载数据的方式。由于preload总是会生成两个SQL进程单独查询,所以我们无法使用articles表中的条件。 以下查询将导致错误。

pry(main)> User.preload(:articles).where("articles.content='Encore Shao'").to_a

  # >> 
  User Load (0.7ms) SELECT "users".* FROM "users" WHERE (articles.content='Encore Shao') 
  ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "articles"
但是在预加载我们可以使用表(users)的条件。

pry(main)> User.preload(:articles).where("users.name='Encore Shao'").to_a 
  # >> 
  User Load (0.5ms) SELECT "users".* FROM "users" WHERE (users.name='Encore Shao') Article Load (0.5ms) 
  SELECT "articles".* FROM "articles" WHERE "articles"."user_id" = $1 [["user_id", 3]] 
=> [#<User id: 3, email: "xxx", name: "Encore Shao", avatar: "encore.jpg", link: "", title: "Senior Web Developer", uuid: nil, introduction: "">]


Includes - 包括

包括像预加载一样在单独的查询中加载关联数据。

但是它比预加载更聪明。 上面我们看到预加载失败,查询User.preload([:articles]).where("articles.content ='Encore Shao'")。 让我们尝试与包括。

pry(main)> User.includes([:articles]).where("articles.content ='Encore Shao'").to_a
  # >>
  User Load (0.7ms)  SELECT "users".* FROM "users" WHERE (articles.content ='Encore Shao')
  ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "articles"


如您所见,和preload的结果是一样的,无法使用aritcles的条件


因此,在某些情况下,我们希望"包括"将两个查询更改为单个查询。 默认情况下,它将使用两个查询。 假设出于某种原因,您希望强制一个简单的包含案例使用单个查询而不是两个。 使用参考来实现这一点。


pry(main)> User.includes(:articles).references(:articles).to_a

  # >>
  SELECT "users"."id" AS t0_r0, "users"."email" AS t0_r1, ...., "users"."created_at" AS t0_r19, "users"."updated_at" AS t0_r20, "users"."title" AS t0_r21, "users"."uuid" AS t0_r22, "users"."introduction" AS t0_r23, "articles"."id" AS t1_r0, "articles"."title" AS t1_r1, "articles"."permalink" AS t1_r2, "articles"."content" AS t1_r3, "articles"."is_published" AS t1_r4, "articles"."published_at" AS t1_r5, "articles"."view_count" AS t1_r6, "articles"."likes_count" AS t1_r7, "articles"."dislike_count" AS t1_r8, "articles"."reprinted_source" AS t1_r9, "articles"."reprinted_link" AS t1_r10, "articles"."category_id" AS t1_r11, "articles"."user_id" AS t1_r12, "articles"."created_at" AS t1_r13, "articles"."updated_at" AS t1_r14, "articles"."embed_link" AS t1_r15 FROM "users" LEFT OUTER JOIN "articles" ON "articles"."user_id" = "users"."id"


pry(main)> User.includes(:articles).references(:articles).where('articles.content = ?', "Encore Shao").to_a

  # >>
  SELECT "users"."id" AS t0_r0, "users"."email" AS t0_r1, ...., "users"."created_at" AS t0_r19, "users"."updated_at" AS t0_r20, "users"."title" AS t0_r21, "users"."uuid" AS t0_r22, "users"."introduction" AS t0_r23, "articles"."id" AS t1_r0, "articles"."title" AS t1_r1, "articles"."permalink" AS t1_r2, "articles"."content" AS t1_r3, "articles"."is_published" AS t1_r4, "articles"."published_at" AS t1_r5, "articles"."view_count" AS t1_r6, "articles"."likes_count" AS t1_r7, "articles"."dislike_count" AS t1_r8, "articles"."reprinted_source" AS t1_r9, "articles"."reprinted_link" AS t1_r10, "articles"."category_id" AS t1_r11, "articles"."user_id" AS t1_r12, "articles"."created_at" AS t1_r13, "articles"."updated_at" AS t1_r14, "articles"."embed_link" AS t1_r15 FROM "users" LEFT OUTER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE (articles.content = 'Encore Shao')



在上述情况下,我们通过单个查询语句完成articles条件的查询。


Joins - 联合

连接使用内部连接带来关联数据。

pry(main)> User.joins(:articles)
  
  # >>>
  User Load (0.8ms)  SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id"


在上述情况下,没有选择过滤。 上面的查询也可以产生重复的结果。 

那么我们可以避免出现重复的数据,可以使用distinct。

pry(main)> User.joins(:articles).select('distinct users.*').to_a

  # >>
  ELECT distinct users.* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id"


另外,如果我们想利用文章表中的属性,那么我们需要选择它们。



  pry(main)> User.joins(:articles).select('distinct users.*, articles.content, articles.published_at').to_a



* 请注意,使用连接意味着如果您使用user.posts,则会执行另一个查询。


Eager load - 热加载

 热加载使用LEFT OUTER JOIN在单个查询中加载所有关联。


 pry(main)> User.eager_load(:articles).to_a

  # >>
  SELECT "users"."id" AS t0_r0, "users"."email" AS t0_r1, "users"."name" AS t0_r15, "users"."is_admin" AS t0_r16, "users"."avatar" AS t0_r17, "users"."link" AS t0_r18, "users"."created_at" AS t0_r19, "users"."updated_at" AS t0_r20, "users"."title" AS t0_r21, "users"."uuid" AS t0_r22, "users"."introduction" AS t0_r23, "articles"."id" AS t1_r0, "articles"."title" AS t1_r1, "articles"."permalink" AS t1_r2, "articles"."content" AS t1_r3, "articles"."is_published" AS t1_r4, "articles"."published_at" AS t1_r5, "articles"."view_count" AS t1_r6, "articles"."likes_count" AS t1_r7, "articles"."dislike_count" AS t1_r8, "articles"."reprinted_source" AS t1_r9, "articles"."reprinted_link" AS t1_r10, "articles"."category_id" AS t1_r11, "articles"."user_id" AS t1_r12, "articles"."created_at" AS t1_r13, "articles"."updated_at" AS t1_r14, "articles"."embed_link" AS t1_r15 FROM "users" LEFT OUTER JOIN "articles" ON "articles"."user_id" = "users"."id"

这正是包含在where或order子句使用articles表中的属性时进行单个查询时所做的事情。

或许更多情况我们应该此eager_load查询.