Как спасти транзакцию модели и показать пользователю ошибку?

Итак, представьте, что у вас есть 2 модели, Person и Address, и только один адрес на человека может быть помечен как "Основной". Поэтому, если я хочу изменить основной адрес человека, мне нужно использовать транзакцию, чтобы отметить новый как основной и снять отметку с прежнего. И насколько я знаю, использование транзакций в контроллерах не очень хорошо, поэтому у меня есть специальный метод в модели, вот что у меня есть:

AddressesController < ApplicationController
 def update
  @new_address = Address.find(params[:id])
  @old_address = Address.find(params[:id2])
  @new_address.exchange_status_with(@old_address)       
 end
end

Модель:

class Address < ActiveRecord::Base
  def exchange_status_with(address)
    ActiveRecord::Base.transaction do
     self.save!
     address.save!
    end     
  end
end

Таким образом, если транзакция в методе модели завершается неудачно, мне нужно ее спасти и сообщить пользователю об ошибке, как мне это сделать? Есть ли способ заставить этот метод модели возвращать true или false в зависимости от успешности транзакции или нет, например, метод save?

Вероятно, я мог бы поместить эту транзакцию в контроллер и отобразить сообщение об ошибке в части спасения, но я думаю, что это неправильно, или я мог бы применить этот метод в обратном вызове, но представьте, что есть какая-то причина, почему я не могу этого сделать, Какая альтернатива?

PS не обращайте внимания на поиск экземпляров с параметрами id и id2, просто случайность, чтобы показать, что у меня есть 2 экземпляра

Ответы

Ответ 1

def exchange_status_with(address)
  ActiveRecord::Base.transaction do
   self.save!
   address.save!
  end
rescue ActiveRecord::RecordInvalid => exception
  # do something with exception here
end

FYI, исключение выглядит следующим образом:

#<ActiveRecord::RecordInvalid: Validation failed: Email can't be blank>

и

exception.message
# => "Validation failed: Email can't be blank"

Обратите внимание: вы можете изменить self.save! на save!


Альтернативное решение, если вы хотите сохранить свои активные ошибки модели:

class MyCustomErrorClass < StandardError; end

def exchange_status_with(address)
  ActiveRecord::Base.transaction do
   raise MyCustomErrorClass unless self.save
   raise MyCustomErrorClass unless address.save
  end
rescue MyCustomErrorClass
  # here you have to check self.errors OR address.errors
end