В Rails как вычислить значение на основе набора дочерних записей и сохранить его в родительской записи - PullRequest
2 голосов
/ 05 октября 2009

У меня есть счета, которые состоят из пунктов счета. У каждой позиции есть прибыль, и я хочу суммировать ее и сохранить общую прибыль на уровне счета.

Ранее я выполнял эти вычисления на лету, но для повышения производительности мне теперь нужно сохранить это значение в базе данных.

class InvoiceItem < ActiveRecord::Base
  belongs_to :invoice
end

class Invoice< ActiveRecord::Base
  has_many :invoice_items
  def total_profit
    invoice_items.sum(:profit)
  end
end

Я хочу, чтобы total_profit всегда был корректным, поэтому его необходимо обновлять при добавлении, редактировании или удалении элемента счета Также total_profit, вероятно, следует защитить от непосредственного редактирования.

Ответы [ 3 ]

4 голосов
/ 05 октября 2009

Вы можете попробовать методы обратного вызова «после создания», «после сохранения» и «до уничтожения», чтобы добавить или вычесть сумму из общей прибыли родителей. Таким образом, ваш родительский объект будет обновляться только в том случае, если будут внесены изменения в элементы счета.

С уважением, Джо

редактирование:

чтобы дать вам несколько непроверенных подсказок псевдокода:

class InvoiceItem < ActiveRecord::Base
  belongs_to :invoice
  before_destroy { |item| item.invoice.subtract(item.amount) }
  after_create   { .. }
  after_save     { .. }
end
3 голосов
/ 06 октября 2009

Джо на правильном пути, но его ответ не решает все ваши проблемы. Вам также необходимо установить атрибут total_profit в Invoice. Сначала вам нужно добавить поле с соответствующей миграцией. Тогда вы захотите защитить этот атрибут с помощью

attr_protected :total_profit

Или еще лучше:

attr_accessible ALL_NON_PROTECTED_ATTRIBUTES

Также не помешает установить способ принудительного пересчета total_profit. В конце концов у вас будет что-то вроде этого:

class Invoice < ActiveRecord::Base
  has_many :invoice_items

  attr_protected :total_profit

  def total_profit(recalculate = false)
    recalculate_total_profit if recalculate
    read_attribute(:total_profit)
  end

  private

    def recalculate_total_profit
      new_total_profit = invoice_items.sum(:profit)
      if new_total_profit != read_attribute(:total_profit)
        update_attribute(:total_profit, new_total_profit)
      else
        true
      end
    end

end

Конечно, это может быть немного излишним для вашего конкретного приложения, но, надеюсь, это даст вам некоторые идеи о том, что может быть лучше для вас.

0 голосов
/ 06 октября 2009

Таким образом, мое решение было таким, как Питер предложил добавить total_proft в Invoices с соответствующей миграцией.

Затем, как предложил Йоханнес, я использовал ActiveRecord :: Callbacks на моей дочерней модели:

class InvoiceItem < ActiveRecord::Base
  belongs_to :invoice

  def after_save
    self.update_total_profit
  end
  def after_destroy
    self.update_total_profit
  end
  def update_total_profit
    self.invoice.total_profit = self.invoice.invoice_items.sum(:profit)
    self.sale.save
  end

end

class Invoice< ActiveRecord::Base
  has_many :invoice_items
  def total_profit
    invoice_items.sum(:profit)
  end
end

ОБРАТИТЕ ВНИМАНИЕ: По какой-то причине приведенный выше код не работает при совместном создании счета-фактуры и счета-фактуры. Все начинается нормально, оператор INSERT SQL запускается первым для Invoice. Затем с новым ID счета-фактуры можно сохранить запись InvoiceItem. Однако после этого мой код запускает запрос ...

SELECT sum(`invoice_items`.profit) AS sum_profit 
FROM `invoice_items` 
WHERE (`invoice_items`.invoice_id = NULL) 

По какой-то причине invoice_id имеет значение NULL, хотя он только что использовался для вставки invoice_item.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...