LoginSignup
1
0

More than 3 years have passed since last update.

APIなどのmethod全てに対してdynamicにprependしたい

Last updated at Posted at 2019-02-01

問題

例えば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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0