Ruby syntax for declaring an lvalue / rvalue = operator - ruby

I am trying to build an equals function in Ruby. What I want to do is something like this, suppose we have a class called A:
class A
attr_accessor :secret_datum
def public_datum
return secret_datum
end
def public_datum= what
secret_datum = what
end
end
What I want basically is to build a more complex behind the scenes functionality depending on whether public_datum is used as an lvalue or an rvalue, but my code above does not work:
2.5.0 :027 > a = A.new
=> #<A:0x00007fc2293ec338>
2.5.0 :028 > a.public_datum = 7
=> 7
2.5.0 :029 > a.public_datum
=> nil
What is the correct syntax for declaring such a set of functions?

When assigning a value to a bareword with secret_datum = what, you are always setting a simple local variable.
It seems that you actually want to use the setter of your class (which then sets the instance variable). For that, you have to hint to Ruby that you want to call the method. You can do this by defining a receiver object. Your setter can thus look like this:
def public_datum=(what)
self.secret_datum = what
end
Alternatively, you could directly set the instance variable as follows:
def public_datum=(what)
#secret_datum = what
end
As a final note, please be aware that your secret_datum accessor is still public. Thus, you could also call a.secret_datum = 7 and it would work exactly the same.

An instance variable is declared with a prepended "#" like
#secret_datum = what
Without the # the variable is declared on the stack and gone when you exit the function.
So just refer to the instance variable as #secret_datum and it will work
def public_datum=(what)
#secret_datum = what
end

Related

Ruby: Working with instance variables within a case statement [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 2 months ago.
I'm perplexed as to why I need to selectively refer to an instance variable with "self" inside a case statement inside a class method:
I have a class with an instance method #working_dir:
class FileSystem
attr_accessor :sizes, :working_dir
attr_reader :input
def initialize(input)
#input = input.split("\n")
#sizes = Hash.new(0)
#working_dir = []
end
...
end
I've defined a method parse_cmd that performs an operation on #working_dir depending on the outcome of a case statement:
...
def parse_cmd(str)
cmd_arr = str.split(' ')
return unless cmd_arr[1] == 'cd'
case cmd_arr[2]
when '..'
working_dir.pop
when '/'
self.working_dir = ['/']
else
working_dir << cmd_arr[2]
end
end
...
Rubocop/the interpreter yells at me if I exclude the self on self.working_dir = ['/']. Why is this? Why do I need to include it here, but not on other references to #working_dir within the case statement?
Consider a simple example:
class A
attr_accessor :b
def initialize(b)
#b = b
end
def c
b = 42
end
end
a = A.new(27)
# => #<A:0x00007f7999088bc0 #b=27>
a.c
# => 42
a.b
# => 27
Calling a.c is assigning 42 to a local variable b, and is not modifying the instance variable #b.
I'd either need to use self.b = 42 or #b = 42 to ensure I am modifying the instance variable.
In your case, you don't need to use self.working_dir elsewhere because those uses cannot be construed as assigning to a local variable. Because no local variable working_dir exists, the accessor method is used.
In your case statement, you are NOT directly refering to the #working_dir instance variable. Instead, you are using the accessor methods defined by attr_accessor :working_dir at the top of your class.
When calling attr_accessor, it will effectively define two methods on your class:
def working_dir
#working_dir
end
def working_dir=(value)
#working_dir = value
end
This allows you to access the value of the instance variable via the method call as if it were a local variable (but it's not, it's always a method call).
Now, in order to call the setter method working_dir=, Ruby requires that you call it with an explicit receiver (self in your case).
This is because without an explicit receiver, if you assign some value, Ruby will always assign to local variable. With working_dir = 'value', you are thus always creating a local variable named working_dir and assign a value to it.
If you use an explicit receiver however, e.g. self.working_dir = 'value', Ruby knows that this can not be a variable assignment anymore and will thus call your setter method.

In Ruby, how do I assign a value to a class instance

I want to create a class instance that has a value such that I can do something like puts a = Example.new(1) where a's value is specified in initialize.
I expect that this is a simple problem since all predefined Ruby classes allow this, but I'm unable to figure out how to do it for my classes.
Class#new and Return Values
Your example doesn't quite work because Ruby treats Class#new as a special case, and is expected to invoke the #initialize method and return an object. If it didn't, calling #new on a class would surprise a lot of people by returning the last evaluation of the initializer from your class, or from Object#new if it's otherwise undefined for your class. In either case, this would violate the principle of least surprise.
However, you can do what you want pretty easily by simply creating an accessor method and then chaining off of Example#new. For example, in Ruby 3.1.0:
class Example
attr_reader :int
def initialize(int) = (#int = int)
end
# prints `1` to STDOUT and assigns the value to *a*,
# but returns nil because you're using Kernel#puts
# which always returns nil
puts a = Example.new(1).int
# shows that the local variable *a* is set to the value
# returned by the Example#int accessor for the class'
# #int instance variable
a
#=> 1
To avoid the confusion of having a nil return value (even though this is expected with Kernel#puts, just change your puts statement to use Kernel#p instead:
p a = Example.new(2).int
#=> 2
Refactoring the Example Class for Older Rubies
If you're using an older Ruby than 3.0, you can't use an endless method or the improved handling for them in Ruby 3.1. The only difference is that rather than an inline method, you need to specify it with the standard def...end syntax, e.g.:
class Example
attr_reader :int
def initialize(int)
#int = int
end
end
Otherwise, the points above are valid as far back as any currently-supported Ruby version.

Does Ruby have a method_missing equivalent for undefined instance variables?

When I invoke a method that doesn't exist, method_missing will tell me the name of the method. When I attempt to access a variable that hasn't been set, the value is simply nil.
I'm attempting to dynamically intercept access to nil instance variables and return a value based on the name of the variable being accessed. The closest equivalent would be PHP's __get. Is there any equivalent functionality in Ruby?
I do not believe this is possible in Ruby. The recommended way would be to use a ''user'' method rather than a ''#user'' instance var in your templates.
This is consistent with the way you deal with Ruby objects externally (''obj.user'' is a method which refers to ''#user'', but is actually not ''#user'' itself). If you need any kind of special logic with an attribute, your best bet is to use a method (or method_missing), regardless if you're accessing it from inside or outside the object.
See my answer to another similar question. But just because you can do it doesn't mean that it's a good idea. Sensible design can generally overcome the need for this kind of thing and allow you to produce more readable and hence maintainable code.
instance_variable_get seems to be the closest equivalent of PHP's __get from what I can see (although I'm not a PHP user).
Looking at the relevant Ruby source code, the only 'missing' method for variables is const_missing for constants, nothing for instance variables.
there isn't an instance_variable_missing (at least that I know of)
But why are you accessing randomly named instance variables anyway?
If your thread all the access to the object state through method calls (as you should anyway) then you wouldn't need this.
If you are looking for a way to define magic stuff without messing up with the method lookup, you may want to use const_missing.
A bit late but, instance_variable_missing is the same as method_missing to a point... Take the following class:
class Test
def method_missing(*args)
puts args.inspect
end
end
t = Test.new
Now let's get some instance variables:
t.pineapples #=> [:pineapples]
t.pineapples = 5 #=> [:pineapples=,5]
Not sure why the method is nil for you...
EDIT:
By the sounds of it you want to accomplish:
t = SomeClass.new
t.property.child = 1
So let's try returning a Test object from our previous example:
class Test
def method_missing(*args)
puts args.inspect
return Test.new
end
end
So what happens when we call:
t = Test.new
t.property.child = 1
#=>[:property]
#=>[:child=,1]
So this goes to show that this is indeed possible to do. OpenStruct uses this same technique to set instance variables dynamically. In the below example, I create EternalStruct which does exactly what you wanted:
require 'ostruct'
class EternalStruct < OpenStruct
def method_missing(*args)
ret = super(*args)
if !ret
newES = EternalStruct.new
self.__send__((args[0].to_s + "=").to_sym, newES)
return newES
end
end
end
Usage of EternalStruct:
t = EternalStruct.new
t.foo.bar.baz = "Store me!"
t.foo.bar.baz #=> "Store me!"
t.foo #=> #<EternalStruct bar=#<EternalStruct baz="Store me!">>
t.a = 1
t.a #=> 1
t.b #=> #<EternalStruct:...>
t.b = {}
t.b #=> {}
def t.c(arg)
puts arg
end
t.c("hi there") #=> "hi there"

Why do instance variables seemingly disappear when inside a block?

Forgive me, guys. I am at best a novice when it comes to Ruby. I'm just curious to know the explanation for what seems like pretty odd behavior to me.
I'm using the Savon library to interact with a SOAP service in my Ruby app. What I noticed is that the following code (in a class I've written to handle this interaction) seems to pass empty values where I expect the values of member fields to go:
create_session_response = client.request "createSession" do
soap.body = {
:user => #user, # This ends up being empty in the SOAP request,
:pass => #pass # as does this.
}
end
This is despite the fact that both #user and #pass have been initialized as non-empty strings.
When I change the code to use locals instead, it works the way I expect:
user = #user
pass = #pass
create_session_response = client.request "createSession" do
soap.body = {
:user => user, # Now this has the value I expect in the SOAP request,
:pass => pass # and this does too.
}
end
I'm guessing this strange (to me) behavior must have something to do with the fact that I'm inside a block; but really, I have no clue. Could someone enlighten me on this one?
First off, #user is not a "private variable" in Ruby; it is an instance variable. Instance variables are available within the the scope of the current object (what self refers to). I have edited the title of your question to more accurately reflect your question.
A block is like a function, a set of code to be executed at a later date. Often that block will be executed in the scope where the block was defined, but it is also possible to evaluate the block in another context:
class Foo
def initialize( bar )
# Save the value as an instance variable
#bar = bar
end
def unchanged1
yield if block_given? # call the block with its original scope
end
def unchanged2( &block )
block.call # another way to do it
end
def changeself( &block )
# run the block in the scope of self
self.instance_eval &block
end
end
#bar = 17
f = Foo.new( 42 )
f.unchanged1{ p #bar } #=> 17
f.unchanged2{ p #bar } #=> 17
f.changeself{ p #bar } #=> 42
So either you are defining the block outside the scope where #user is set, or else the implementation of client.request causes the block to be evaluated in another scope later on. You could find out by writing:
client.request("createSession"){ p [self.class,self] }
to gain some insight into what sort of object is the current self in your block.
The reason they "disappear" in your case—instead of throwing an error—is that Ruby permissively allows you to ask for the value of any instance variable, even if the value has never been set for the current object. If the variable has never been set, you'll just get back nil (and a warning, if you have them enabled):
$ ruby -e "p #foo"
nil
$ ruby -we "p #foo"
-e:1: warning: instance variable #foo not initialized
nil
As you found, blocks are also closures. This means that when they run they have access to local variables defined in the same scope as the block is defined. This is why your second set of code worked as desired. Closures are one excellent way to latch onto a value for use later on, for example in a callback.
Continuing the code example above, you can see that the local variable is available regardless of the scope in which the block is evaluated, and takes precedence over same-named methods in that scope (unless you provide an explicit receiver):
class Foo
def x
123
end
end
x = 99
f.changeself{ p x } #=> 99
f.unchanged1{ p x } #=> 99
f.changeself{ p self.x } #=> 123
f.unchanged1{ p self.x } #=> Error: undefined method `x' for main:Object
From the documentation:
Savon::Client.new accepts a block inside which you can access local variables and even public methods from your own class, but instance variables won’t work. If you want to know why that is, I’d recommend reading about instance_eval with delegation.
Possibly not as well documented when this question was asked.
In the first case, self evaluates to client.request('createSession'), which doesn't have these instance variables.
In the second, the variables are brought into the block as part of the closure.
Another way to fix the issue would be to carry a reference to your object into the block rather than enumerating each needed attribute more than once:
o = self
create_session_response = client.request "createSession" do
soap.body = {
:user => o.user,
:pass => o.pass
}
end
But now you need attribute accessors.

metaprogramming access local variables

class Foo
def initialize
bar = 10
end
fiz = 5
end
Is there a possibility to get these local values (outside the class) ?
The local variable in initialize would be lost.
You are able to get the value fiz outside of the class, but only upon defining that class, and recording the return of the definition of the class.
return_of_class_definition = (class A ; fiz = 5 ; end) would assign the value of fiz to the variable.
You can also use binding but of course, this means changing the class, which may not be allowed for the exercise.
class A
bin = 15
$binding = binding
end
p eval 'bin', $binding
No. Once a local variable goes out of scope (for bar that is when the initialize method has run - for fiz when the end of the class definition has been reached), it's gone. No trace left.
While a local variable is still in scope you can see it (well, its name) with local_variables and get and set its value with eval (though that's definitely not recommended for sanity reasons), but once it's out of scope, that's it. No way to get it back.
In ruby we have something we could call scope gates - places when a program written in ruby leaves the previous scope. Those gates are: class, module and method (def keyword). In other words after class, module of def keyword in the code you're immediately entering into a new scope.
In ruby nested visibility doesn't happen and as soon as you create a new scope, the previous binding will be replaced with a new set of bindings.
For example if you define following class:
x = 1
class MyClass
# you can't access to x from here
def foo
# ...from here too
y = 1
local_variables
end
end
local_variables method call will return [:y]. It means that we don't have an access to the x variable. You can workaround this issue using ruby's technique called Flat Scopes. Basically instead defining a class using class keyword you can define it using Class.new and pass a block to this call. Obviously a block can take any local variables from the scope where it was defined since it's a closure!
Our previous example could be rewritten to something like like that:
x = 1
Foo = Class.new do
define_method :foo do
i_can_do_something_with(x)
y = 1
local_variables
end
end
In this case local_variables will return [:x, :y].

Resources