在Rails 4中计算关联对象的有效方法

时间:2016-04-30 22:04:41

标签: ruby-on-rails ruby ruby-on-rails-4

我正在寻找一种方法来显示images的{​​{1}}数量,但通过category关联获得。我一直在has_many上读一点,但对实现还没有任何乐趣

counter_cache

控制器

class Category < ActiveRecord::Base
  has_many :image_categories
  has_many :images, through: :image_categories
end

class ImageCategory < ActiveRecord::Base
  # Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
  belongs_to :image
  belongs_to :category
end

class Image < ActiveRecord::Base
  # Categories
  has_many :image_categories, dependent: :destroy
  has_many :categories, through: :image_categories
end

查看

@categories  = Category.all

2 个答案:

答案 0 :(得分:2)

由于您正在寻找一种有效的方式,我建议使用counter_cache

以下是您的模型的外观:

class Category < ActiveRecord::Base
  has_many :image_categories
  has_many :images, through: :image_categories
end

class ImageCategory < ActiveRecord::Base
  # Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
  belongs_to :image, counter_cache: :category_count
  belongs_to :category, counter_cache: :image_count
end

class Image < ActiveRecord::Base
  # Categories
  has_many :image_categories, dependent: :destroy
  has_many :categories, through: :image_categories
end

您需要在image_count表格中添加categories字段,在category_count表格中添加images字段。

完成添加计数器和字段后,您需要重置计数器,以便使用数据库中已存在的记录的正确计数值更新字段。

Category.find_each { |category| Category.reset_counters(category.id, :images) }

Image.find_each { |image| Image.reset_counters(image.id, :categories) }

答案 1 :(得分:2)

counter_cache要考虑的几件重要事项:

  • 某些Rails方法可以在绕过回调的同时更新数据库(例如update_columnupdate_allincrementdecrementdelete_all等)和可能导致计数器缓存的值不一致。这同样适用于Rails之外的任何数据库更改。
  • 创建/删除子模型始终需要更新父模型。为确保计数器缓存的一致性,Rails在此更新期间使用额外的数据库事务。这通常不是问题,但如果频繁创建/删除子模型,或者父模型经常更新,则可能导致数据库死锁。 (http://building.wanelo.com/2014/06/20/counter-cache-a-story-of-counting.html

由于您在连接表中使用计数器缓存,因此会加剧这些问题。

如果您想进行有效的动态计数,那么它始终是最新的,那么您可以使用带有分组联接的自定义select

@categories = Category.select("categories.*, COUNT(DISTINCT images.id) AS images_count").joins(:images).group("categories.id")

<% @categories.find_each do |c| %>
  <li>
    <%= link_to '#', data: { :filter => '.' + c.name.delete(' ') } do %>
      <%= c.name %> (<%= c.images_count # <- dynamic count column %>)
    <% end %>
  </li>
<% end %>

如果您的外键被编入索引,这个分组连接的成本应该非常小,如果您需要images_count始终与真值保持一致,或者图像是经常被创造或破坏。从长远来看,这种方法也可能更容易维护。