日頃お世話になっているactive_decorator。とっても便利ですよね!
https://github.com/amatsuda/active_decorator
最近、以下のようにアソシエーションにもデコレーターが適用されることに気づきました。(神)
# コントローラー
@user = User.first
# ビュー
@user.posts.each do |post|
post.decorated_method # アソシエーションも自動的にデコレートされる
end
なので今回はなぜアソシエーションにもデコレーターが適用できるのか仕組みを調べてみました。
どこでデコレーターが適用されているか
まず最初にrailsではControllerで定義されたインスタンス変数をView側に渡すためのhashを作成するview_assignsというメソッドがあります
# actionpack/lib/abstract_controller/rendering.rb
def view_assigns
variables = instance_variables - _protected_ivars
variables.each_with_object({}) do |name, hash|
hash[name.slice(1, name.length)] = instance_variable_get(name)
end
end
(インスタンス変数名をキーにして値を詰めている。sliceしてるのはなんでだろうと思ったら”@”を消すためでした)
デコレーターの適用はこのview_assignsメソッドをオーバーライドして行われているようです。
# lib/active_decorator/monkey/abstract_controller/rendering.rb
def view_assigns
hash = super
hash.each_value do |v|
ActiveDecorator::Decorator.instance.decorate v
end
hash
end
(decorateメソッドは今回の話からやや脱線するので省略しますが、配列やhashにも対応してくれており、再帰処理していて素敵なメソッドでした😃)
https://github.com/amatsuda/active_decorator/blob/master/lib/active_decorator/decorator.rb#L25
どこでアソシエーションにデコレーターを適用しているか
そして今回のきもとなる部分はAssociationのtargetのオーバーライドです。
このtargetがアソシエーションの実態らしいです🤔
ここでアソシエーションにデコレーターを適用していました。
# lib/active_decorator/monkey/active_record/associations.rb
module ActiveDecorator
module Monkey
module ActiveRecord
module Associations
module Association
def target
ActiveDecorator::Decorator.instance.decorate_association(owner, super)
end
end
decorate_associationでは親オブジェクトがデコレートされてる時だけデコレーターが適用されるようになっていました。これでViewでだけデコレーターが適用される仕組みみたいです。
# lib/active_decorator/decorator.rb
def decorate_association(owner, target)
(ActiveDecorator::Decorated === owner) ? decorate(target) : target
end
というわけでアソシエーションにもデコレーターが適用されている仕組みがわかりました!
今回はCursorにざっくりと仕組みを聞いてから実装を辿るという方法で調べてみたのですが全体を把握してからコードを読めるのでわかりやすかったです。今後もGemのコードを読んだりするハードルが下がりそうと思いました!