Accessing method name and its arguments at runtime in Ruby

Not so long ago, I wanted create a helper function to access current’s method (name) and its arguments at runtime for logging purposes.

To give a more specific example, imagine you have a module to call external API and a few classes relying on it:

module SomeApi
  def self.get(url)
    # Some function to call some external API
  end
end

class Accounting
  def self.user(user_id)
    SomeApi.get('/users/#{user_id}')
  end

  def self.account(account_id)
    SomeApi.get("/accounts/#{account_id}")
  end
end

Now, let’s say you want to record each invocation of SomeApi.get from Accounting module’s public functions to log their name and arguments (sudo code):

def self.user(user_id)
  record do
    SomeApi.get('/users/#{user_id}')
  end
end

def self.account(account_id)
  record do
    SomeApi.get("/accounts/#{account_id}")
  end
end

Since Ruby has very powerful meta-programming abilities, we can get access to current method’s name and its parameter with __method__ and method('method_name').parameters.

With this knowledge, we can build our helper record function:

def record(caller_binding, &block)
  method_name = caller_binding.eval('__method__')
  params = method(method_name).parameters.map do |_, name|
    [name, caller_binding.local_variable_get(name)]
  end.to_h

  block.call.tap do |result|
    # record the method who called a block, e.g. in a database
    ApiActivity.create!(method: method_name, params: params)
  end
end

We rely on ability of passing binding (execution context) to have access to the caller.

def self.user(user_id)
  record(binding) do
    SomeApi.get('/users/#{user_id}')
  end
end