Как инициировать действие после уничтожения ассоциации «многие ко многим»?

#ruby-on-rails #ruby #activerecord

#ruby-on-rails #ruby #activerecord

Вопрос:

В моем приложении Rails 4 у меня есть следующие модели:

 class Invoice < ActiveRecord::Base

  has_many  :allocations
  has_many  :payments, :through   => :allocations

end
 

 class Allocation < ActiveRecord::Base

  belongs_to :invoice
  belongs_to :payment

end
 

 class Payment < ActiveRecord::Base

  has_many    :allocations, :dependent => :destroy
  has_many    :invoices,    :through => :allocations

  after_save    :update_invoices
  after_destroy :update_invoices # won't work

  private

  def update_invoices
    invoices.each do |invoice|
      invoice.save
    end
  end

end
 

Проблема в том, что мне нужно обновлять invoice , когда один из его payments уничтожается.

Очевидно, что приведенный update_invoices выше обратный вызов никогда не может быть запущен, потому что в момент его вызова соединение с invoice уже было уничтожено.

Итак, как это можно сделать?

Прямо сейчас я делаю это в своем PaymentsController :

 def destroy
  @payment.destroy
  current_user.invoices.each do |invoice|
    invoice.save
  end
  ...
end
 

Однако, конечно, это очень дорого, потому что оно проходит через все, invoice что есть у a user .

Что может быть лучшей альтернативой этому?

Спасибо за любые отзывы.

Комментарии:

1. что именно вы пытаетесь сохранить в счете-фактуре? приведенный выше код, похоже, не загрязняет счет-фактуру

2. @blotto: Много чего. Например outstanding_amount , и payment_status .

Ответ №1:

Одним из решений было бы захватить счета-фактуры перед уничтожением экземпляра платежа. Однако это добавляет немного больше логики к контроллеру, но именно здесь возникает цель обоих действий (уничтожение оплаты и обновление счетов-фактур). Это также сокращает итерацию только до тех счетов, на которые повлиял уничтоженный платеж.

  def destroy
   invoices = @payment.invoices
   @payment.destroy
   invoices.each do |invoice|
     invoice.save
   end
   ...
 end
 

Предположительно, вы переопределяете save метод модели Invoice (или также выполняете обратный вызов), хотя я бы выбрал более явный метод для этого намерения. Например, removed_payment может быть метод для обработки этого конкретного сценария и обновления соответствующих атрибутов — outstanding_amount и payment_status и т. Д.

  def destroy
   invoices = @payment.invoices
   @payment.destroy
   invoices.map(amp;:removed_payment)
   ...
 end
 

Комментарии:

1. Это странно. Я не могу заставить работать ни одно из ваших предложений. Возможно ли, что invoices переменная каким-то образом изменяется при @payment уничтожении? Тем не менее, он все еще работает, когда я использую current_user.invoices .

2. попробуйте переименовать invoices в примере выше. хотя я сомневаюсь в его столкновении — возможно, вызовите его affected_invoices . попробуйте также вывести на консоль, чтобы убедиться, что платеж действительно имеет какие-либо счета. например p affected_invoices.count

3. Я понял. В моем первоначальном сообщении я присвоил :dependent => :destroy вызов в Payment модели. Таким образом, при @payment уничтожении больше нет связи между платежами и счетами, а счета, хранящиеся в переменной invoices , больше не могут быть доступны. Извините, я должен был включить эту строку в свой первый пост. Есть ли альтернативный подход к вашему?

Ответ №2:

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

 class Allocation < ActiveRecord::Base
  belongs_to :invoice
  belongs_to :payment

  after_destroy :update_invoice

  def update_invoice
    if destroyed?
      invoice.save!
    end
  end
end
 

Вот тестовый проект Rails 4.1 с тестами для этого:

https://github.com/infused/update_parent_after_destroy

Комментарии:

1. Вот и все! Большое вам спасибо.