問題
例えばjsonで結果が返ってくるが、raiseはしない..とか、エラーの際にはログに取りたいとかの場合、各メソッドの結果をみて毎回処理する必要がある。
とても煩雑で取りこぼしも起きやすい。
response = api.add("test name", "test description")
if response.status == "ok"
update_response = api.update(response.id, "test name updated")
unless update_response.status == "ok"
logger.error update_response.error
if update_response.code == "9999"
raise "致命的なエラー"
end
end
else
logger.error response.error
if response.code == "9999"
raise "致命的なエラー"
end
end
prependを使う
prependを使えば同メソッド名の呼び出しの前後に処理を突っ込めるが、メソッド名が分かってないといけない。
分かってたとしても10,20とメソッド名があると管理が大変。
解決策 - dynamicにprependする
APIのpublicなメソッドだけを対象にして、前後に処理を差し込む
そのままirbで動くサンプル.rb
require "json"
require "active_support/concern"
module ApiHookable
extend ActiveSupport::Concern
included do
# public_methods(false)で継承を含まないメソッドだけ取れる
# 継承の一部までのメソッドも含めて必要な場合は別途処理が必要
method_names = self.instance_methods(false)
# includeと同時にprependする
prepend(
# 動的にmoduleを作って、動的にメソッドを宣言
Module.new do
method_names.each do |name|
define_method name do |*args, &block|
puts "[prepended] pre"
super(*args, &block).tap do |response|
# ここでresponseの内容によってraiseしたりslackに通知したり
puts "[prepended] post"
puts "[prepended] checking response... "
unless JSON.parse(response)["status"] == "ok"
raise "error"
end
end
end
end
end
)
end
module ClassMethods
end
end
class SomeApi
# 各インスタンスメソッドがまだ宣言されていないので、
# ここでincludeしてしまうとダメ
def add(name, description)
puts "calling api server..."
%|{"status":"ok"}| # 例えばjsonを返す
end
end
# 宣言が確定したクラスに対してincludeする
SomeApi.class_eval{ include ApiHookable }
response = SomeApi.new.add "test name", "test description"
p [:response, response]
# [prepended] pre
# calling api server...
# [prepended] post
# [prepended] checking response...
# [:response, "{\"status\":\"ok\"}"]
prepend後
response.status == "ok"
じゃない場合のprependを適用したとして、最初の例が次のようになり、裏で自動的にログ処理が走る。
response = api.add("test name", "test description")
if response.status == "ok"
api.update(response.id, "test name updated")
end