Is there a way to implicitly evaluate variables in ruby? - ruby

In PHP, I can do this:
$a = 1;
$c = 'a';
$$c = 2;
//now $a == 2
Is there any equivalent in ruby? By which I mean, any simple way to have it dereference a variable during execution like this? I'd rather not use eval, because it looks messy - I've already determined that eval can't be called as a method of a string.

It is possible but it's a bit more complicated., and you actually have two possibilities:
Kernel#local_variables
Returns the names of the current local variables.
fred = 1
for i in 1..10
# ...
end
local_variables #=> [:fred, :i]
Binding#local_variable_get/set
Returns a value of local variable symbol.
def foo
a = 1
binding.local_variable_get(:a) #=> 1
binding.local_variable_get(:b) #=> NameError
end
This method is short version of the following code.
binding.eval("#{symbol}")

if you just need this you can do
a = 1
c = 'a'
eval("#{c} = 2")
a == 2 # => true
... but this is moron way to do this
if you need this for instance variables
class Foo
attr_reader :a
def initialize
#a = 1
end
end
foo = Foo.new
foo.instance_variable_get(:a) #=> 1
foo.a #=> 1
foo.instance_variable_set(:"#a", 2)
foo.a #=> 2
...you can also eval instance like this:
# ...
foo.instance_eval do
#a = 'b'
end
foo.a # => 'b'

Related

How do I call a ruby function named []?

I am new to Ruby, so please excuse this question if it is obvious.
I am working with a Module with a function signature that I don't understand. How would I call this function?
module Facter
...
def self.[](name)
collection.fact(name)
end
...
In my code I want to reference something that should be in collection.fact, in this Facter module. What syntax to I use to call this function?
Cheers
It works like this:
class MyModule
def self.[](arg)
puts arg
end
end
MyModule["Hello world"] # will print Hello world
Please see official docs:
https://ruby-doc.org/core/doc/syntax/methods_rdoc.html
Additionally, methods for element reference and assignment may be defined: [] and []= respectively. Both can take one or more arguments, and element reference can take none.
class C
def [](a, b)
puts a + b
end
def []=(a, b, c)
puts a * b + c
end
end
obj = C.new
obj[2, 3] # prints "5"
obj[2, 3] = 4 # prints "10"
So about example from docs
# From docs
obj[2, 3]
# It's the same as
obj.[](2, 3)
More interesting example
# From docs
obj[2, 3] = 4
# will print 10
# => 4
# It's the almost as
obj.[]=(2, 3, 4)
# will print 10
# => nil
As you see when you call as obj[2, 3] = 4 Ruby takes the value after = as the last argument of the []= method and return it as method result
And regardless of whether there is return in the method body. For example
class C
def []=(a, b, c)
puts "Before return"
return 12
puts "After return"
end
end
obj = C.new
obj[2, 3] = 4
# will print Before return
# => 4
obj.[]=(2, 3, 4)
# will print Before return
# => 12
It is desirable to define such method with more than one parameter. Technically, you can have only one, but the call will be like this obj[] = 1

how to convert "+=" to operator in ruby

I can convert "+" , "-" or "/" to operator use 2.send("-",3)
but it does not work with "+="
a = 2
a += 2 #=> 4
a = 2
a.send("+=", 4) #=> NoMethodError: undefined method `+=' for 2:Fixnum
I have tried to convert symbol first ; but not works too;
how to fix this?
2.send("-", 3) works, because - is a method and 2 responds to that method:
2.respond_to?('-') #=> true
= and += on the other hand are not methods:
2.respond_to?('=') #=> false
2.respond_to?('+=') #=> false
And even if = was a valid method1, then
a = 2
a.send("=", 4)
would be equivalent to:
2.send("=", 4)
or simply:
2 = 4
In other words: it would redefine 2 as 4, something Ruby doesn't allow you to do.
This is because variables (like a above) are not objects. a.send doesn't send a message to the variable a, but to the object, a is referring to (2 in the example).
The same applies to abbreviated assignment like +=:
a = 2
a += 2
is equivalent to:
a = 2
a = a + 2
You can just rewrite it as:
a = 2
a = a.send("+", 2)
The assignment is not part of the method invocation.
1 you can actually define a method = via define_method:
class Foo
define_method('=') { |other| puts "`=' called with #{other}" }
end
But it is just an ordinary method. In particular, it does not and can not alter the variable the object was assigned-to or the object's identity:
f = Foo.new
#=> #<Foo:0x007ff20c0eeda8>
f.send('=', 123)
# `=' called with 123
#=> nil
f
#=> #<Foo:0x007ff20c0eeda8>

Why isn't a variable defined by attr_accessor available in a method?

class A
attr_accessor :rank
def change_rank
rank = rank + 1
end
end
a = A.new
a.rank = 5
p a.rank
a.change_rank
p a.rank
produces an error for rank = rank + 1 (undefined method + for nil:Nilclass). Shouldn't an implicit call to the "rank" method return the value of the instance variable #rank? For some reason, this code works if I change the 4th line to:
self.rank = self.rank + 1
Why does an explicit call to the rank method works while an implicit one doesn't?
def change_rank
rank = rank + 1
end
In this context name rank does not resolve to one of methods generated by attr_accessor. It is seen by ruby roughly as this:
def change_rank
rank = nil
rank = rank + 1
end
When ruby sees an assignment to a "naked" name, it creates a local variable with this name. It will shadow the outer method. And since local vars are initialized with nil, it explains the error you're getting.
You can do this to avoid the error (be explicit about what you're modifying):
def change_rank
self.rank += 1
end
Update:
Here's more code that illustrates this
defined?(x) # => nil # name x is unidentified yet
defined?(x = x) # => "assignment" # local var x is created by assignment
defined?(x) # => "local-variable" # now local var x exists
x # => nil # and its value is nil
I rewrite your code as following. You can see how Ruby treats rank= and rank with different ways.
class A
attr_accessor :rank
def change_rank
self.rank = rank + 1
end
end
a = A.new
a.rank = 5
p a.rank
a.change_rank
p a.rank
execution result:
5
6

ruby variable as same object (pointers?)

>> a = 5
=> 5
>> b = a
=> 5
>> b = 4
=> 4
>> a
=> 5
how can I set 'b' to actually be 'a' so that in the example, the variable a will become four as well. thanks.
class Ref
def initialize val
#val = val
end
attr_accessor :val
def to_s
#val.to_s
end
end
a = Ref.new(4)
b = a
puts a #=> 4
puts b #=> 4
a.val = 5
puts a #=> 5
puts b #=> 5
When you do b = a, b points to the same object as a (they have the same object_id).
When you do a = some_other_thing, a will point to another object, while b remains unchanged.
For Fixnum, nil, true and false, you cannot change the value without changing the object_id. However, you can change other objects (strings, arrays, hashes, etc.) without changing object_id, since you don't use the assignment (=).
Example with strings:
a = 'abcd'
b = a
puts a #=> abcd
puts b #=> abcd
a.upcase! # changing a
puts a #=> ABCD
puts b #=> ABCD
a = a.downcase # assigning a
puts a #=> abcd
puts b #=> ABCD
Example with arrays:
a = [1]
b = a
p a #=> [1]
p b #=> [1]
a << 2 # changing a
p a #=> [1, 2]
p b #=> [1, 2]
a += [3] # assigning a
p a #=> [1, 2, 3]
p b #=> [1, 2]
You can't. Variables hold references to values, not references to other variables.
Here's what your example code is doing:
a = 5 # Assign the value 5 to the variable named "a".
b = a # Assign the value in the variable "a" (5) to the variable "b".
b = 4 # Assign the value 4 to the variable named "b".
a # Retrieve the value stored in the variable named "a" (5).
See this article for a more in-depth discussion of the topic: pass by reference or pass by value.
As has been noted the syntax you are using can not be done. Just throwing this out there though you could make a wrapper class it depends what you actually want to do
ruby-1.8.7-p334 :007 > class Wrapper
ruby-1.8.7-p334 :008?> attr_accessor :number
ruby-1.8.7-p334 :009?> def initialize(number)
ruby-1.8.7-p334 :010?> #number = number
ruby-1.8.7-p334 :011?> end
ruby-1.8.7-p334 :012?> end
=> nil
ruby-1.8.7-p334 :013 > a = Wrapper.new(4)
=> #<Wrapper:0x100336db8 #number=4>
ruby-1.8.7-p334 :014 > b = a
=> #<Wrapper:0x100336db8 #number=4>
ruby-1.8.7-p334 :015 > a.number = 6
=> 6
ruby-1.8.7-p334 :016 > a
=> #<Wrapper:0x100336db8 #number=6>
ruby-1.8.7-p334 :017 > b
=> #<Wrapper:0x100336db8 #number=6>
You can use arrays:
a = [5]
b = a
b[0] = 4
puts a[0] #=> 4
This idea is based on this answer.
Just for the sake of reference.
>> a = 5
=> 5
>> a.object_id
=> 11
>> b = a
=> 5
>> b.object_id
=> 11
>> b = 4
=> 4
>> b.object_id
=> 9
>> a.object_id
=> 11
# We did change the Fixnum b Object.
>> Fixnum.superclass
=> Integer
>> Integer.superclass
=> Numeric
>> Numeric.superclass
=> Object
>> Object.superclass
=> BasicObject
>> BasicObject.superclass
=> nil
I hope this gives us all a little better understanding about objects in Ruby.
One option in cases where you feel you would like to have direct pointer operations is to use the replace method of Hashes, Arrays & Strings.
this is useful for when you would like to have a method return a variable that a proc the method sets up will change at a later date, and don't want the annoyance of using a wrapper object.
example:
def hash_that_will_change_later
params = {}
some_resource.on_change do
params.replace {i: 'got changed'}
end
params
end
a = hash_that_will_change_later
=> {}
some_resource.trigger_change!
a
{i: 'got changed'}
It's probably better generally to use explicit object wrappers for such cases, but this pattern is useful for building specs/tests of asynchronous stuff.
I'm no Ruby expert. But for a technically crazy kluge...that would only work if you felt like going through eval every time you worked with a variable:
>> a = 5
=> 5
>> b = :a
=> :a
>> eval "#{b} = 4"
=> 4
>> eval "#{a}"
=> 4
>> eval "#{b}"
=> 4
Note that a direct usage of b will still give you :a and you can't use it in expressions that aren't in eval:
>> b
=> :a
>> b + 1
NoMethodError: undefined method `+' for :a:Symbol
...and there are certainly a ton of caveats. Such as that you'd have to capture the binding and pass it around in more complex scenarios...
'pass parameter by reference' in Ruby?
#Paul.s has an answer for if you can change the point of declaration to be a wrapper object, but if you can only control the point of reference then here's a BasicReference class I tried:
class BasicReference
def initialize(r,b)
#r = r
#b = b
#val = eval "#{#r}", #b
end
def val=(rhs)
#val = eval "#{#r} = #{rhs}", #b
end
def val
#val
end
end
a = 5
puts "Before basic reference"
puts " the value of a is #{a}"
b = BasicReference.new(:a, binding)
b.val = 4
puts "After b.val = 4"
puts " the value of a is #{a}"
puts " the value of b.val is #{b.val}"
This outputs:
Before basic reference
the value of a is 5
After b.val = 4
the value of a is 4
the value of b.val is 4

Ruby defining operator procedure

how can a write a class in ruby that has a procedures that i can call like this:
a = MyObj.new()
b = MyObj.new()
c = a * b
d = a / b
e = a - b
this is nicer than:
c = a.multiply(b)
...
thanks
class Foo
attr_accessor :value
def initialize( v )
self.value = v
end
def *(other)
self.class.new(value*other.value)
end
end
a = Foo.new(6)
#=> #<Foo:0x29c9920 #value=6>
b = Foo.new(7)
#=> #<Foo:0x29c9900 #value=7>
c = a*b
#=> #<Foo:0x29c98e0 #value=42>
You can find the list of operators that may be defined as methods here:
http://phrogz.net/ProgrammingRuby/language.html#operatorexpressions
You already got an answer on how to define the binary operators, so just as little addendum here's how you can define the unary - (like for negative numbers).
> class String
.. def -#
.. self.swapcase
.. end
.. end #=> nil
>> -"foo" #=> "FOO"
>> -"FOO" #=> "foo"
Just create methods whose name is the operator you want to overload, for example:
class MyObj
def / rhs
# do something and return the result
end
def * rhs
# do something and return the result
end
end
In Ruby, the * operator (and other such operators) are really just calling a method with the same name as the operator. So to override *, you could do something like this:
class MyObj
def *(obj)
# Do some multiplication stuff
true # Return whatever you want
end
end
You can use a similar technique for other operators, like / or +. (Note that you can't create your own operators in Ruby, though.)

Resources