【问题标题】:Active Record: Eager load association with parameterActive Record:与参数的急切负载关联
【发布时间】:2013-03-21 08:46:35
【问题描述】:

我有以下型号:

class Rating < ActiveRecord::Base
  belongs_to :item
  belongs_to :user
end

class Item < ActiveRecord::Base
  has_many :ratings
end

我想获取所有项目,以及特定用户的评分,以在每个项目旁边显示当前用户的评分(如果存在!)。

我试过了……

Item.includes(:ratings).where('ratings.user_id = ?', user_id)

...但这并没有给我没有评级的项目。

我的第一个想法是 has_many 与一个参数的关联,然后使用 include 方法传递该参数。但这似乎并不存在。

如何在不执行 N+1 查询或将所有实体加载到内存中的情况下,根据参数过滤所有帖子和预先加载的关联?

【问题讨论】:

  • 请包含用户模型
  • @AbibullahRahamathulah 这个问题是关于静态参数(has_many 条件),而不是动态
  • @gabrielhilal 用户模型为空

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


【解决方案1】:

第 1 步

使模型层中的current_user 可访问(此处概述了一种技术:https://stackoverflow.com/a/2513456/163203

第 2 步

添加与在运行时评估的条件的关联。

Rails 2

class Item < ActiveRecord::Base
  has_many :ratings
  has_many :current_user_ratings, 
           :class_name => "Rating", 
           :conditions => 'ratings.user_id = #{User.current_user.try(:id)}'
end

Rails 3.1 及更高版本

class Item < ActiveRecord::Base
  has_many :ratings
  has_many :current_user_ratings, 
           :class_name => "Rating", 
           :conditions => proc { ["ratings.user_id = ?", User.current_user.try(:id)] }
end

第 3 步

Item.includes(:current_user_ratings)

【讨论】:

  • 完美运行!一个小提示:您链接到的步骤 1 中的答案没有采取预防措施,即线程变量不在请求之间共享。我使用 gem request_store 来简化存储当前用户并确保它不会泄露。
  • 是的,看起来 thinpuma 服务器有问题。顺便说一句,gem 使用Thread.current 存储,他们在调用后将其清除。
  • 我已经修复了步骤 1 中建议的解决方案中的线程泄漏问题。
  • 我认为将current_user 带到模型层是错误的。这种情况有更好的解决方案。
【解决方案2】:

正如我最近在this blog post 中所写的,我建议您的情况如下:

items = Item.all
ActiveRecord::Associations::Preloader.new.preload(items, :ratings, Rating.where(user_id: user_id))

您可以使用预加载器的自定义范围并访问已由用户确定范围的items.each { |i| i.ratings }

【讨论】:

    【解决方案3】:

    看起来您基本上是在为has_many :through 关系建模:Item has_and_belongs_to_many User,Rating 是连接模型。您可以在Rails Guide to Active Record Associations 中阅读有关:through 关系的信息。

    如果是这种情况,我建议您使用 has_many :through 构建模型关系,如下所示:

    class Rating < ActiveRecord::Base
      attr_accessible :item_id, :user_id
      belongs_to :item
      belongs_to :user
    end
    
    class User < ActiveRecord::Base
      has_many :ratings
      has_many :rated_items, :through => :ratings
    end
    
    class Item < ActiveRecord::Base
      has_many :ratings
      has_many :rated_by_users, :through => :ratings, :source => :user
    end
    

    然后,假设您在数据库中有以下记录:

    $ sqlite3 db/development.sqlite3 'SELECT * FROM items';
    1|2013-03-22 03:21:31.264545|2013-03-22 03:21:31.264545
    2|2013-03-22 03:24:01.703418|2013-03-22 03:24:01.703418
    
    $ sqlite3 db/development.sqlite3 'SELECT * FROM users';
    1|2013-03-22 03:21:28.029502|2013-03-22 03:21:28.029502
    
    $ sqlite3 db/development.sqlite3 'SELECT * FROM ratings';
    1|1|1|2013-03-22 03:22:01.730235|2013-03-22 03:22:01.730235
    

    您可以使用以下语句请求所有项目及其关联的评级和用户实例:

    items = Item.includes(:rated_by_users)
    

    这将为您执行 3 个 SQL 查询:

    Item Load (0.1ms)  SELECT "items".* FROM "items" 
    Rating Load (0.2ms)  SELECT "ratings".* FROM "ratings" WHERE "ratings"."item_id" IN (1, 2)
    User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (1)
    

    可以通过在每个项目上调用#rated_by_users 关联方法来尝试访问对每个项目进行评分的用户:

    > items.map {|item| item.rated_by_users }
    => [[#<User id: 1, created_at: "2013-03-22 03:21:28", updated_at: "2013-03-22 03:21:28">], []] 
    

    【讨论】:

    • 它似乎工作正常!我想这也是this question 的答案吗?这似乎是堆栈溢出当前没有答案的问题(链接上的答案将所有关联对象加载到内存中并以编程方式过滤)
    • 我遇到了这个解决方案的问题 - 如果项目 A 有一个评级并且我正在查询项目 B,我没有得到项目 B。似乎评级的存在阻止了检索其他项目?
    • 如果用户 ID 为 1 的用户对项目 485 有评分,则对用户 ID 为 2 的过滤器的项目 485 的请求不会返回任何项目。没有结果:SELECT DISTINCT "items".id FROM "items" LEFT OUTER JOIN "ratings" ON "ratings"."item_id" = "items"."id" WHERE (items = 485) AND (ratings.user_id = 2 OR ratings.user_id IS NULL) 结果项目 485:SELECT DISTINCT "items".id FROM "items" LEFT OUTER JOIN "ratings" ON "ratings"."wine_id" = "items"."id" WHERE (items.id = 485) AND (ratings.user_id = 1 OR ratings.user_id IS NULL)
    • 我现在明白了。我已经用更好的解决方案完全重写了我的答案,请再看一遍
    • 但是您的解决方案没有过滤用户?如果不进行 N+1 查询,这可能吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-14
    • 1970-01-01
    相关资源
    最近更新 更多