cookpadさんのArproxyのソースコードリーディングをしました。

機能の概要

ActiveRecordのDBへのアクセス前後に処理を入れることができるようになるプロキシ的なライブラリです。

具体的な用途としては以下のように、ロガー、セキュリティ系で利用されるイメージです。

  • スロークエリを抽出してログを書き出す
  • 特定のクエリを禁止する(例えばSELECTだけ許可してDMLはNGとする)

プロキシ自体は多段にすることができ、処理内容の責務を分けることが可能です。

使い方

READMEのコピペですが、プロキシクラスを定義してconfigureで設定してenable!で有効化します。

Arproxy::Baseはproxy_chain、next_proxyのプロパティとexecuteメソッドを持ちます。

Baseを継承したプロキシクラスはexecuteメソッドを実装しますが、その際に、super、つまりnext_proxy.executeを呼び出します。これによってプロキシクラスのexecuteの処理 => next_proxyのexecuteの処理、というように連鎖していき、最後にはActiveRecordのアダプタのexecuteメソッドにたどりつくという仕組みです。

Arproxy.configureではConfigクラスをイニシャライズしてからブロックに記述した通りに設定します。

Config内にはproxiesとadapterプロパティを持っており、proxiesはプロキシのクラスとオプションのリストとなっており、adapter_classは任意のDBに対するActiveRecordのアダプタのクラスを返すメソッドとなっており、@adapterにはアダプタクラスに対応する名前をセットすることになります。

enable!メソッド内ではProxyChainをイニシャライズして、@configを引数にしてenable!メソッドを呼び出します

ProxyChainはイニシャライズ時にChainTailインスタンスを生成します。その名の通り、このインスタンスがexecuteチェーンの最後のインスタンスとなり、executeメソッドではActiveRecordのDBアダプタのexecuteメソッドを呼び出します。proxy_chain.connectionにはActiveRecordのDBアダプタのインスタンスがセットされます。

以下でconfigに入れたproxiesをセットしていきます。

Proxy1、Proxy2という順番で設定した場合、reverseにより逆順で処理されます。injectの初期値(最初のnext_proxy)はChainTailで、proxy_configはproxiesの最後に設定したプロキシクラスになります。順にプロキシクラスをインスタンス化してnext_proxyによって後ろからつなげていき最後に一番最初に設定したプロキシクラスを@headとしてセットします。

enable!メソッドではアダプタクラスのclass_evalで以下の処理を実行します。

  • execute_with_arproxyメソッドを定義:ProxyChainのheadのexecuteを走らせる(ここからチェーンしてChainTailのexecuteまで処理を行う)
  • もともとのexecuteメソッドのエイリアスとしてexecute_without_arproxyを定義
  • execute_with_arproxyのエイリアスとしてexecuteを定義

これによりアダプタクラスのexecuteが呼び出されるとexecute_with_arproxyが呼び出され、プロキシクラス内に定義されたexecuteを順に呼び出していき、最後にChainTailのself.proxy_chain.connection.execute_without_proxyによって本来のexecute(エイリアスでスイッチする前)を呼び出します。

わからなかったとこ

ProxyChainのconnectionのアクセサメソッドで保持先がThread.current[:arproxy_connection]にしてるのが、よくわからず。Threadごとに固有の値にすることがメリットなのか、スレッドセーフにすることがメリットなのか…。プラグインクラス利用時のメリデメとかも。

所感

ActiveRecordの前後処理ができるのでActiveRecord関連のプロファイリングするときにはめっちゃ便利そう。