在不使用连接的情况下将相关数据添加到ActiveRecords

时间:2015-09-01 00:52:36

标签: ruby-on-rails join scope rails-activerecord

我的Rails 4应用拥有User型号,Link型号和Hit型号。每个User都有很多Link个,每个Link都有很多Hit个。有时,我希望显示User的{​​{1}}个列表,其中包含Link个。{/ p>

显而易见的方法是循环链接并在每个链接上调用Hit,但这会产生N + 1个查询。相反,我编写了一个连接link.hits.count表的范围:

hits

这有效地为每个scope :with_hit_counts, -> { joins("LEFT OUTER JOIN hits ON hits.link_id = links.id").select('links.*', 'count(hits.link_id) AS hit_count').group("links.id") } 添加了一个虚拟hit_count属性,该属性在单个查询中计算。奇怪的是,它似乎是一个单独的查询加载链接,而不是实际在同一个查询中完成:

Link

不幸的是,随着SELECT COUNT(*) AS count_all, links.id AS links_id FROM "links" LEFT OUTER JOIN hits ON hits.link_id = links.id WHERE "links"."user_id" = $1 GROUP BY links.id ORDER BY "links"."domain_id" ASC, "links"."custom_slug" ASC, "links"."id" ASC ; 表的增长,这已成为一个缓慢的查询。 hits表示查询正在使用索引连接所有匹配及其匹配链接,然后通过顺序扫描将链接缩小到具有正确EXPLAIN的链接;这似乎是它缓慢的原因。但是,如果我们已经单独加载链接列表 - 我们就是 - 根本没有实际需要加入链接表。我们可以获取用户的链接ID列表,然后仅使用user_id在hits表上进行查询。

将此作为单独的查询编写很容易,并且它可以快速运行:

hits.link_id IN (list of IDs)

问题是,我无法弄清楚如何让ActiveRecord作为Hit.where(link_id: @user.links.ids).group(:link_id).count 模型上的范围执行此操作,以便每个Link都具有Link属性,我可以使用,以便我可以使用生成的返回值作为将其他查询链接到它的能力的关系。有什么想法吗?

(我知道ActiveRecord的hit_count功能,但我不想在这里使用它 - counter_cache由一个单独的非Ruby系统插入,并修改该系统以更新反缓存会有点痛苦。)

1 个答案:

答案 0 :(得分:0)

正如trh所提到的,常见的方法是在link.rb中添加一个hits_count然后你可以快速查询和排序。你只需要让它们保持同步。如果它确实是基本计数,那么您可以使用基本的rails计数器缓存。这将在创建和销毁时递增和递减。

class AddCounterCacheToLink < ActiveRecord::Migration
  def up
    add_column :links, :hits_count, :integer, :default => 0  

    Link.reset_column_information  
    Link.all.each do |l|  
      l.update_attribute :hits_count, l.hits.size
    end 
  end
end

然后在模型中

class Hit < ActiveRecord::Base

  belongs_to :link, :counter_cache => true   #this will trigger the rails magic

如果你有更复杂的东西,你可以写自己的,这是微不足道的。