こころがホッコリー

ただイカしたRubyistになりたい人生だった。

Rubyで任意のメソッド呼出を検出し、任意のコードを実行できるgemを作った

はじめに

今年8月のエントリーで、任意のメソッド呼出を監視し、任意のコードを実行するgem「Okuribito」の作成を報告しました。

muramurasan.hatenablog.jp

その後、gemの機能や、使われ方の思想が結構変わったので、改めて「Okuribito」の使い方を紹介したいと思います。

Okuribitoとは

Okuribitoは任意のメソッド呼出を監視し、任意のコード実行をできるようにするRubyのgemです。
このgemを使えば、例えば、メソッド呼び出しが行われた際にログファイルに書き出すことや、Slack通知を行えるようにするということができるようになります。
また、yamlファイルによって外から監視メソッドを指定するため、コードをほぼ(※)汚すことがない、というのも本gemの目玉となっております。

※ 監視開始を指示する部分と、任意のコードを記述するブロック部分は、どうしてもコーディングする必要があります

使い方

以下のようなコードにOkuribitoを適用する例を通じて、使い方を説明していきたいと思います。

class TestTarget
  def self.deprecated_self_method
    puts "deprecated_self_method"
  end

  def deprecated_method
    puts "deprecated_method"
  end
end

TestTarget.deprecated_self_method
TestTarget.deprecated_self_method
test1 = TestTarget.new
test2 = TestTarget.new
test1.deprecated_method
test2.deprecated_method

インストール

まず、Gemfileに次の記述を追加してbundle installします。

gem 'okuribito'

監視したいメソッドの記述

監視したいメソッドをyamlファイルに記述し、配置します。
以下のように、クラス名にネストしてメソッド名を記述していきます。
メソッド名を記述する際は、クオテーションで囲み、それがクラスメソッドなのかインスタンスメソッドなのかのシンボルを記述する必要があります。

TestTarget:
  - ".deprecated_self_method"
  - "#deprecated_method"

requireの追加

以下を追記し、コードからOkuribitoを使えるようにしましょう。

require "okuribito"

Okuribitoの適用を記述

以下のように、Okuribitoのインスタンス生成時に、メソッド呼び出し検出時に実行したいコードを記述します。
applyした以降は、Okuribitoによるメソッド呼び出し監視が有効になります。

okuribito = Okuribito::OkuribitoPatch.new(once_detect: true) do |method_name, obj_name, caller_info|
  puts "#{obj_name} #{method_name} #{caller_info[0]}"
end
okuribito.apply("okuribito.yml")

渡す仮引数は以下をサポートしています。

  • method_name:メソッド名を取得できます
  • obj_name:オブジェクトを文字列に変換したものを取得できます
  • caller_info:バックトレース(メソッドの呼び出し履歴)を取得できます
  • class_name:クラス名を取得できます
  • method_symbol:クラスメソッド(.)かインスタンスメソッド(#)かの文字を取得できます

また、Okuribitoのインスタンスを生成する際、以下のオプションをハッシュで指定することができます。

  • once_detect:true の場合、Okuribitoは同じメソッドの2回目以降の呼び出しを無視してくれるようになります

運用

Okuribitoを適用した最終的なコードは以下のようになっているとします。

require "okuribito"

class TestTarget
  def self.deprecated_self_method
    puts "deprecated_self_method"
  end

  def deprecated_method
    puts "deprecated_method"
  end
end

okuribito = Okuribito::OkuribitoPatch.new(once_detect: true) do |method_name, obj_name, caller_info|
  puts "#{obj_name} #{method_name} #{caller_info[0]}"
end
okuribito.apply("okuribito.yml")

TestTarget.deprecated_self_method
TestTarget.deprecated_self_method
test1 = TestTarget.new
test2 = TestTarget.new
test1.deprecated_method
test2.deprecated_method

プログラムを実行すると、TestTarget.deprecated_self_methodTestTarget#deprecated_method の呼び出しがそれぞれ一度だけ検出され、結果、以下のような標準出力が得られます。

TestTarget deprecated_self_method okuribito.rb:18:in `<main>'
deprecated_self_method
deprecated_self_method
#<TestTarget:0x007f9c61c96080> deprecated_method okuribito.rb:22:in `<main>'
deprecated_method
deprecated_method

課題

一通り、欲しい機能は作れたかなと思っています。
が、現状、名前空間に対応していないのが課題です。
後、(自分が把握している範囲で)誰かに使ってもらった訳ではないので、バグがまだあるかもしれない、というのが悩みです。

何で Okuribito って名前なの?

元々、未使用のメソッドを安全に削除するのを支援する目的で作ったgemだからです。
メソッドの最後を看取る......という想いを込めて、Okuribito。

おわりに

周囲に「どんなgemがあると嬉しいか?」というのをヒアリングしながら作ったので、コードの汚さはともかく、機能的には良いモノができたと思っています。
使ってみて、もしもバグと思える動作が発生した際は、issue の登録をお願い致します!(もちろん、要望も歓迎します!)
また、機能やコードを改善するPull Requestもお待ちしています!

少しでも「いいね」と思っていただけた方は、リポジトリにStarをつけていただけると幸いです \(^o^)/