この投稿はRuby on Rails Advent Calendar 2014の25日目の記事です。
concerned_withパターンでModelを分かりやすく分割する
本記事では、concerned_withパターンを使ってModelを分かりやすく分割する方法について解説します。
この方法を覚えておくと、gemでよく見かける下記のような不思議な書き方を自分で実装できるようになります。
class Forum < ActiveRecord::Base # ↓これとか resourcify end class Widget < ActiveRecord::Base # ↓これのこと has_paper_trail end
concerned_withパターンとは
concerned_withパターンとは、RailsのModelを機能単位で分割する時に使えるデザインパターンです。
少なくとも2011年の時点で存在してたようですが、Webで検索してもあまり情報が見付からない、不遇な?デザインパターンです。
一例として、バリデーションだけを別ファイルに定義する例はこんな感じです。
# app/models/user.rb class User < ActiveRecord::Base concerned_with :validation end # app/models/user/validation.rb class User validates :first_name, presence: true validates :last_name, presence: true ... (大量のバリデーションコード) ... end # config/initializers/concerns.rb class << ActiveRecord::Base def concerned_with(*concerns) concerns.each do |concern| require_dependency "#{name.underscore}/#{concern}" end end end
上記の例ではバリデーションのみを別ファイルに分けていますが、他の機能ごとに分けることももちろん可能です。
class User < ActiveRecord::Base concerned_with :activation, :authentication, :validation, ... end
Fat Controllerはよく批判の的に挙がりますが、「じゃあcontrollerからどこに・どういう風にコードを移せばいいの?」と聞かれると、Webに情報はあまりなく、人によって答えは分かれ、Rails初心者を迷わせるポイントになっているのではないかと個人的には思います。
そのような際の引き出しの1つとして、concerned_withパターンを覚えておくと、今後何かのお役に立つかもしれません。
実はRails標準で似たような機能が用意されています
ここまで書いておきながら、実はRails標準で似たような機能が実装されています。ActiveSupport::Concernです。
ActiveSupport::Concernを使った例はこんな感じです。
# app/models/user.rb class User < ActiveRecord::Base include Concerns::User::Validation end # app/models/concerns/user/validation.rb require "active_support/concern" module Concerns::User::Validaton extend ActiveSupport::Concern included do validates :first_name, presence: true validates :last_name, presence: true ... (大量のバリデーションコード) ... end end
ただし、このように似てはいるものの、結論からいうと、今回のような用途にはconcerned_withパターンの方が適していると個人的には思います。
上記の例では、concerned_withパターンと似せて書きましたが、現時点では、ActiveSupport::Concernは、「複数Model(またはController)から共通してincludeされるモジュール用」みたいな位置付けのようです。
そのため、今回のような単体のクラスから利用するケースにはあまり向かないかもなぁと個人的には思います。
参考リンク
Concerned_withパターンでRailsのひとつのmodelを複数ファイルに分けて書く方法
今回のサンプルコードの大半はこの記事のものを利用させていただきました。
ActiveSupport::Concern と、Module#concerning