最近、モジュールとrefinementsを組み合わせて使ってみようとして、requireがループしてハマったため回避策を書いておく。
Railsでこんなファイル構成を取っていた。
class Post < ActiveRecord::Base
include Commentable
belongs_to :user
end
using CommentableRefinement
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
def thread_author
author
end
end
module CommentableRefinement
refine Post do
def author
user
end
end
end
(これはサンプルで適当に書いたので、正確ではない)
こうやっていれば、commentable.rbでのみrefinementで拡張したメソッドを元々持っていたかのように振舞える。
とは言え、こういう書き方をすると、usingもrefineもincludeもファイルを読み込んだ時点で評価されてしまう。
なので、Postを定義している途中で、Commentableが必要になり、Commentableを読みにいくと冒頭でCommentableRefinementが必要になり、CommentableRefinementでrefineを定義しようとするとPostが必要になって読み込みがループして、Post::Commentableが見つからねえよ、と言われて死ぬ。
仕方が無いので、includeの評価をCommentable読み込み後まで遅延させるように工夫した。
class Post < ActiveRecord::Base
klass = self
ActiveSupport.on_load :commentable do |mod|
klass.send(:include, mod)
end
require "commentable"
belongs_to :user
end
using CommentableRefinement
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
def thread_author
author
end
ActiveSupport.run_load_hooks(:commentable, self)
end
これで、とりあえず依存関係のループは切れるのだが、なんかダサいw
ちなみに、refinementはAsakusa.rbでmoroさんからざっくり話を聞いて、自分なりにこんな感じか?と書いてみたもので、用途としてアリかどうかについてはまだ分かってない。