【Rails】ActiveDecorator読んでみたら超勉強になった
神戸.rb Meetup #10 - Kobe.rb | Doorkeeperで検証してみた内容.
ActiveDecorator?
ActiveDecoratorはPresenterなどと呼ばれ, ModelとViewの中間のレイヤーを担う. ViewでModelが関わるロジック等を扱いたいとき(表示を整形したいとか)にHelperの代替として用いられる.
このレイヤーを作るメリットは、
- model/viewに余計なロジックを書かなくてすむ
- Helperのメソッドが使われている場所が不明という事態を防ぐ
Helperを乱用し過ぎるとグローバル関数っぽくてややこしくなったりしますよね. Ruby Toolboxの Rails Presenterカテゴリでは4位(記事執筆時点). 他にはDraperが有名.
Draperとの違いとしては…
- 明示的に
decorate
しなくても良い - 関連先Model等はデコられない(partialに投げたらデコってくれる)
どうやって動いてる?
Controllerのインスタンス変数をデコる
AbstractController::Rendering
をモンキーパッチ的に拡張することで自動デコり機能を実現してる.
Controllerで宣言されたインスタンス変数すべてにdecorate
メソッドを適用する.
(alias_method_chain
なんてメソッド初めて知った.便利だ.)
# lib/active_decorator/monkey/abstract_controller/rendering.rb#L3 def view_assigns_with_decorator hash = view_assigns_without_decorator hash.values.each do |v| ActiveDecorator::Decorator.instance.decorate v end hash end alias_method_chain :view_assigns, :decorator
その変数のクラスにDecorator
が存在した場合に,その変数にDecorator
でextend
する.
また,"#{model_class.name}Decorator"
という命名になっているものを探してくる.
User
→UserDecorator
Gochiusa::PyonPyon
→Gochiusa::PyonpyonDecorator
# lib/active_decorator/decorator.rb#L29 d = decorator_for obj.class return obj unless d obj.extend d unless obj.is_a? d
Partialの引数をデコる
こちらはActionView::PartialRenderer
を拡張.locals
,object
,collection
をデコってくれる.
# lib/active_decorator/monkey/action_view/partial_renderer.rb#L19 def setup_with_decorator(context, options, block) setup_without_decorator context, options, block setup_decorator end alias_method_chain :setup, :decorator
# lib/active_decorator/monkey/action_view/partial_renderer.rb#L3 def setup_decorator @locals.values.each do |v| ActiveDecorator::Decorator.instance.decorate v end unless @locals.blank? ActiveDecorator::Decorator.instance.decorate @object unless @object.blank? ActiveDecorator::Decorator.instance.decorate @collection unless @collection.blank? self end
隠れた特徴
名前に"Active"って入ってるからActiveRecord
/ActiveModel
限定だと思い込んでいたが,実はそうでもない.
デコる対象はインスタンス変数全て,locals
全部などクラス問わず,Decorator
の命名にあうものがあればそれを引っ張ってきてくれる.
# app/models/gochiusa.rb class Gochiusa attr_accessor :pyon_pyons def initialize @pyon_pyons = [Pyonpyon.new] end class Pyonpyon end end
# app/decorators/gochiusa_decorator.rb module GochiusaDecorator end
# app/decorators/gochiusa/pyonpyon_decorator.rb module Gochiusa::PyonpyonDecorator end
# app/controllers/gochiusa_controller.rb class GochiusaController < ApplicationController def show @gochiusa = Gochiusa.new end end
<%# app/views/gochiusa/show.html.erb %> <%# @gochiusaはデコられる %> <%# @gochiusa.pyonpyonsはデコられない %> <%= render partial: 'gochiusa/pyonpyon', collection: @gochiusa.pyonpyons, as: :pyonpyon %>
<%# app/views/gochiusa/_pyonpyon.html.erb %> <%# pyonpyonはデコられてる %>
まとめ
- ActiveDecoratorはなんでもデコってくれる
ActiveRecord
やActiveModel
以外でも関係なし
- 関連先等はpartialに放り込んだらデコってくれる
- Viewが強制的にSkinnyになる
- 実装がキレイで読みやすく,勉強になる
alias_method_chain
の存在はここで初めて知った- Railsの内部について知ることができる
- Controllerやpartialにおける変数の扱い方も学べる