Railsでドメインロジックをモデルに書くのは、果たして良い設計なのだろうか?

Railsの”モデル”は責務を持ちすぎでは無いだろうか?

RailsのActive Recordはエンティティ(テーブル)とロジックを1対1に縛って、1枚岩の”モデル”と呼ぶオブジェクトとして扱っている。単純な場合は1対1で、多少の複雑さであればActive Recordは非常に便利なAPIを提供してくれて、開発効率をグンと上げてくれる。

しかし、ある程度の複雑度が出てくるとドメインロジックはエンティティと1対1に対応しなくなるし、それを扱う側(Controllerなど)によってロジックが変わることもある。

単純にFat modelに従えば、どちらか一方のモデルのメソッドとしてロジックを隠蔽する。そのため、複数のモデルの操作をしているにも関わらず、様々なデータ操作の責務を1つのモデルが抱えてしまう。これをControllerやViewで扱う時には、操作するオブジェクトが直感的で無く扱いづらい。

(例えば、複数のエンティティのデータを複合した集計なのに、一方のモデルのメソッドから取ってくる場合、1つのフォームから複数のモデルに対してアクセスする場合。)

かといってControllerに書いたら、DRYではない。

 

この1枚岩になっているエンティティとドメインロジックを、切り離してそれぞれ別のオブジェクトにした方が良いのではないだろうか?

この問題についてずっと考えてきた個人的な結論としては、ModelとControllerの中間層にオブジェクトが介在して、このオブジェクトがビジネスロジックの責務を持つのが良いのではないか?と考えてきた。

ふとSQLアンチパターンを呼んだところ、まさにActive Recordパターンのこの問題について言及している章があった。(24章:マジックビーンズ)SQLアンチパターンで提示されている解決策としても、エンティティ=アクティブレコード, ドメインロジック=ドメインモデルとして分離することを推奨していた。

 

ところで、1枚岩のActive Recordの機能をバラして使おうとする(Active Modelなど)とActive RecordやRailsの恩恵が犠牲になってしまう。(文字通りレールから逸れている)
ここをうまく切り抜けるにはどうしよう…というところ。うまくRailsのAPIを吸収して、ドメインモデルを分離したい。
この辺りに取り組んだ事例などが知りたい…

追記(12/11)

a_suenamiさんから、アドバイス記事をいただきました!

Rails でドメインロジックの実装方法まとめ

他にも参考になる記事があったので紹介します。

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

 

has_manyで関連元のオブジェクトに依存した条件を付けたいとき

Railsのモデルで、has_manyの関連で関連先を絞り込むために条件を付けたいときがありますよね。

Rails Guidesにもあるように、 第2引数でlambda式を渡して条件を付けることができます。

has_many :users, -> { where("created_at > ?", Time.now) }

こんな感じで。

where以外にorderなども使えるので、予めソートしておきたい場合などに便利です。

これぐらいであれば、Rails Guidesや書籍、他のサイトにも載っている情報だと思います。

 

しかし、こんなシーンではどうしますか?

「関連元のオブジェクトのフィールドに依存した条件を付けたい」

つまり、has_manyを書いているモデル自身のフィールド値を、このlambda式内に伝える方法です。

実は、このやり方はRails Guidesでも紹介されていない上に、あまりググっても出てこない情報です。(私調べ)

Continue reading

ActiveRecordを使わずにI18nを使うときの注意

Railsでデータベースに関連付かないモデルを作成するときは,ActiveModelだけを使いますよね.

このときに注意したいのが,I18nのロケールファイルの記述です.

ついうっかり,activerecordに書いてしまうとエラーメッセージなどが翻訳されません.

ActiveModelだけを使うときはactivemodelに書かないと翻訳をしてくれません><

これだけでかなり時間を食ってしまった・・・