Как вызвать исключение ActiveRecord :: Rollback и вместе вернуть значение?

У меня есть модель, в которой используется acts_as_nested_set форк, и я добавил к модели метод, позволяющий сохранить модель и переместить узел в набор за одну транзакцию. Этот метод вызывает метод проверки, чтобы убедиться, что ход действителен, который возвращает истину или ложь. Если проверка не удалась, я хочу, чтобы мой метод сохранения поднял ActiveRecord::Rollback для отката транзакции, но также вернул false вызывающей стороне.

Моя модель выглядит так:

class Category < ActiveRecord::Base
  acts_as_nested_set :dependent => :destroy, :scope => :journal

  def save_with_place_in_set(parent_id)
    Category.transaction do
      return false if !save_without_place_in_set

      if !validate_move parent_id
        raise ActiveRecord::Rollback and return false
      else
        place_in_nested_set parent_id
        return true
      end
    end
  end

  alias_method_chain :save, :place_in_set

  def validate_move(parent_id)
    # return true or false if the move is valid
    # ...
  end

  def place_in_nested_set(parent_id)
    # place the node in the correct place in the set
    # ...
  end
end

Однако, когда я вызываю save в ситуации, которая не удалась, транзакция откатывается, но функция возвращает nil:

>> c = Category.new(:name => "test") 
=> #<Category id: nil, name: "test" parent_id: nil, lft: nil, rgt: nil>
>> c.save_with_place_in_set 47
=> nil
>> c.errors.full_messages
=> ["The specified parent is invalid"]

person Daniel Vandersluis    schedule 29.06.2009    source источник


Ответы (3)


Вы можете сохранить значение, которое вы хотите вернуть из функции, в переменной и вернуть его вне блока транзакции. Например.

  def save_with_place_in_set(parent_id)
    return_value = false
    Category.transaction do
      if !save_without_place_in_set
        return_value = false
      elsif !validate_move parent_id
        return_value = false
        raise ActiveRecord::Rollback
      else
        place_in_nested_set parent_id
        return_value = true
      end
    end
    return return_value
  end

Изначально я установил для return_value значение false, так как единственный другой способ выйти из этого блока транзакции - это если один из других методов повысит ActiveRecord::Rollback, как я полагаю.

person Shadwell    schedule 29.06.2009
comment
СПАСИБО! Все еще актуально в Rails 3.2.8. Из документации мне не было ясно, что raise ActiveRecord::Rollback переходит на строку после окончания сделки. Похоже, что он просто проваливается, как будто откат вообще не прерывает выполнение программы. - person Mark Berry; 12.10.2012

Поскольку ActiveRecord::Rollback исключение обрабатывается, но не повторно вызывается ActiveRecord::Transaction, я мог бы переместить свой возврат из блока транзакции и, таким образом, вернуть значение после отката транзакции.

С небольшим рефакторингом:

def save_with_place_in_set(parent_id = nil)
  Category.transaction do
    return false if !save_without_place_in_set
    raise ActiveRecord::Rollback if !validate_move parent_id

    place_in_nested_set parent_id
    return true
  end

  return false
end
person Daniel Vandersluis    schedule 29.06.2009

Я знаю, что это может быть немного поздно, но я столкнулся с той же проблемой и только что обнаружил, что в блоке транзакции вы можете просто вызвать исключение и спасти его ... Rails неявно откатывает всю транзакцию. Так что в ActiveRecord :: Rollback нет необходимости.

Например:

def create
  begin
    Model.transaction do
      # using create! will cause Exception on validation errors
      record = Model.create!({name: nil})
      check_something_afterwards(record)
      return true
    end
  rescue Exception => e
    puts e.message
    return false
  end
end

def check_something_afterwards(record)
  # just for demonstration purpose
  raise Exception, "name is missing" if record.name.nil?
end

Я работаю с Rails 3.2.15 и Ruby 1.9.3.

person Matteo    schedule 13.06.2016
comment
Судя по всему, ActiveRecord :: Rollback - единственное исключение, не передаваемое за пределы блока. - person sakurashinken; 29.03.2019