[Rails] IF文で囲まれたtransactionをrescue

業務ロジックを実装するにはTransactionは必要不可欠である。**[Railsドキュメント](http://railsdoc.com/references/transaction)**によるとRailsのTransactionは下記の構文がある。

```ruby
  User.transaction do
    # Something todo
  end
  render :text => 'success'
  rescue => e
  render :text => e.message
```

```ruby
ActiveRecord::Base.transaction do
  # Something todo
end
  render :text => 'success'
rescue => e
  render :text => e.message
```

しかし、もし上記構文を`if...end`で囲むと、まさかの`syntax error`が発生します

## 現象
+ *成功する構文*

```ruby
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
```

*結果:*

```sh
...
   (0.1ms)  BEGIN
   (0.1ms)  COMMIT
"++AAAAAAAAAAA"
"Succeed"
"--AAAAAAAAAAA"
  Rendered text template (0.1ms)
...
```

+ *失敗する構文*

```ruby
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
```

*結果:*

```sh
...
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`構文でエラーをキャッチ

```ruby
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
```

*成功した場合：*

```sh
...
   (0.2ms)  BEGIN
   (0.1ms)  COMMIT
"++AAAAAAAAAAA"
"Succeed"
"--AAAAAAAAAAA"
  Rendered text template (0.0ms)
...
```

*失敗した場合：*

```sh
...
   (0.1ms)  BEGIN
   (0.2ms)  ROLLBACK
"++BBBBBBBBBBB"
"Failed"
#<ActiveRecord::RecordInvalid: Failed！: >
"--BBBBBBBBBBB"
  Rendered text template (0.0ms)
...
```

### `begin...end`構文でTransactionを囲む
```ruby
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
```

*成功した場合：*

```sh
...
   (0.2ms)  BEGIN
   (0.1ms)  COMMIT
"++AAAAAAAAAAA"
"Succeed"
"--AAAAAAAAAAA"
  Rendered text template (0.0ms)
...
```

*失敗した場合：*

```sh
...
   (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`の直下でなければならないそうだ。ゆえに下記構文もリーガル的である。

```ruby
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`に通らない現象が確認できた。

## 参考
https://qiita.com/Yinaura/items/24997f0005e187f54210