こんにちは、hachi8833です。今回のBigBinaryシリーズは、週刊Railsウォッチでも紹介していなかったRails 5.1の新機能です。
DHHはこのメソッドをdecoratorで使うことを想定してるとのことです。
概要
- 原文: Rails 5.1 adds delegate_missing_to(米国BigBinary社のブログより)
- 原文公開日: 2017/05/30
- 著者: Neeraj Singh
新機能: delegate_missing_to(翻訳)
#method_missing
を使うときには、#respond_to_missing?
も併用するべきです。しかし、#method_missing
と#respond_to_missing?
を両方使うとコードが冗長になるのも確かです。
DHHが自ら上げた#23824で、冗長なコードの典型的な例を見ることができます。
(訳注: このコード例はそのままAPIドキュメントでも使われています)
class Partition
def initialize(first_event)
@events = [ first_event ]
end
def people
if @events.first.detail.people.any?
@events.collect { |e| Array(e.detail.people) }.flatten.uniq
else
@events.collect(&:creator).uniq
end
end
private
def respond_to_missing?(name, include_private = false)
@events.respond_to?(name, include_private)
end
def method_missing(method, *args, &block)
@events.public_send(method, *args, &block)
end
end
DHHは、こうしたコードを改善するために新しいModule#delegate_missing_to
メソッドの利用を提案しています。利用例は次のとおりです。
class Partition
delegate_missing_to :@events
def initialize(first_event)
@events = [ first_event ]
end
def people
if @events.first.detail.people.any?
@events.collect { |e| Array(e.detail.people) }.flatten.uniq
else
@events.collect(&:creator).uniq
end
end
end
SimpleDelegatorクラスでは不足な理由
BigBinary社では従来RubyのSimpleDelegatorクラスを使っていました。このクラスの問題は、このdelegatorは実行時にどんな種類のオブジェクトでも使われる可能性があるため、呼び出しが委譲される先のオブジェクトを静的に確定できないという点です。
DHHはこのパターンについて次のように言っています。
この程度のシンプルな機能のために継承ツリーをハイジャックしなくても済む方がいい。
(訳注: 続きはこうなっています)
継承と
super
を使うのもどうかと思う。#delegate_missing_to
なら意図が明確になる。
#delegate
メソッドでは不足な理由
Module#delegate
メソッドを使う手もあります。しかしその場合は全メソッドをホワイトリスト化しなければなりませんし、ホワイトリストが非常に長くなってしまうこともあります。以下は私たちの実際の案件から引用した実例コードです。
delegate :browser_status, :browser_stats_present?,
:browser_failed_count, :browser_passed_count,
:sequential_id, :project, :initiation_info,
:test_run, :success?,
to: :test_run_browser_stats
すべてを委譲するならdelegate_missing_to
状況によってはあらゆるmissing methodを委譲したくなることがありますが、#delegate_missing_to
ならそうした作業をすっきり行えます。なお、この委譲は委譲先のオブジェクトのpublicなメソッドに対してのみ有効であることにご注意ください。
詳しくは#23930のPRをご覧ください。
訳注
現在の5.1-stableの#delegate_missing_to
は以下のようになっています。
# 5-1-stable/activesupport/lib/active_support/core_ext/module/delegation.rb#L262
# オブジェクト内の呼び出し可能なものはすべて対象にできる
# (例: インスタンス変数、メソッド、定数)
def delegate_missing_to(target)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def respond_to_missing?(name, include_private = false)
# 見落としのように見えるかもしれないが、privateなものは委譲されないので
# あえてinclude_privateを渡さないようにしている
#{target}.respond_to?(name) || super
end
def method_missing(method, *args, &block)
if #{target}.respond_to?(method)
#{target}.public_send(method, *args, &block)
else
super
end
end
RUBY
end
#23824では#delegate_missing_to
が何でも委譲することの是非などについて多少議論が紛糾していました。DHHは最後に「このパターンはdecorator用である」と述べています。
関連記事
- [Rails 5] cache_keyによる結果セットとコレクションのキャッシュ機能(翻訳)
- [Rails 5.1]thread_mattr_accessorの変数はサブクラスと共有されないようになった(翻訳)
- [Rails 5]モジュールやクラスレベルの変数をスレッドベースで作成する機能(翻訳)
- [Rails 5] モデルの継承元がActiveRecord::BaseからApplicationRecordに変更された
- [Rails 5] フォームごとに異なるCSRFトークンを受け取れるようになった(翻訳)
- [Rails 5] developmentモードのアセットログはデフォルトでオフになる(翻訳)
- [Rails 5] rails dev:cacheコマンドでdevelopmentモードでのキャッシュを簡単にオン・オフできる
- [Rails 5] コントローラの制約を受けずに任意のビューテンプレートをレンダリングする
- [Rails 5] rakeタスクがrailsコマンドでもできるようになった
- [Rails 5] Rails 5の新フレームワークデフォルト設定ファイルでアップグレード作業を軽減する
- [Rails 5] マイグレーション時にデータベースのカラムにコメントを追加する