¿Cómo generar una excepción ActiveRecord:: Rollback y devolver un valor juntos?


Tengo un modelo que usa una bifurcación acts_as_nested_set, y he agregado un método al modelo para guardar el modelo y mover el nodo al conjunto en una transacción. Este método llama a un método de validación para asegurarse de que el movimiento es válido, lo que devuelve verdadero o falso. Si la validación falla, quiero que mi método save levante ActiveRecord::Rollback para revertir la transacción, pero también devuelva false al llamador.

Mi modelo se ve así:

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

Sin embargo, cuando llamo a save en una situación que fallaría, el la transacción es revertida pero la función devuelve 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"]
Author: Daniel Vandersluis, 2009-06-29

3 answers

Puede almacenar el valor que desea devolver de la función en una variable y devolverlo fuera del bloque de transacción. Por ejemplo,

  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

He establecido el return_value a false inicialmente ya que la única otra forma de salir de ese bloque de transacción es si uno de los otros métodos plantea ActiveRecord::Rollback Creo.

 27
Author: Shadwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-29 15:21:52

Debido a que la excepción ActiveRecord::Rollback se maneja, pero no se vuelve a subir por ActiveRecord::Transaction, podría mover mi retorno fuera del bloque de transacción, y por lo tanto devolver un valor después de que la transacción se revierte.

Con un poco de refactorización:

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
 10
Author: Daniel Vandersluis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-29 15:20:17

Sé que puede ser un poco tarde, pero me encontré con el mismo problema y me acabo de enterar, que dentro de un bloque de transacción que simplemente puede plantear una Excepción y rescatar que uno...Rails revierte implícitamente toda la transacción. Así que no hay necesidad de ActiveRecord:: Rollback.

Por ejemplo:

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

Estoy trabajando con Rails 3.2.15 y Ruby 1.9.3.

 -1
Author: Matteo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-13 13:57:37