もっとControllerから分離してtestableに

関心

Rails の Controller にメソッドを生やすとテストしにくい。できるだけ分けたい。1

とりあえず思いついてたり実現できていたりすることをメモ。

Controllerの基本

まずは概念の整理から。

のうえで、どうしても Rails で残りがちなものが以下かなという気がしている。これらをどうバラすかのメモ。

※ Strong Parameters についてもいたずらに private メソッドが増えるのはよくないと感じていて、そもそも dry-validation など代替の手法があるが、validation の話はそれだけでそれなりに大きいので今回は除外。

before_actionをバラしたい

  • 副作用アリなら module へバラして mixin
    • 関連するメソッド群を一つの module に固めておく
    • 極力 controller に依存する処理を集約して単独でテストできる部分を増やすように
    • controller に依存する部分は普通に適当な controller にincludeして書けばよい
  • 副作用ナシで request を叩き落とすだけなら constraint へ

rescue_fromをバラしたい

  • V, Cのいずれにも依存しない副作用は C で rescue せずにRack middleware へ逃す
    • ActiveRecord 周りとか
  • View の layout や content に依存しない response も Rack middleware で可能

rescue_fromをclassでバラすこともできるけど

rescue_from をできるだけ testable にするために constraint のように独立した class にできないかと試行錯誤したが、できることはできるけど、あんまりメリットない気がする。せっかくなので一応書き出しておくけど。

rescue_from には block か Method オブジェクトに変換できる Symbol しか与えることができない。後者も最終的には Proc オブジェクトになるので、結局 Proc オブジェクトしか与えることができない。これはコードを追うと中で class を見て場合分けしているので避けようがない。そこで以下のような class を作ったとしても、

class Handler
  def call(exception)
  end
end

実際には以下のように Proc オブジェクトに変換して with に与える必要がある。

rescue_from Exception, with: Handler.new.method(:call).to_proc

だいぶおおげさではあるが、よほど複雑な処理をするのであればこういう書き方も可能。ただし、self が合わないのでそのままでは実行時の controller の中の値にはタッチできない。「そのままでは」がキモで、ちゃんと辿れば可能。

exception.bindings.first.receiver

とやれば実際に rescue した controller インスタンスが取得できるので、ここから request オブジェクトなどを参照できる。

まぁなかなかこんな方法使わないかなぁという気もするが、Exception Handler を class ベースにして(例えば特別なログを出力する、何らかのレポートサービスに通知するなど)継承したい実装を用意しておくというのは一つの手法としてアリかもしれない。単に rescue しただけで片付けてしまうと事実が消えてしまうが、その記録を毎度毎度ゼロから書くのもバカバカしいので。

  1. Rails は 1 Action : 1 Method になるのである程度以上の複雑さを持った場合に安易に private メソッドが増えやすい。private メソッドはテストしにくいので悪。Hanami 方式なら 1 Action : 1 Class なのでいいんだけど。 

More