モデルにメソッドとか属性とかmixinしたい。
えっと、今やってるアプリケーションは"開始日"と"終了日"を持ってるモデルがたくさんあって、属性の名前も同じ、やってるバリデーションも同じ、やってる検索もだいたい同じ。だけど、今は全部コピペで記述した感じになっている。
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つ方法がある。
- Moduleを使う
- 継承する
今回は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では省略した。