1

我在下面有两个模型。由于必须进行联接,因此很难像进行工资计算一样呈现班次的索引视图。时钟有很多属性,有必要将它们分开存储,以使轮班在我们需要时具有动态性,包括旅行、休息和工作。

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


问题:保存时钟当前会回调其 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


我试图尽可能简化代码以突出问题。让我知道是否缺少某些东西,或者是否有完全不同的方法可以更好地做到这一点。

4

1 回答 1

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)
于 2013-03-07T17:19:09.330 回答