acts_as_nested_set
フォークを使用するモデルがあり、モデルを保存して1回のトランザクションでノードをセットに移動するメソッドをモデルに追加しました。このメソッドは、検証メソッドを呼び出して、移動が有効であることを確認します。これにより、trueまたはfalseが返されます。検証が失敗した場合、saveメソッドで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"]
関数から返される値を変数に格納し、それをトランザクションブロックの外に返すことができます。例えば。
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
そのトランザクションブロックから抜け出すことができる唯一の他の方法は、他のメソッドの1つがActiveRecord::Rollback
を発生させる場合であるため、最初はreturn_valueをfalseに設定しました。
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
少し遅れるかもしれませんが、同じ問題が発生し、トランザクションブロック内で例外を発生させて、その例外をレスキューできることがわかりました... 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で作業しています。