如何使用包含3个型号的ActiveRecord?

时间:2011-09-28 23:17:54

标签: ruby-on-rails ruby-on-rails-3

鉴于以下模型:

Room (id, title, suggested)
  has_many :room_apps, :dependent => :destroy

RoomApp (room_id, app_id, appable_id, appable_type)
  belongs_to :appable, :polymorphic => true
  has_many :colors, :as => :appable
  has_many :shirts, :as => :appable

Colors (room_id)
  belongs_to :room
  belongs_to :room_app
  belongs_to :app

我想要的是获得所有建议的房间。在我的控制器中,我有:

@suggested_rooms = Room.includes(:room_apps).find_all_by_suggested(true).first(5)

这里的问题是包含不起作用并且数据库被多次命中:

Processing by PagesController#splash as HTML
  Room Load (0.6ms)  SELECT "rooms".* FROM "rooms" WHERE "rooms"."suggested" = 't' ORDER BY last_activity_at DESC
  RoomApp Load (0.6ms)  SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND ("room_apps".room_id IN (5,4,3)) ORDER BY created_at DESC
  RoomApp Load (5.9ms)  SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND "room_apps"."id" = 6 AND ("room_apps".room_id = 5) ORDER BY created_at DESC LIMIT 1
  Color Load (0.4ms)  SELECT "colors".* FROM "colors" WHERE "colors"."id" = 5 LIMIT 1
  RoomApp Load (0.6ms)  SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND "room_apps"."id" = 5 AND ("room_apps".room_id = 4) ORDER BY created_at DESC LIMIT 1
  Color Load (0.4ms)  SELECT "colors".* FROM "colors" WHERE "colors"."id" = 4 LIMIT 1
  RoomApp Load (0.4ms)  SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND "room_apps"."id" = 4 AND ("room_apps".room_id = 3) ORDER BY created_at DESC LIMIT 1
  Color Load (0.3ms)  SELECT "colors".* FROM "colors" WHERE "colors"."id" = 3 LIMIT 1

设置不正确吗?我希望能够获得建议的房间,并使用包含room_apps的一次打击,而不是目前每个房间的打击。

想法?感谢

2 个答案:

答案 0 :(得分:0)

我认为你要么像这样使用完整的Rails3 arel接口:

@suggested_rooms = Room.includes(:room_apps).where(:suggested => true).limit(5)

或者为Rails 2.3x执行此操作:

@suggested_rooms = Room.find_all_by_suggested(true, :include=>:room_apps).first(5)

答案 1 :(得分:0)

进行了一些挖掘,我想我知道发生了什么。

默认情况下,

include不会生成单个查询。它生成N个查询,其中N是要包含的模型数。

ruby-1.9.2-p180 :014 > Room.where(:suggested => true).includes(:room_apps => :colors)
  Room Load (0.5ms)  SELECT "rooms".* FROM "rooms" WHERE "rooms"."suggested" = 't'
  RoomApp Load (0.8ms)  SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."room_id" IN (1)
  Color Load (0.5ms)  SELECT "colors".* FROM "colors" WHERE "colors"."room_app_id" IN (1)

一个例外是如果你有一个where子句引用其中一个模型表,在这种情况下它将使用LEFT OUTER JOIN将where子句添加到该表。

如果你想INNER JOIN中有一些模型 AND 包含它们,你必须使用两个连接和包含给定的模型。单独加入只会在关系中进行INNER JOIN,包括将拉入字段并设置返回的模型,其关系完好无损。

ruby-1.9.2-p180 :015 > Room.where(:suggested => true).joins(:room_apps => :colors)
  Room Load (0.8ms)  SELECT "rooms".* 
                     FROM "rooms" 
                     INNER JOIN "room_apps" 
                       ON "room_apps"."room_id" = "rooms"."id"
                     INNER JOIN "colors" 
                       ON "colors"."room_app_id" = "room_apps"."id" 
                     WHERE "rooms"."suggested" = 't'

ruby-1.9.2-p180 :016 > Room.where(:suggested => true).joins(:room_apps => :colors).includes(:room_apps => :colors)
  SQL (0.6ms)  SELECT "rooms"."id" AS t0_r0, "rooms"."suggested" AS t0_r1, "rooms"."created_at" AS t0_r2, "rooms"."updated_at" AS t0_r3, "room_apps"."id" AS t1_r0, "room_apps"."room_id" AS t1_r1, "room_apps"."created_at" AS t1_r2, "room_apps"."updated_at" AS t1_r3, "colors"."id" AS t2_r0, "colors"."room_id" AS t2_r1, "colors"."room_app_id" AS t2_r2, "colors"."created_at" AS t2_r3, "colors"."updated_at" AS t2_r4 
               FROM "rooms" 
               INNER JOIN "room_apps" 
                 ON "room_apps"."room_id" = "rooms"."id" 
               INNER JOIN "colors" 
                 ON "colors"."room_app_id" = "room_apps"."id" 
               WHERE "rooms"."suggested" = 't'

最后一个查询中令人费解的SELECT部分​​是ARel,确保所有模型中的字段都是唯一的,并且能够在需要映射回实际模型时进行区分。

您是单独使用包含还是包含连接是一个问题,即您带回的数据量,以及如果您不进行INNER JOIN可能会有多大的速度差异,从而导致返回大量重复数据。我想如果'房间'有十几个字段,'颜色'有1个字段,但有100种颜色映射到单个房间,而不是总共拉回113个字段(1个房间* 13 + 100种颜色) * 1)最终会有1400个字段(13 + 1 * 100种颜色)。不完全是性能提升。

虽然单独使用include的缺点是,如果每个房间确实有大量的颜色,IN(ID)将是巨大的,是一把双刃剑。

这是我使用sqlite3

进行各种配置的快速测试

我设置了两套房间,其中一套房间:建议=>是的,另一个:建议=>假。建议的房间在房间/房间/应用/颜色之间的比例为1:1:2,建议的假房间设置为1:1:10比例相同,建议与未建议的比例为10:1。

# 100/10 rooms
# insert only
100 * 1/1/2: 8.1ms
10 * 1/1/10: 3.2ms

# insert + joins
100 * 1/1/2: 6.2ms
10 * 1/1/10: 3.1ms

# 1000/100 rooms
# insert only
1000 * 1/1/2: 76.8ms
100 * 1/1/10: 19.8ms

# insert + joins
1000 * 1/1/2: 54.5ms
100 * 1/1/10: 23.1ms

时间本身并不相关,这是通过IRB在UXntu客户机上运行在WinXP主机上的硬盘上。鉴于你有一个限制(5),它可能不会产生巨大的差异。