String to argument name - ruby

Suppose I have a ruby function (func) with named arguments (foo and bar) which I can call by providing either or both arguments like this:
func(foo: "whatever")
func(bar: "whatever")
func(foo: "whatever", bar: "whatever")
What I need is a way to call this function by passing strings for the arguments' names:
name = "foo"
func(name: "whatever")
I read about to_sym but don't know how to use it. At least this does not work:
name = "foo"
func(name.to_sym: "whatever")
Is there a way?
Thanks.

func(name.to_sym => "whatever")
Seems to work in Ruby 3.1

You can use the following:
func("#{name}": "whatever")
The name will be interpolated and converted to symbol

Related

Check if local variable is defined given it's name as string in ruby

Can I check if a local variable is defined given it's name as string?
I know there is the function defined?, but you have to give the variable itself.
Example:
a = 'cat'
print defined?(a) # => "cat"
print defined?(b) # => nil
What I need is:
a = 'cat'
print string_defined?("a") # => "cat"
print string_defined?("b") # => nil
Or something like that. I can't find it in the docs...
I tried to use respond_to?, but doesn't seems to work...
Starting with Ruby 2.1.0 you can use Binding#local_variable_defined?:
a = 'cat'
binding.local_variable_defined? 'a' #=> true
binding.local_variable_defined? 'b' #=> false
The following will return true when the local variable in question is (to be) defined in the context, not necessary in a position preceding the point of it:
local_variables.include?("a".to_sym)
#=> true
You can do it using eval:
a = 'cat'
eval("defined?(#{'a'})")
=> "local-variable"
eval("defined?(#{'b'})")
=> nil
Disclaimer: This answer makes use of eval, so it can be dangerous if you don't strictly control the string you want to pass into it. And definitely you shouldn't do it this way if these strings come from user input.

String destructive methods don't seem to work on string slice

I have a string and I want to capitalize the first letter. I tried the following:
x='abc'
x[0].upcase! # => "A"
x # => "abc"
It doesn't work as intended, even though the method upcase! is destructive. The following works:
x='abc'
x[0] = x[0].upcase # => "A"
x # => "Abc"
Can someone explain why upcase! doesn't work above?
x[0] is returning a new string. You are modifying this new string in place and then you do nothing with it.
Note:
You can use the capitalize method to do the same:
x='abc'
x.capitalize!
x #=> 'Abc'
upcase! is destructive, and it modifies the string x[0], but not x. You haven't done anything to x with upcase!.
It is a different string object. You can find out if you use object_id on your objects and compare them.

resolve #{var} in string

I have loaded a string with #{variable} references in it. How would I resolve those variables in the string like puts does?
name="jim"
str="Hi #{name}"
puts str
Instead of puts, I would like to have the result available to pass as a parameter or save into a variable.
you could eval it
name = "Patrick"
s = 'hello, #{name}'
s # => "hello, \#{name}"
# wrap the string in double quotes, making it a valid interpolatable ruby string
eval "\"#{s}\"" # => "hello, Patrick"
puts doesn't resolve the variables. The Ruby parser does when it creates the string. if you passed str to any other method, it would be the same as passing 'Hi jim', since the interpolation is already done.
String has a format option that appears as %. It can be used to pass arguments into a predefined string much like interpolation does.
message = "Hello, %s"
for_patrick = message % "Patrick" #=> "Hello, Patrick"
for_jessie = message % "Jessie" #=> "Hello, Jessie"
messages = "Hello, %s and %s"
for_p_and_j = messages % ["Patrick", "Jessie"] #=> "Hello, Patrick and Jessie"
It may not look "Rubyish" but I believe it is the functionality you are looking for.
So, if you have a string coming in from somewhere that contains these placeholders, you can then pass in values as arguments as so:
method_that_gets_hello_message % "Patrick"
This will also allow you to only accept values you are expecting.
message = "I can count to %d"
message % "eleven" #=> ArgumentError: invalid value for Integer()
There's a list on Wikipedia for possible placeholders for printf() that should also work in Ruby.
The eval seems to be the only solution for this particular task. But we can avoid this dirty-unsafe-dishonourable eval if we modify the task a bit: we can resolve not local, but instance variable without eval using instance_variable_get:
#name = "Patrick"
#id = 2 # Test that number is ok
#a_b = "oooo" # Test that our regex can eat underscores
s = 'hello, #{name} !!#{id} ??#{a_b}'
s.gsub(/#\{(\w+)\}/) { instance_variable_get '#'+$1 }
=> "hello, Patrick !!2 ??oooo"
In this case you even can use any other characters instead of #{} (for example, %name% etc), by only modifying the regex a bit.
But of course, all this smells.
It sounds like you want the basis for a template system, which Ruby does easily if you use String's gsub or sub methods.
replacements = { '%greeting%' => 'Hello', '%name%' => 'Jim' }
pattern = Regexp.union(replacements.keys)
'%greeting% %name%!'.gsub(pattern, replacements)
=> "Hello Jim!"
You could just as easily define the key as:
replacements = { '#{name}' => 'Jim' }
and use Ruby's normal string interpolation #{...} but I'd recommend not reusing that. Instead use something unique.
The advantage to this is the target => replacement map can easily be put into a YAML file, or a database table, and then you can swap them out with other languages, or different user information. The sky is the limit.
The benefit to this also, is there is no evaluation involved, it's only string substitution. With a bit of creative use you can actually implement macros:
macros = { '%salutation%' => '%greeting% %name%' }
replacements = { '%greeting%' => 'Hello', '%name%' => 'Jim' }
macro_pattern, replacement_pattern = [macros, replacements].map{ |h| Regexp.union(h.keys) }
'%salutation%!'.gsub(macro_pattern, macros).gsub(replacement_pattern, replacements)
=> "Hello Jim!"

Concise way of prefixing string if prefix is not empty

Is there a shorter way of doing the following?
foo =
config.include?(:bar) ?
"#{bar}.baz" :
"baz"
I'm looking for a readable one-liner that appends a variable, plus a delimiter, if the variable exists (assuming it's a string).
config is a Hash.
You could do this:
foo = [bar, 'baz'].compact.join('.')
If bar is nil then compact will remove it from the array and delimiter won't be added.
foo = "#{"bar." if config.include?(:bar)}baz"

String interpolation when not using a string literal

I have a Ruby script that used string interpolation to build error messages.
p "#{vName} is not a defined variable" => 'xxx is not a defined variable'
Another programmer came through and attempted to externalize the string literals to a separate configuration file. Of course, he doesn't get the substitution.
p err_string_from_config => '#{vName} is not a defined variable'
I've looked around, but couldn't come up with anything better than converting to sprintf strings and using printf.
Does anybody know how to get the #{} substitution to work on strings that are not double quote literals within the Ruby script?
Actually Ruby has functionality very similar to John's Python example:
$ irb
>> greeting = 'hello %s, my name is %s!'
>> interpolated = greeting % ['Mike', 'John']
=> "hello Mike, my name is John!"
>>
This is also useful if your argument is an array constant. If you must use #{} style interpolation you could use eval:
>> greeting = 'hi #{name}' # notice name is not defined yet
>> name = "mike"
>> eval '"' + greeting + '"'
The eval approach is going to be much slower than using % style interpolation, so it's a trade-off.
I suggest that you look at Liquid templating language which provides more powerful features (e.g. you can reference parameters by name).
Previous example would look like:
greeting = Liquid::Template.parse("hello {{your_name}}, my name is {{my_name}}!")
interpolated = greeting.render('your_name' => 'Mike', 'my_name' => 'John')
# => "hello Mike, my name is John!"
Here's how I do it, just for the record. A bit clearer imo.
gief = '#{later} please'
later = "later"
puts eval(%Q["#{gief}"])
# => later please
But honestly, that is such a hack. Unless you have a really good reason to use a string, use a proc instead. I always try to use plain ruby instead of evaling strings.
gief = Proc.new {|l| "#{l} please" }
later = "later"
puts gief.call(later)
# => later please

Resources