How to dynamically call accessor methods in Ruby [duplicate] - ruby

This question already has answers here:
How to call methods dynamically based on their name? [duplicate]
(5 answers)
Closed 8 years ago.
Regardless of whether it's good practice or not, how can I dynamically call accessor methods in Ruby?
Here's an example class:
class Test_Class
attr_accessor :a, :b
end
I can use the Object.send method to read the variable...
instance.a = "value"
puts( instance.send( "a" ) )
# => value
But I'm having a hard time trying to write to it. These throw "wrong number of arguments (1 for 0) (ArgumentError)"
instance.send("a", "value")
and
instance.method("a").call("value")
Please help me StackOverflow!

I am not a ruby expert, but I think that you could do:
instance.send("a=", "value")

You can also directly access instance variables of an object using instance_variable_* functions:
instance = Test_Class.new # => #<Test_Class:0x12b3b84>
# instance variables are lazily created after first use of setter,
# so for now instance variables list is empty:
instance.instance_variables # => []
instance.instance_variable_set(:#a, 123) # => 123
instance.a # => 123
instance.instance_variables # => ["#a"]
instance.instance_variable_get("#a") # => 123

Related

Ruby - get method params names and values [duplicate]

This question already has answers here:
Is there a way to access method arguments in Ruby?
(11 answers)
Closed 5 years ago.
Is there a way to get method parameters names and values dynamically, maybe a metaprogramming?
def my_method(name, age)
# some code that solves this issue
end
my_method('John', 22) # => { name: 'John', age: 22 }
I want to use this in all my methods in order to log methods calls and respective parameters, but without to do this manually in every method.
Thanks.
Yup! In ruby, it's called the binding, which is an object that encapsulates the context in which a particular line runs. The full docs are here, but in the case of what you're trying to do...
def my_method(arg1, arg2)
var = arg2
p binding.local_variables #=> [:arg1, :arg2, :var]
p binding.local_variable_get(:arg1) #=> 1
p Hash[binding.local_variables.map{|x| [x, binding.local_variable_get(x)]}] #=> {:arg1 => 1, :arg2 => 2, :var => 2}
end
my_method(1, 2)
I'd strongly advise against Binding#eval, if you can possibly help it. There's almost always a better way to sovle problems than by using eval. Be aware that binding encapsulates context on the line at which it is called, so if you were hoping to have a simple log_parameters_at_this_point method, you'll either need to pass the binding into that method, or use something cleverer like binding_of_caller

Using loop counter as part of a variable's name in Ruby [duplicate]

This question already has answers here:
In Ruby, is there no way to dynamically define a local variable in the current context? [duplicate]
(3 answers)
Closed 7 years ago.
I'm trying to generate a bunch of variables that will be operated on. Started with 2 variables, 4, 8,16 and its about time I put the variable generation in a loop.
The variable is acting as a storage for the index of an array.
So a thread wakes up, picks up a variable and says "variable_0" is the first index, and stores location 24 inside of it.
This is usually done like so. This variable goes on and is used multiple times later on.
variable_0 = get_index(threadid)
Once I have 16 of such variables, its a pain and ugly to see that line repeated more than once. So I thought I'd put in a loop and tried it with 2 methods.
1. EVAL
for i in 0..15 do
eval("variable_#{i} = get_index(threadid)")
end
Does not work at all.
2. instance_variable_set
for i in 0..15 do
index_name = "variable_#{i}"
index_val = get_index(threadid)
instance_variable_set("#{index_name}", :index_val)
end
This doesn't work either, since a later statement prints "variable not found " pretty much.
Is there a good way to go about doing this?
Due to restrictions later down the line, I cannot use an array for this, each variable has to be constructed as a single variable, so no arrays or anything like that would work
As others have pointed out it is not possible to create local variables dynamically in Ruby, you could set up a binding as well if you're looking for another method of achieving this.
With eval
b = binding
10.times do |i|
eval("var#{i} = 'foo'", b)
end
> eval("var1", b)
=> "foo"
> eval("local_variables", b)
=> [:var9, :var8, :var7, :var6, :var5, :var4, :var3, :var2, :var1, :var0, :b, :_]
Without eval
b = binding
10.times do |i|
b.local_variable_set("var#{i}", 'foo')
end
> b.local_variable_get('var1')
=> "foo"
> b.local_variables
=> [:var9, :var8, :var7, :var6, :var5, :var4, :var3, :var2, :var1, :var0, :b, :_]
Your second example is almost valid. You forgot # for instance variable name and you passed a symbol as value instead the value itself.
for i in 0..15 do
index_val = "some value #{i}"
instance_variable_set("#variable_#{i}", index_val)
end
puts #variable_4 # "some value"
https://repl.it/BnLO/6

what ruby tap method does on a {} [duplicate]

This question already has answers here:
Understanding tap in Ruby
(2 answers)
Closed 8 years ago.
I've read about what tap does in Ruby but I'm confused by the code block below,
{}.tap do |h|
# some hash processing
end
any help would be greatly appreciated.
#tap method simply passes an object it was called on to a block. At the end of the block it returns the same object again. This way you can chain operations or restrict variable scope.
{}.tap { |h| h[:a] = 1 }.size # => 1
You were able to chain a next method to this block. And also avoided creating a h variable in your scope.
tap is particularyl useful if you want to do some modifications on the hash and after that return it, e.g. inside a method that returns the hash. The advantage of tap being that you don't have to explitely return the hash at the end and there's no need for an intermediary variable.
hash = {}
hash[:a] = 'b'
hash
# => {:a=>"b"}
vs.
{}.tap do |hash|
hash[:a] = 'b'
end
# => {:a=>"b"}
For exemple : you want to chain delete and each on a hash.
You cannot do
hash.delete(:key).each {...}
but can do
hash.tap { |h| h.delete(:key) }.each { ... }
It is a good way to chain your methods.
tap doesn't do anything to it. Whatever you do to h in the block is whatever is done to the original {}.

How to turn a string into a method call? [duplicate]

This question already has answers here:
How to call methods dynamically based on their name? [duplicate]
(5 answers)
Closed 8 years ago.
How can I use a string as a method call?
"Some Word".class #=> String
a = "class"
"Some World".a #=> undefined method 'a'
"Some World"."#{a}" #=> syntax error, unexpected tSTRING_BEG
Object#send
>> a = "class"
>> "foo".send(a)
=> String
>> a = "reverse"
>> "foo".send(a)
=> "oof"
>> a = "something"
>> "foo".send(a)
NoMethodError: undefined method `something' for "foo":String
If you want to do a chain, can also use Object#eval
>> a = "foo"
=> "foo"
>> eval "a.reverse.upcase"
=> "OOF"
If you have a string that contains a snippet of ruby code, you can use eval. I was looking for question with that answer when I landed here. After going off and working it out (thanks ProgrammingRuby), I'm posting this in case others come here looking for what I was looking for.
Consider the scenario where I have a line of code. here it is:
NAMESPACE::method(args)
Now consider the scenario where that is in a string variable
myvar = "NAMESPACE::method(args)"
Using send(myvar) does not execute the command. Here is how you do it:
eval(myvar)

Dynamically set local variables in Ruby [duplicate]

This question already has answers here:
How to dynamically create a local variable?
(4 answers)
Closed 7 years ago.
I'm interested in dynamically setting local variables in Ruby. Not creating methods, constants, or instance variables.
So something like:
args[:a] = 1
args.each_pair do |k,v|
Object.make_instance_var k,v
end
puts a
> 1
I want locally variables specifically because the method in question lives in a model and I dont want to pollute the global or object space.
As an additional information for future readers, starting from ruby 2.1.0 you can using binding.local_variable_get and binding.local_variable_set:
def foo
a = 1
b = binding
b.local_variable_set(:a, 2) # set existing local variable `a'
b.local_variable_set(:c, 3) # create new local variable `c'
# `c' exists only in binding.
b.local_variable_get(:a) #=> 2
b.local_variable_get(:c) #=> 3
p a #=> 2
p c #=> NameError
end
As stated in the doc, it is a similar behavior to
binding.eval("#{symbol} = #{obj}")
binding.eval("#{symbol}")
The problem here is that the block inside each_pair has a different scope. Any local variables assigned therein will only be accessible therein. For instance, this:
args = {}
args[:a] = 1
args[:b] = 2
args.each_pair do |k,v|
key = k.to_s
eval('key = v')
eval('puts key')
end
puts a
Produces this:
1
2
undefined local variable or method `a' for main:Object (NameError)
In order to get around this, you could create a local hash, assign keys to this hash, and access them there, like so:
args = {}
args[:a] = 1
args[:b] = 2
localHash = {}
args.each_pair do |k,v|
key = k.to_s
localHash[key] = v
end
puts localHash['a']
puts localHash['b']
Of course, in this example, it's merely copying the original hash with strings for keys. I'm assuming that the actual use-case, though, is more complex.
interesting, you can change a local variable but you cannot set it:
def test
x=3
eval('x=7;')
puts x
end
test =>
7
def test
eval('x=7;')
puts x
end
test =>
NameError: undefined local variable or method `x' for main:Object
This is the only reason why Dorkus Prime's code works.
I suggest you use the hash (but keep reading for other alternatives).
Why?
Allowing arbitrary named arguments makes for extremely unstable code.
Let's say you have a method foo that you want to accept these theoretical named arguments.
Scenarios:
The called method (foo) needs to call a private method (let's call it bar) that takes no arguments. If you pass an argument to foo that you wanted to be stored in local variable bar, it will mask the bar method. The workaround is to have explicit parentheses when calling bar.
Let's say foo's code assigns a local variable. But then the caller decides to pass in an arg with the same name as that local variable. The assign will clobber the argument.
Basically, a method's caller must never be able to alter the logic of the method.
Alternatives
An alternate middle ground involves OpenStruct. It's less typing than using a hash.
require 'ostruct'
os = OpenStruct.new(:a => 1, :b => 2)
os.a # => 1
os.a = 2 # settable
os.foo # => nil
Note that OpenStruct allows you access non-existent members - it'll return nil. If you want a stricter version, use Struct instead.
This creates an anonymous class, then instantiates the class.
h = {:a=>1, :b=>2}
obj = Struct.new(* h.keys).new(* h.values)
obj.a # => 1
obj.a = 2 # settable
obj.foo # NoMethodError
since you don't want constants
args = {}
args[:a] = 1
args[:b] = 2
args.each_pair{|k,v|eval "##{k}=#{v};"}
puts #b
2
you might find this approach interesting ( evaluate the variables in the right context)
fn="b*b"
vars=""
args.each_pair{|k,v|vars+="#{k}=#{v};"}
eval vars + fn
4

Resources