如何在没有递归的情况下维护父级和子级的缓存属性?

时间:2013-03-07 05:16:15

标签: ruby-on-rails ruby postgresql

我有两个型号。由于必须进行连接,因此转换的索引视图很难呈现,就像进行工资核算一样。时钟具有很多属性,有必要将它们分开存储,以使轮班变得像我们需要的那样动态,包括旅行,休息和工作。

为了避免性能问题,第一个和最后一个时钟的日期时间(我知道这是这个属性的错误名称)存储在shift中作为started_at和finished_at以及shift的总持续时间。直到下一个时钟的持续时间存储在每个时钟中。


问题:保存时钟当前回调其Shift以重新计算其属性,然后保存它们,这会再次自动保存所有时钟。这不是无限期的,因为回调中的attribute_changed检查,但它会重新计算并保存多次。

问题:如果保存父级或子级,我如何确保所有模型都保持正确的缓存值而不会重新计算多次?


班次模型:

class Shift < ActiveRecord::Base
  has_many :clocks, autosave: true, dependent: :destroy

  attr_accessible :employee_id, :started_at, :finished_at, :duration, :clocks_attributes
  before_save :calculate_cached_attributes

  def calculate_cached_attributes
    clocks.sort_by!(&:datetime)

    self.started_at = clocks.first.datetime

    # assign finished_at if shift is finished and check that there isn't one finished clock
    if clocks.last.activity == :finished && clocks.first != clocks.last
      self.finished_at = clocks.last.datetime
    else
      self.finished_at = nil
    end

    following_clock = nil
    shift_duration = 0

    clocks.reverse.each do |clock|
      if following_clock
        clock.duration = following_clock.datetime - clock.datetime
      else
        clock.duration = 0
      end

      shift_duration += clock.duration unless clock.activity == :break
      following_clock = clock
    end

    self.duration = shift_duration
  end

  def update_cached_attributes!
    if clocks.any? # check if being called because of clocks dependent destroy callback
      save!
    end
  end

end


时钟模型:

class Clock < ActiveRecord::Base
  belongs_to :shift
  attr_accessible :shift_id, :datetime, :duration, :activity
  symbolize :activity, in: [:work, :travel, :break, :finished] # symbolize gem

  after_save :update_shift_attributes_after_save!
  after_destroy :update_shift_attributes_after_destroy!

  def update_shift_attributes_after_save!
    if (datetime_changed? || activity_changed?) && shift
      shift.update_cached_attributes!
    end
  end

  def update_shift_attributes_after_destroy!
    if shift
      shift.reload.update_cached_attributes!
    end
  end
end


需要(重新)计算的例子:

shift.clocks.last.update_attribute(:datetime, Time.now)

# shift may no longer be complete if activity changed from :finished
shift.clocks.last.update_attribute(:activity, :work)

shift.clocks.last.datetime = Time.now
shift.save!

Shift.create!(employee_id: 1, clocks_attributes: [....] )

shift.clocks.last.destroy


我尽可能地简化代码以突出显示问题。让我知道是否有遗漏,或者是否有一种完全不同的方式来做到这一点。

1 个答案:

答案 0 :(得分:1)

我认为您需要避免在需要更新缓存属性时调用Clock#update_shift_attributes_after_save!回调。看来,在这种情况下,Clock上唯一更新的缓存属性为duration,因此以下内容可能很简单

after_save :update_shift_attributes_after_save!, :if => -> { changed.include?("duration") }

最好让Clock模型负责更新自己的缓存属性。这就是我这样做的方法。

# Clock
CACHED_ATTRIBUTES = [:duration].freeze

def update_cached_attributes!(attrs = {})
  self.class.where(:id => id).update_all(attrs.slice(*CACHED_ATTRIBUTES))  # update_all will NOT trigger callbacks.
                                                                           # This only works for persisted records. You'd need to add logic if you must handle new records.
end

# In Shift#calculate_cached_attributes
clocks.reverse.each do |clock|
  clock.update_cached_attributes!(:duration => following_clock ? (following_clock.datetime - clock.datetime) : 0)