Forum: Ruby Method argument introspection

5d902998374cb6a948694d2cb10d0537?d=identicon&s=25 Augusts Bautra (epigene)
on 2017-04-12 20:46
Recently I found myself needing to programmatically access arguments
passed to a method and after some searching found a somewhat convoluted
solution (added below).

Is there a built-in ruby variable via `$<something>`, `__something__` or
similar less-known approach that lets one access arguments passed to a
method?

```rb
# Known method arguments as of 2.4.1

# 1. mandatory argument
# 2. optional argument
# 3. splatted argument list
# 4. mandatory keyword argument
# 5. optional keyword argument
# 6. splatted keyword arguments
# 7. block

def frankenstein(mandatory, optional=nil, *argument_list,
mandatory_keyword:, optional_keyword: nil, **keyword_list)
  naive_param_hash = method(__method__).parameters.inject({}){|mem, arr|
mem[arr[1].to_sym] = binding.local_variable_get(arr[1]) ; mem}
  #=> {
  #   :mandatory=>"mandatory",
  #   :optional=>nil,
  #   :argument_list=>["argument_in_list_1", "argument_in_list_2"],
  #   :mandatory_keyword=>"mandatory_keyword",
  #   :optional_keyword=>nil,
  #   :keyword_list=>{:keyword_list_one=>"first",
:keyword_list_two=>"second"}
  # }

  descriptive_param_hash = method(__method__).parameters.inject({})do
|mem, arr|
    arg_type = arr[0].to_sym
    arg_name = arr[1].to_sym
    arg_value = binding.local_variable_get(arg_name)

    current_parameter_hash = {arg_name => arg_value}
    mem[arg_type].respond_to?(:merge!) ?
mem[arg_type].merge!(current_parameter_hash) : (mem[arg_type] =
current_parameter_hash)
    mem
  end
  #=> {
  #   :req=>{:mandatory=>"mandatory"},
  #   :opt=>{:optional=>nil},
  #   :rest=>{:argument_list=>["argument_in_list_1",
"argument_in_list_2"]},
  #   :keyreq=>{:mandatory_keyword=>"mandatory_keyword"},
  #   :key=>{:optional_keyword=>nil},
  #   :keyrest=> {
  #     :keyword_list=> {
  #       :keyword_list_one=>"first", :keyword_list_two=>"second"
  #     }
  #   }
  # }

  optimized_descriptive_param_hash =
method(__method__).parameters.inject({})do |mem, arr|
    arg_type = arr[0].to_sym
    arg_name = arr[1].to_sym
    arg_value = binding.local_variable_get(arg_name)

    current_parameter_hash = {arg_name => arg_value}

    case arg_type
    when :rest, :keyrest
      # since these keys will definitely only appear once, no sense to
have a subhash, instead, use the method-definded key name
      mem[arg_name] = arg_value
    else
      mem[arg_type].respond_to?(:merge!) ?
mem[arg_type].merge!(current_parameter_hash) : (mem[arg_type] =
current_parameter_hash)
    end

    mem
  end
  #=> {
  #   :req=>{:mandatory=>"mandatory"},
  #   :opt=>{:optional=>nil},
  #   :argument_list=>["argument_in_list_1", "argument_in_list_2"],
  #   :keyreq=>{:mandatory_keyword=>"mandatory_keyword"},
  #   :key=>{:optional_keyword=>nil},
  #   :keyword_list=>{
  #     :keyword_list_one=>"first", :keyword_list_two=>"second"
  #   }
  # }
end

frankenstein("mandatory", nil, "argument_in_list_1",
"argument_in_list_2", mandatory_keyword: "mandatory_keyword",
keyword_list_one: "first", keyword_list_two: "second")
```
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.