- Ruby / Rails関連
[Rails5] Active Support Core ExtensionsのString#inquiryでメタプログラミング
こんにちは、hachi8833です。Active Support探訪シリーズ、今回はString#inquiry
にお邪魔します。実用的なコードとしてはおそらく最もシンプルなメタプログラミングを見つけたのでそのあたりを読み進めてみました。
今回のメソッド
- メソッド:
String#inquiry
- ディレクトリ配置: https://github.com/rails/rails/blob/5-0-stable/activesupport/lib/active_support/core_ext/string/inquiry.rb
String#inquiry
実は地味ながら便利なメソッドです。以下のようなRails.env
での使い方が典型的です。
# Inquiryを使わない場合
Rails.env == 'production'
# Inquiryを使った場合
Rails.env.production?
条件にリテラルを直に書き込む== "production"
よりも、リテラルを使わずにtrue/falseをかっこよくチェックできる.production?
の方がRailsらしい書き方とされています。
Railsガイドにも説明がありますが、以下の公式サンプルがわかりやすいですね。
pets = [:cat, :dog].inquiry
pets.cat? # => true
pets.ferret? # => false
pets.any?(:cat, :ferret) # => true
pets.any?(:ferret, :alligator) # => false
条件
- Railsバージョン: 5-0-stable
- Rubyバージョン: 2.4.0
String#inquiry
inquiry
は以下のようなシンプルなコードです。例によってコメントなどは省略しています。
require 'active_support/string_inquirer'
class String
def inquiry
ActiveSupport::StringInquirer.new(self)
end
end
ActiveSupport::StringInquirer
の実体は、active_support/string_inquirer.rbにありました。今回はすぐにエンドが見えてほっとしました。
module ActiveSupport
class StringInquirer < String
private
def respond_to_missing?(method_name, include_private = false)
method_name[-1] == '?'
end
def method_missing(method_name, *arguments)
if method_name[-1] == '?'
self == method_name[0..-2]
else
super
end
end
end
end
これはいわゆるメタプログラミングというやつですね。実はメタプロのコードをみっちり読むのはこれが初めてですが、これならなんとかなりそうです。
method_missing
の内容
以下のmethod_missing
はRubyのBasicObject#method_missingをオーバーライドしていることになります。
def method_missing(method_name, *arguments)
if method_name[-1] == '?'
self == method_name[0..-2]
else
super
end
end
method_name[-1]
はメソッド名末尾を、method_name[0..-2]
はメソッド名の末尾以外をそれぞれ指します。メソッド名末尾が?で終わっていれば自身が持つ文字列とメソッド名本体が一致するかどうかをtrue
/false
で返します。
それ以外のメソッドはsuper
で見送りますので、StringやObjectで通常どおり処理されます。
respond_to_missing
の役割
ところで、上のmethod_missing
オーバーライドがあるので完成かと思いきや、以下のrespond_to_missing
メソッドもオーバーライドしています。これは一体何をしているのでしょうか。
def respond_to_missing?(method_name, include_private = false)
method_name[-1] == '?'
end
これはリファレンスマニュアル: Object#respond_to_missing?を読むとわかります。
Object#respond_to? はメソッドが定義されていない場合、 デフォルトでこのメソッドを呼びだし問合せます。BasicObject#method_missing を override した場合にこのメソッドも override されるべきです。
リファレンスマニュアル: Object#respond_to_missing?より
Object#respond_to?
が期待どおりに動くよう、respond_to_missing
を適切にオーバーライドしないとしないといけないんですね。メタプロするときはこれを忘れないようにしましょう。
メタプログラミングに触れて
ご存知のとおり、メタプログラミングという手法はRailsのさまざまなコード、特にActive Recordで激しく使われています。
末尾が?で終わるメソッド名であれば、Stringクラスが元々持っているメソッドであってもmethod_missing
でキャッチしてしまうのかなと思っていましたが、クラスが持っているメソッドがあればそもそもmethod_missing
が動き出すこともありませんね。
これを応用すれば、メソッドをわざわざ書かなくてもメソッドが動的に生えてくるかのようにクラスが振る舞うわけです。実際はメソッド名に応答するようになっただけといえばそれまでですが。
実際にはそのメソッド名はないので、IDEのコードジャンプで追いたくてもメソッド名がインデックス化されるはずもありません。method_missing
もrespond_to_missing?
も通常のメソッド呼び出しとまったく経路が違うので、RubyMineでもジャンプしようがありません。
メタプログラミングは非常に強力である反面、やりすぎるとコードを追うのが大変困難になるのは私のような者でも想像がつきます。