concerned_withパターンでModelを分かりやすく分割する

この投稿は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を複数ファイルに分けて書く方法

今回のサンプルコードの大半はこの記事のものを利用させていただきました。


Railsで1つのモデルを複数ファイルに分けて書く方法

ActiveSupport::Concern と、Module#concerning


著者プロフィール
Webサイトをいくつか作っています。
著者プロフィール