Named arguments as local variables in Ruby
arguments, ruby, variables
Solution
I don't believe there's any way to do this in Ruby (if anyone comes up with one, please let me know, and I'll update or delete this answer to reflect it!) - if a local variable hasn't been defined yet, there's no way to dynamically define it with the binding. You could conceivably do something like `orange, lemon, grapefruit = nil` before calling eval, but you may run into other problems - for instance, if args[:orange] is the string "Orange", you'll end up evaluating `orange = Orange` with your current implementation.
Here's something that could work, though, using the OpenStruct class from the standard library (by "could work", I mean "it's up to your sense of style whether `a.orange` is any nicer than `args[:orange]`"):
require 'ostruct'
def my_method_with_ostruct(args)
a = OpenStruct.new(args)
puts "my_method_with_ostruct variables: #{a.orange}, #{a.lemon}, #{a.grapefruit}"
end
If you don't need easy access to any state or methods on the receiver of this method, you could use instance_eval, as follows.
def my_method_with_instance_eval(args)
OpenStruct.new(args).instance_eval do
puts "my_method_with_instance_eval variables: #{orange}, #{lemon}, #{grapefruit}"
end
end
You could even do something tricky with method_missing (see here for more) to allow access to the "primary" object, but the performance probably wouldn't be great.
All in all, I think it's probably most straightforward/readable to go with the less DRY initial solution that bothered you.
Problem
I find myself constantly writing what I see as unnecessary code in Ruby when using named arguments for methods. Take for example the following code: ``` def my_method(args) orange = args[:orange] lemon = args[:lemon] grapefruit = args[:grapefruit] # code that uses # orange, lemon & grapefruit in this format which is way prettier & concise than # args[:orange] args[:lemon] args[:grapefruit] puts "my_method variables: #{orange}, #{lemon}, #{grapefruit}" end my_method :orange => "Orange", :grapefruit => "Grapefruit" ``` What I really don't like about this code is that I am having to take the args and pass the values into local variables going against DRY principles and just generally taking up space in my methods. And if I don't use local variables and just refer to all variables with the args[:symbol] syntax then the code becomes somewhat illegible. I have tried working up a solution to this but keeping hitting a brick wall as I don't know how to define local variables using eval in the scope of the method, or using any other technique. Here is one of many attempts below, which results in an error ``` def my_method_with_eval(args) method_binding = binding %w{ orange lemon grapefruit}.each { |variable| eval "#{variable} = args[:#{variable}]", method_binding; } # code that uses # orange, lemon & grapefruit in this format which is way prettier & concise than # args[:orange] args[:lemon] args[:grapefruit] puts "my_method_with_eval variables: #{orange}, #{lemon}, #{grapefruit}" end my_method_with_eval :orange => "Orange", :grapefruit => "Grapefruit" ``` When running that code I simply get `NameError: undefined local variable or method ‘orange’ for main:Object method my_method_with_eval in named_args_to_local_vars at line at top level in named_args_to_local_vars at line 9` Anyone got any ideas how I could simplify this down somehow so that I don't have to start my named argument methods with loads of var=args[:var] code? Thanks, Matthew O'Riordan