16

我有一个活动记录对象树,例如:

class Part < ActiveRecord::Base
  has_many :sub_parts, :class_name => "Part"

  def complicated_calculation
    if sub_parts.size > 0
      return self.sub_parts.inject(0){ |sum, current| sum + current.complicated_calculation }
    else
      sleep(1)
      return rand(10000)
    end
  end

end

每次重新计算 complex_calculation 成本太高。所以,我需要一种缓存值的方法。但是,如果任何部分发生更改,则需要使其缓存以及其父级和祖父级的缓存等无效。

作为一个粗略的草稿,我创建了一个列来将缓存的计算保存在“parts”表中,但这闻起来有点烂。似乎应该有一种更简洁的方法来缓存计算值,而不是将它们塞在“真实”列旁边。

4

5 回答 5

28

我建议使用关联回调。

class Part < ActiveRecord::Base
  has_many :sub_parts,
    :class_name => "Part",
    :after_add => :count_sub_parts,
    :after_remove => :count_sub_parts

  private

  def count_sub_parts
    update_attribute(:sub_part_count, calculate_sub_part_count)
  end

  def calculate_sub_part_count
    # perform the actual calculation here
  end
end

又好又容易=)

于 2008-10-08T07:28:41.260 回答
8
  1. 您可以将实际缓存的值填充到 Rails 缓存中(如果您需要分发它,请使用 memcached)。

  2. 困难的一点是缓存到期,但缓存到期并不常见,对吧?在这种情况下,我们可以依次循环遍历每个父对象并删除其缓存。我在您的类中添加了一些 ActiveRecord 魔法,以使获取父对象本身变得简单——您甚至不需要接触您的数据库。记得Part.sweep_complicated_cache(some_part)在你的代码中适当地调用——你可以把它放在回调等中,但我不能为你添加它,因为我不明白什么时候complicated_calculation改变。

    class Part < ActiveRecord::Base
      has_many :sub_parts, :class_name => "Part"
      belongs_to :parent_part, :class_name => "Part", :foreign_key => :part_id
    
      @@MAX_PART_NESTING = 25 #pick any sanity-saving value
    
      def complicated_calculation (...)
        if cache.contains? [id, :complicated_calculation]
          cache[ [id, :complicated_calculation] ]
        else
          cache[ [id, :complicated_calculation] ] = complicated_calculation_helper (...)
        end
      end
    
      def complicated_calculation_helper
        #your implementation goes here
      end
    
      def Part.sweep_complicated_cache(start_part)
        level = 1  # keep track to prevent infinite loop in event there is a cycle in parts
        current_part = self
    
        cache[ [current_part.id, :complicated_calculation] ].delete
        while ( (level <= 1 < @@MAX_PART_NESTING) && (current_part.parent_part)) {
         current_part = current_part.parent_part)
         cache[ [current_part.id, :complicated_calculation] ].delete
        end
      end
    end
    
于 2008-10-08T06:01:48.563 回答
2

有一个类似于计数器缓存的字段。例如: order_items_amount 并将其作为缓存的计算字段。

使用 after_save 过滤器重新计算任何可以修改该值的字段。(包括唱片本身)

编辑:这基本上就是你现在所拥有的。除非您想将缓存的计算字段存储在另一个表中,否则我不知道任何更清洁的解决方案。

于 2008-10-08T01:42:40.037 回答
2

使用 before_save 或 ActiveRecord Observer 是确保缓存值是最新的方法。我会使用 before_save 然后检查你在计算中使用的值是否真的改变了。这样,如果不需要,您就不必更新缓存。
将值存储在数据库中将允许您缓存多个请求的计算。另一种选择是将值存储在内存缓存中。您可以为该值创建一个特殊的访问器和设置器,以检查内存缓存并在需要时对其进行更新。
另一个想法:是否存在您将更改其中一个模型中的值并需要在保存之前更新计算的情况?在这种情况下,每当您更新模型中的任何计算值时,您都需要弄脏缓存值,而不是使用 before_save。

于 2008-10-08T03:20:49.230 回答
1

我发现有时有充分的理由对数据库中的信息进行非规范化。我正在开发的应用程序中有类似的东西,只要集合发生变化,我就会重新计算该字段。

It doesn't use a cache and it stores the most up to date figure in the database.

于 2008-10-13T16:57:22.640 回答