1

以下に2つのモデルがあります。シフトのインデックスビューは、結合を行う必要があるため、給与計算を行う場合と同様にレンダリングするのが非常に困難です。時計には多くの属性があり、シフトを必要なだけ動的にし、移動、休憩、仕事を含めるには、時計を個別に保存する必要があります。

パフォーマンスの問題を回避するために、最初と最後のクロックの日時(これはこの属性の不適切な名前です)は、シフトの合計期間だけでなく、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 に答える