モデルにメソッドとか属性とかmixinしたい。

railsruby初心者なのでいろいろ困ってます。

えっと、今やってるアプリケーションは"開始日"と"終了日"を持ってるモデルがたくさんあって、属性の名前も同じ、やってるバリデーションも同じ、やってる検索もだいたい同じ。だけど、今は全部コピペで記述した感じになっている。

class Hoge < ActiveRecord::Base
  attr_accessible :start, :end
  validate_date :start
  validate_date :end, :on_or_after => :start
  ....
end 

class Fuga < ActiveRecord::Base
  attr_accessible :start, :end
  validate_date :start
  validate_date :end, :on_or_after => :start
  ....
end 
class Piyo < ActiveRecord::Base
  attr_accessible :start, :end
  validate_date :start
  validate_date :end, :on_or_after => :start
  ....
end
....

こんなクラスが10個くらい。もちろんrspecも同じようなコードが大量にあります。 残念なコードです、はい。

書くコードを1つにするためには2つ方法がある。

  1. Moduleを使う
  2. 継承する

今回は1でやってみた。

lib/models/has_date_duration.rb

module HasDateDuration
  extend ActiveSupport::Concern
  included do
    # 属性とvalidationはここに書く。
    attr_accessible :start, :end
    validate_date :start
    validate_date :end, :on_or_after => :start
    ...
  end
  # インスタンスメソッドは普通に
  def hoge
      ...
  end

  #クラスメソッドを書く際は以下のように書かないといけない。
  def self.included(base)
    base.extend(HasDateDurationClassMethods)
  end
  module HasDateDurationClassMethods
    def class_method_hoge
        ....
    end
  end
end

lib/modelsにおいてるので、そのままでは読み込めない。application.rbに以下を追加 config.autoload_paths += %W(#{config.root}/lib/models)

実際に使うクラスではincludeするだけ。 各クラスにstart,endの定義やvalidationはいらない。

class Hoge < ActiveRecord::Base
  include HasDateDuration
  ...
end

ちなみに、rspecはHasDateDurationに対するspecだけ書いて、各モデルのspecでは省略した。