ホーム DoRuby method_missingを使ってみる
method_missingを使ってみる
 

method_missingを使ってみる

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

method_missing とは

何かメソッドを呼び出したとき、
rubyはそのクラスに該当メソッドがあるか捜索し、なければ継承元のクラスを捜索しに行きます。
これはごく普通の挙動ですよね。

ですが捜索した結果該当メソッドが存在しなかった場合呼び出されるのが method_missing です。
これをうまいこと使うとコードが一気に短縮できるのでそのパターンを紹介します。

実際以下のようなクラスがあった場合を想定します。

class User
  def update
     puts "更新されたよ"
  end

  def login
    puts "ログインしたよ"
  end
  # more methods...
end
class UsersManager
  def initialize
    @array_users = []
  end

  def add_user(user)
    @array_users << user
  end

  def update 
    @array_users.each do |user|
      user.update
    end
  end
end

UsersManagerが持っている配列内のUserのメソッドをまとめてコールするというだけのものです。

manager = UsersManager.new
manager.add_user(User.new)

manager.update
=> "更新されたよ"

問題なく使用できると思います。

ですが今の状態で下記のように実行すると

manager.login

NoMethodError: undefined method `login'

もちろんエラーになります。
単純に考えるとUserManagerにloginの処理を追加すれば実行できますが、Userが100や150のメソッドを持っていてそれらも実行しようとしたとき、その数分メソッドを作るのは現実的じゃないですよね。

method_missingを利用してみよう

上の実行ではNoMethodErrorが出力されています。
rubyでは呼び出されたメソッドが存在しなかった場合に

manager.send(:method_missing, :login)

が呼ばれ、その結果NoMethodErrorになります。

これがどうかしたのかというと、あくまでただのメソッドなので
継承先に method_missing を実装してやることで挙動を制御することができます。

先ほどのクラスに method_missing を下記のように実装して実行すると、

class UsersManager
  def initialize
    @array_users = []
  end

  def add_user(user)
    @array_users << user
  end

  def method_missing(name, *args)
    @array_users.each do |user|
      user.send( name, *args )
    end
  end
end
manager = UsersManager.new
manager.add_user(User.new)

manager.login
=>ログインしたよ

という形になります。
これは呼び出したメソッドがUserManagerクラスに存在しなかったので method_missing が呼び出されるのですが、その時UserManagerクラスに method_missing がないか捜索してくれるのでこのような実行結果になるわけです。

こうすればUserクラスにどれだけメソッドが増えたとしてもUserManagerには処理を追加する必要がなくなります。
とっても便利ですよね。

使用時の注意点

無限ループに陥りやすい?

method_missingを記述するとき、「継承元にあるmethod_missingは呼ばれない」ということに気をつけなければいけません。
例えば先ほどのUserManagerクラスを以下のように変更します

def method_missing(name, *args)
  self.hoge
  @array_users.each do |user|
    user.send( name, *args )
  end
end

この状態でmethod_missingが呼ばれると

manager.login
=>output error: SystemStackError: stack level too deep

どうやらStackOverFlowが起こっているようです。

self.hogeが存在しない -> method_missingが呼ばれる -> self.hogeが存在しない -> ……
といった風に無限ループを発生させてしまっています。

今回のパターンではかなり露骨な書き方をしているのでわかりやすいですが、NoMethodErrorは比較的やってしまいがちなエラーだと思います。それがいきなりStackOverFlowになってしまうので、この危険性は頭の隅に置いておかないと何のエラーだかわかりにくく危険です。

処理を追いにくい

method_missingを使用したコードを他の人が追おうとした時、クラスに呼んだメソッドが記述されていなかったらまず親クラスのメソッドなのかと疑うと思います。そもそもmethod_missingを知らない人はどうしようもないですよね。

まとめ

実際うまく使えたらとっても便利だと思いますが、
method_missingを知っておかないと何をしているのか分からないコードだなという印象を強く感じ、
こういった特殊な動きをするものは知識として入れておかないといけないなと再認識できました。
これから先難解なコードに手をつける機会が訪れた時に迷うことを極力減らせるように、rubyの知識を増やしていこうと思います。

記事を共有

最近人気な記事