Rails 3 带条件的预加载 - 如何访问预加载的数据?

发布于 2024-11-26 02:49:48 字数 3208 浏览 1 评论 0原文

我的应用程序中有很多情况,用户在与另一个对象(“组”)的关联中只有一个对象(例如“描述”)。

例如:

class User < ActiveRecord::Base
  has_many :descriptions
  has_many :groups

class Group < ActiveRecord::Base
  has_many :users
  has_many :descriptions

class Description < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

如果我想呈现某个组中的所有用户并包含他们的相关描述,我可以这样做:

#users model
def description_for(group_id)
  descriptions.find_by_group_id(group_id)
end

#view
@group.users.each do |user|
   user.name
   user.description_for(@group.id).content

但这会生成大量的描述查询。我尝试过使用连接:

#controller
@group = Group.find(params[:id], :joins => [{:users => :descriptions}], :conditions => ["descriptions.group_id = ?", params[:id]])

但由于我仍在调用 user.description_for(@group.id) 它对页面加载没有帮助。

更新:生成的 SQL 示例

Rendered users/_title.html.haml (1.6ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 7 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 7 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 7 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 51 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 51 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 51 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.8ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 5 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 5 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 5 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 52 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.2ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 52 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 52 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)

I have a lot of cases in my app where a user has no more than one object (say, a "Description") within its association to another object (a "Group").

For example:

class User < ActiveRecord::Base
  has_many :descriptions
  has_many :groups

class Group < ActiveRecord::Base
  has_many :users
  has_many :descriptions

class Description < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

If I wanted to render all the users in certain group and include their relevant descriptions, I could do something this:

#users model
def description_for(group_id)
  descriptions.find_by_group_id(group_id)
end

#view
@group.users.each do |user|
   user.name
   user.description_for(@group.id).content

But this generates a huge number of Description queries. I've tried using joins:

#controller
@group = Group.find(params[:id], :joins => [{:users => :descriptions}], :conditions => ["descriptions.group_id = ?", params[:id]])

But since I'm still calling user.description_for(@group.id) it doesn't help with the page loading.

UPDATE: Sample generated SQL

Rendered users/_title.html.haml (1.6ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 7 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 7 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 7 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 51 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 51 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 51 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.8ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 5 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 5 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 5 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 52 LIMIT 1
CACHE (0.0ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.2ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 52 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms)  SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 52 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

木有鱼丸 2024-12-03 02:49:49

看一下 include——它指定了一个应该立即加载的关联。

Take a look at include--it specifies an association that should be eager-loaded.

何处潇湘 2024-12-03 02:49:48

是的,我认为实际上你不需要rails 3中的joins子句。如果你使用include和where,Arel会为你做这些艰苦的工作。

我已经使用具有相同底层关联安排的模型对此进行了测试(尽管使用了与您不同的一组模型(和属性)),并且我认为这应该有效:

在 models/user.rb 中:

scope :with_group_and descriptions, lambda { |group_id| includes(:groups, :descriptions).where(:groups => { :id => group_id }, :descriptions => { :group_id => group_id }) }

然后在您的控制器中调用:

@users = User.with_group_and_descriptions(params[:id])

最后在视图中你可以这样做:

@users.each do |user|
  user.name
  user.descriptions.each do |desc|
    desc.content
# or
@users.each do |user|
  user.name
  user.descriptions[0].content

如果我的想法是正确的,那么这只应该进行 2 次 db 调用。第一个用于获取 user_ids 列表,第二个用于获取用户、组和描述数据,即使您正在调用用户对象的描述方法,该方法通常会包含所有描述(而不仅仅是特定组的描述) ),因为您已经填充了关联栏,当您调用 user.descriptions 时,不会再次抓取所有关联,而是仅列出您从数据库使用descriptions.group_id where 子句。然而,调用user.descriptions(true)将强制重新加载描述,从而导致它返回用户的所有描述关联的数组。

Right, I think that actually you don't need the joins clause in rails 3. If you use include and where, Arel will do the hard work for you.

I've tested this (albeit using a different set of models (and attributes) than yours) using models with the same underlying arrangement of associations, and I think this should work:

in models/user.rb:

scope :with_group_and descriptions, lambda { |group_id| includes(:groups, :descriptions).where(:groups => { :id => group_id }, :descriptions => { :group_id => group_id }) }

Then in your controller you call:

@users = User.with_group_and_descriptions(params[:id])

Finally in the view you can then do:

@users.each do |user|
  user.name
  user.descriptions.each do |desc|
    desc.content
# or
@users.each do |user|
  user.name
  user.descriptions[0].content

If I've gotten my thinking right then this should only make 2 db calls. One to get a list of user_ids and the second to get the user, group and description data, and even though you're calling a user object's descriptions method, which would ordinarily have all the descriptions in (not just the ones for a particular group), because you've already populated the association rails won't go off an grab all the associations again when you call user.descriptions, instead it'll just list the ones you've pulled from the DB using the descriptions.group_id where clause. Calling user.descriptions(true) however will force a reload of the descriptions leading to it returning an array of all the description associations for a user.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文