最近Rubyのメタプロ本を再読して、method_missing
を使った実装を試してみたので使い方とかメモしておきます📝
method_missing
とは?
呼びだされたメソッドが定義されていなかった時、Rubyインタプリタがこのメソッドを呼び出します。 https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html
method_missing
とはRubyリファレンスマニュアルに記載してあるとおり、呼び出したメソッドが定義されていなかったときに自動的に呼び出されるメソッドです。
class Hoge def method_missing(name, *args) puts "method #{name} is missing... :(" end end irb(main):040:0> Hoge.new.fuga method fuga is missing... :( => nil
このような感じでメソッドが見つからなかったときの挙動を定義できます。 これを使うと実行時に定義するメソッドを動的に決めたり色々なことができます。
method_missing
を使ってみる
引数で渡された複数のClientの挙動を引き継いで独自の実装を追加するようなClassが必要となったときmethod_missing
が役に立ちます。
例えば複数のDBMSに対して同一の処理を実行するために、それぞれのDBMSのクライアントの挙動を引き継ぎながら共通の処理を追加するWrapperなどです。
※Clientの挙動の引き継ぐやり方として、継承を使うことも考えられますが複数のClientに対応することはできません。
※またRuby標準のSimpleDelegator
を使ったやり方では複数のClientの挙動を委譲することはできません。
class ClientsWrapper def initialize(clients:) @clients = clients end def method_missing(name, *args) @clients.each { |client| client.send(name, *args) } end end class Mysql def self.exec puts "Mysql" end end class Sqlite def self.exec puts "Slite" end end irb(main):016:0> ClientWrapper.new(client: Client).exec Mysql Slite
このような形で複数のClientに対して対してメソッドを実行することができました!
method_missingを使ってもrespond_to?をtrueにしたい
method_missing
を使うと動的にメソッドを定義出来る一方、respond_to?
を使って挙動を制御するといったことはできなくなってしまいます😢
irb(main):082:0> ClientsWrapper.new(clients: [Mysql,Sqlite]).respond_to?(:exec) => false
この問題を解決してくれるのがrespond_to_missing?
です。
自身が symbol で表されるメソッドに対し BasicObject#method_missing で反応するつもりならば真を返します。 Object#respond_to? はメソッドが定義されていない場合、デフォルトでこのメソッドを呼びだし問合せます。 https://docs.ruby-lang.org/ja/latest/method/Object/i/respond_to_missing=3f.html
このメソッドを使うとrespond_to?
の挙動を制御してmethod_missing
による応答の場合でもrespond_to?
をtrueにするといったことができます。
先程のClientsWrapper
もclients
に定義されているメソッドだったら実行可能なので、respond_to?
の結果がtrueになるように修正してみます。
class ClientsWrapper def initialize(clients:) @clients = clients end def method_missing(name, *args) @clients.each { |client| client.send(name, *args) } end def respond_to_missing?(sym, include_private) # 各clientのpublic_methodsに呼び出したメソッドが含まれていればtrueを返す。 @clients.map(&:public_methods).flatten.include?(sym) ? true : super end end
respond_to?(:exec)
を実行したときにtrue
が返却されています🙌
irb(main):109:0> ClientsWrapper.new(clients: [Mysql,Sqlite]).respond_to?(:exec) => true
おわりに
今回はrubyのmethod_missing
について記載してみました。method_missing
を使うと柔軟にメソッドを定義することができますが、
「With great power comes great responsibility.(大いなる力には、大いなる責任が伴う)」 スパイダーマン - Wikipedia
ということで使い所には注意していきたいですね😅
- 作者:Paolo Perrotta
- 発売日: 2015/10/10
- メディア: 大型本