業務ロジックを実装するにはTransactionは必要不可欠である。**Railsドキュメント**によるとRailsのTransactionは下記の構文がある。
User.transaction do
# Something todo
end
render :text => 'success'
rescue => e
render :text => e.message
ActiveRecord::Base.transaction do
# Something todo
end
render :text => 'success'
rescue => e
render :text => e.message
しかし、もし上記構文をif...end
で囲むと、まさかのsyntax error
が発生します
現象
- 成功する構文
def foo
User.transaction do
# Something todo
end
p "++AAAAAAAAAAA"
p "Succeed"
p "--AAAAAAAAAAA"
render nothing: true
rescue => e
p "++BBBBBBBBBBB"
p "Failed"
p e
p "--BBBBBBBBBBB"
render nothing: true
end
結果:
...
(0.1ms) BEGIN
(0.1ms) COMMIT
"++AAAAAAAAAAA"
"Succeed"
"--AAAAAAAAAAA"
Rendered text template (0.1ms)
...
- 失敗する構文
def foo
if true
User.transaction do
# Something todo
end
p "++AAAAAAAAAAA"
p "Succeed"
p "--AAAAAAAAAAA"
render nothing: true
rescue => e
p "++BBBBBBBBBBB"
p "Failed"
p e
p "--BBBBBBBBBBB"
render nothing: true
end
end
結果:
...
SyntaxError (.../foo/app/controllers/foo_controller.rb:96: syntax error, unexpected keyword_rescue, expecting keyword_end
rescue => e
^):
app/controllers/foo_controller.rb:96: syntax error, unexpected keyword_rescue, expecting keyword_end
...
もちろん、ActiveRecord::Base.transaction
の構文も同じエラーが発生した。ただし、rescue
以降の構文を削除すると、正しく動く。
解決策
色々模索した結果、下記の構文にたどり着いた。
伝統的なbegin...rescue...end
構文でエラーをキャッチ
def foo
if true
begin
User.transaction do
# Something todo
# raise ActiveRecord::RecordInvalid.new(User.new)
end
p "++AAAAAAAAAAA"
p "Succeed"
p "--AAAAAAAAAAA"
render nothing: true
rescue => e
p "++BBBBBBBBBBB"
p "Failed"
p e
p "--BBBBBBBBBBB"
render nothing: true
end
end
end
成功した場合:
...
(0.2ms) BEGIN
(0.1ms) COMMIT
"++AAAAAAAAAAA"
"Succeed"
"--AAAAAAAAAAA"
Rendered text template (0.0ms)
...
失敗した場合:
...
(0.1ms) BEGIN
(0.2ms) ROLLBACK
"++BBBBBBBBBBB"
"Failed"
#<ActiveRecord::RecordInvalid: Failed!: >
"--BBBBBBBBBBB"
Rendered text template (0.0ms)
...
begin...end
構文でTransactionを囲む
def foo
if true
begin
User.transaction do
# Something todo
# raise ActiveRecord::RecordInvalid.new(User.new)
end
p "++AAAAAAAAAAA"
p "Succeed"
p "--AAAAAAAAAAA"
render nothing: true
rescue => e
p "++BBBBBBBBBBB"
p "Failed"
p e
p "--BBBBBBBBBBB"
render nothing: true
end
end
end
成功した場合:
...
(0.2ms) BEGIN
(0.1ms) COMMIT
"++AAAAAAAAAAA"
"Succeed"
"--AAAAAAAAAAA"
Rendered text template (0.0ms)
...
失敗した場合:
...
(0.1ms) BEGIN
(0.2ms) ROLLBACK
"++BBBBBBBBBBB"
"Failed"
#<ActiveRecord::RecordInvalid: Failed!: >
"--BBBBBBBBBBB"
Rendered text template (0.0ms)
...
UPDATE 2018-11-05
@scivola さんが指摘した通り、rescue
キーワードはdef..end
かbegin...end
の直下でなければならないそうだ。ゆえに下記構文もリーガル的である。
def foo
if true
User.transaction do
# Something todo
# raise ActiveRecord::RecordInvalid.new(User.new)
end
p "++AAAAAAAAAAA"
p "Succeed"
p "--AAAAAAAAAAA"
render nothing: true
end
rescue => e
p "++BBBBBBBBBBB"
p "Failed"
p e
p "--BBBBBBBBBBB"
render nothing: true
end
余談
余談ですが、Transactionの中でraise ActiveRecord::Rollback
でROLLBACKを強制発生させる場合、Railsでは「成功」とみなし、rescue
に通らない現象が確認できた。
参考