around_actionで引数ありのメソッドをlambdaで指定する

## 概要

`around_action`に引数を持つメソッドを指定する際に無限に詰まったので同じ状況で困っている人のために残しておく。

指定方法だけ知りたい人は`解決`までジャンプしてもらえれば書いてある。

## 状況

なぜ詰まったかというと、まずrailsでAction Controllerのフィルターを使用する際に引数を持つメソッドを指定する場合、ブロックを渡す必要がある。下記のようにしないとエラーが出てしまう。

```rb
class HogeController < ApplicationController
  # 引数なしの場合
  before_action :fuga
  # 引数ありの場合
  before_action -> { fuga(x, y) }
end
```

`before_action`と`after_action`を使用する場合は上記の記述で問題ないけど、`around_action`を使用する場合は1つ問題が発生する。

Railsガイドを読むと下記のように記述がある。
>「around系」フィルタを使う場合は、フィルタ内のどこかで必ずyieldを実行して、関連付けられたアクションを実行してやる義務が生じます。これはRackミドルウェアの動作と似ています。

https://railsguides.jp/action_controller_overview.html#after%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%81%A8around%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF

引数なしの`around_action`を使用する場合は下記のように記述する。

```rb
class HogeController < ApplicationController
  around_action :fuga, only: :show

  private

  def fuga
    ActiveRecord::Base.transaction do
      begin
        # ここでonlyに指定されたshowアクションが実行される
        yield
      ensure
        raise ActiveRecord::Rollback
      end
    end
  end
end
```

つまり、`around_action`を使用する場合はメソッドに対して(上記の場合は`fuga`)ブロックを渡す必要がある。引数なしのメソッドなら何も問題ないが、引数を持っている場合は問題がある。

```rb
class HogeController < ApplicationController
  # -> { fuga(x, y) }にブロックを渡している
  around_action -> { fuga(x, y) }, only: :show

  private

  def fuga(x, y)
    ActiveRecord::Base.transaction do
      begin
        # ここでブロックを受け取る必要があるが、-> { fuga(x, y) }にブロックが渡されていてfugaにブロックが渡されていない
        yield
      ensure
        raise ActiveRecord::Rollback
      end
    end
  end
end
```

`around_action`で設定しているメソッド内で`yield`を使用するのでメソッドに対してブロックを渡す必要があるが、引数を渡すために`-> { fuga(x, y) }`としているため渡せないという問題が発生する。

ここで`-> { fuga(x, y) }`から`fuga`にブロックを渡そうとしても渡すことができない。`Proc.new { fuga(x, y)}`とか`Proc.new { fuga(x, y, &block) }`やってもダメ。

## ブロックを渡す方法

`self.send(params[:action])`を使用する。
参考：https://stackoverflow.com/questions/34949830/rails-4-around-action-with-parameter

```rb
class HogeController < ApplicationController
  # -> { fuga(x, y) }にブロックを渡している
  around_action -> { fuga(x, y) }, only: :show

  private

  def fuga(x, y)
    ActiveRecord::Base.transaction do
      begin
        # 変更箇所
        # ブロックを渡せないからアクションを呼ぶことで解決している
        self.send(params[:action])
      ensure
        raise ActiveRecord::Rollback
      end
    end
  end
end
```

Rubyの黒魔術の説明によく出てくる`send`メソッド。ここでもその力を遺憾無く発揮してる。
`send`がどういうものかはリファレンスを見て貰えばわかると思う。
参考：https://docs.ruby-lang.org/ja/latest/method/Object/i/send.html

これで`around_action`に設定したメソッドを実行することはできる。
しかし、今度はコントローラのアクション終了時に自動でviewに遷移してくれなくなってしまった。調べても分からなかったが、`self.send`でアクションを実行するとその後のレンダリングが行われないらしい。

そのためこちらで本来Railsで行っているレンダリング処理を記述する必要がある。


# 解決

```rb
class HogeController < ApplicationController
  around_action -> { fuga(x, y) }, only: :show

  private

  def fuga(x, y)
    ActiveRecord::Base.transaction do
      begin
        self.send(params[:action])
      ensure
        raise ActiveRecord::Rollback
      end
    end
    # 変更箇所
    default_render unless performed?
  end
end
```

`default_render unless performed?`という処理を追加した。これによってコントローラのアクションで`render`が定義されていない場合にアクション名に対応するviewを自動的にレンダリングしてくれる。

`render`が行われると`response_body`にデータが設定されるが、`preformed?`は`response_body`でデータがあるか判定してくれるらしい。
https://apidock.com/rails/v6.1.3.1/AbstractController/Base/performed%3F

`default_render`はコントローラのアクション名に対応するviewを自動でレンダリングしてくれるRails
が定義しているメソッド。

そのためコントローラのアクションで明示的に`render`していても、`unless performed?`を設定していることで`AbstractController::DoubleRenderError`が発生するのを防いでくれる。

Railsがどうやってテンプレートをレンダリングしているか調べる際に参考にした記事
参考：https://qiita.com/ngtk/items/cd406961cf62531a81c5