2012年9月11日火曜日

model の module 化

巨大な rails アプリを作っていると、すべての業務ロジックをモデルに書くというrails標準の考え方はすぐに破たんする。最もコアのモデルだと数千行を簡単に超えてしまうためである。長すぎるメソッドが良くないのと同様に、長すぎるクラスもよいものではない。

そこで module やラッパーを使って、特定の場面でのみ必要な機能を付加する、ということを考える。通常の rails の考え方では、モデルにはそのモデルに関係したビジネスロジックを書く、ということになっているが、そのモデルのビジネスロジックのうち、特定の場面でのみ必要なロジックを「場面xモデル」の単位でコードを切り出してmodule化しておき、必要なときにそれを include するということをしている。

例えば入力画面でのみ必要なメソッド、というのはよくあるはず。確認画面に表示するためのメソッドだったり、入力フォームを構築するために必要なちょっとしたメソッドなど。このような、本来のモデルのビジネスロジックとは無関係な、特定の機能のためのロジックを models/user/input.rb などに追いやって、実行時に都度includeすることでコードをすっきりさせる。

このような考えを進めたものに DCI というものがある。モデルにすべてのロジックをおくことはせずにモデルxビジネスロジックごとにモジュールを用意しておいて、ビジネスロジックが実行されるときにモジュールが自動的に読み込まれるようにする、という仕組みだ。最近 rails 界隈でもはやっている。

切り出すモジュールとしてよくあるのは

  • 表示用のメソッド。これがたぶん一番多い。たとえば住所を表示する際に郵便番号から番地までひとつの文字列として出したい場合や、それぞれ別々に改行して出したい場合、市区町村以下しか必要ない場合、などいろいろある。そういう細かい違いだけのメソッドが大量に混ざるとコードがどんどんごちゃごちゃするし、どこで何が使われているのか把握できなくなる。exhibit オブジェクトとか presenter とか言う。
  • validation など編集時だけに必要なメソッド。
  • 各種ビジネスロジックごとのメソッド。バッチファイルでのみ必要なメソッドなどを突っ込む
モジュールを include するのをどうするかという問題がある。モデル単体であれば簡単だが関連上のモデル全部に対して extend していくのはなかなか面倒だ。

自分は traverse というオブジェクトツリーをたどるライブラリを自作しており、これを使っている。

user.acts_as! :list_presenter, [ :emails, :tels, :user_groups => :representative ]

などとして一覧表示用のメソッドを追加する。最後の引数は Arel の includes などに渡す hashと同じ形式のものを渡す。そうすると Arel と同様にアソシエーションを解釈して関連にも module を extend していってくれる。





0 件のコメント:

コメントを投稿