Work Records

日々の作業記録です。ソフトウェアエンジニアリング全般から、趣味の話まで。

ruby メタプロ method_missing使用例 - delayed_job編 -

gemをもくもくとよんでみた結果得られた物を書こうと思う。
今回はmethod_missing。

method_missingとは

rubyのメタプロ的書き方の一つ。
継承チェーンを一番上まで辿ってもそんなメソッド無いよ!って時に呼ばれる輩。
ググれば色々と出てきますが、要するに実行時に初めて存在するメソッドを呼ぶ時に便利。

delayed_jobでの使用例

delayed_jobって?

非同期にmethodを実行する為のgem。
github.com

実行方法は、Readmeに書いてある通り、レシーバーとメッソッドの間にdelayを挟むだけ。
これで、@user.activate!(@device)ごとシリアライズしてデータベースにキューを保存、daemon化しているプロセスが定期的にキューを拾って実行、とやっている。

# without delayed_job
@user.activate!(@device)

# with delayed_job
@user.delay.activate!(@device)

method_missing使用例

まず、疑問に感じるのが、delayってどこでよんでいるのだ?と@user.delayがなんでactivateを呼べるのか?というところ

delayが呼べる理由

ここは単純だが、ObjectとModuleにincludeさせている箇所がある。
lib/delayed_job.rb

Object.send(:include, Delayed::MessageSending)
Module.send(:include, Delayed::MessageSending::ClassMethods)

これもメタプロっぽいがincludeで継承チェーンに突っ込んでいる。(ObjectとModuleなので殆どのクラスは継承する事になる)
Delayed::MessageSendingにdelayがいるのではれて皆がdelayを呼べるようになる。
lib/delayed/message_sending.rb

(snip)
module MessageSending
  def delay(options = {})
    DelayProxy.new(PerformableMethod, self, options)
  end
(snip)
activateを呼べる理由

ここでmethod_missingを使っている。
さっきのdelayの中で呼ばれていたDelayProxyを追うと

class DelayProxy < Delayed::Compatibility.proxy_object_class
  def initialize(payload_class, target, options)
    @payload_class = payload_class
    @target = target
    @options = options
  end

  def method_missing(method, *args)
    Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options))
  end
end

となっていて、.new以外は全部method_missingに送っているのである。
もうちょっと読み進めると、.newでレシーバーとメソッドとオプションをセットしていて
enqueueする時にそれらをセットしようとしている事が分かる。
こうやって、呼び出したいメソッドをごっそりとシリアライズしてdbにキューとしてしまっていた。

まとめ

最近メタプログラミングRubyを読んでいて、method_missingの正しい使い道って何だろう?と思っていた。
こんなの黒魔術じゃないかと。
でもdelayed_jobは完全に正しくmethod_missingを使っているなと感動した。
次回はシリアライズあたりをおってみようとおもう。多分続く。