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

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.

Related

Local variables in `class` definition scope versus `def` method scope

Here, I create a local variable in class scope:
class MyClass
x = 1
puts x
end
It prints 1 even if I don't create any instances of MyClass.
I want to use x in some method:
class MyClass
x = 1
def method
puts x
end
end
m = MyClass.new
m.method
And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?
I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.
It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?
Scope of a method is not inside the class. Each method has its own entirely new scope.
New scopes are created whenever you use the class, module, and def keywords. Using brackets, as in C, does not create a new scope, and in fact you cannot arbitrarily group lines of code using brackets. The brackets (or do...end) around a Ruby block create a block-level scope, where variables previously created in the surrounding scope are available, but variables created within the block scope do not escape into the surrounding scope afterward.
Instance methods share the scope of their instance variables with other instances methods. An instance variable defined in the scope of a class definition is available in class-level singleton methods, but not in instance methods of the class.
Illustration:
class Foo
x = 1 # available only here
#y = 2 # class-wide value
def self.class_x
#x # never set; nil value
end
def self.class_y
#y # class-wide value
end
def initialize(z)
x = 3 # available only here
#z = z # value for this instance only
end
def instance_x
#x # never set; nil
end
def instance_y
#y # never set; nil
end
def instance_z
#z # value for this instance only
end
end
Foo.class_x # => nil
Foo.class_y # => 2
Foo.new(0).instance_x # => nil
Foo.new(0).instance_y # => nil
foo3 = Foo.new(3)
foo4 = Foo.new(4)
foo3.instance_z # => 3
foo4.instance_z # => 4
You can access class-level instance variables from within instances using the class-level getter. Continuing the example above:
class Foo
def get_class_y
self.class.class_y
end
end
foo = Foo.new(0)
foo.get_class_y # => 2
There exists in Ruby the notion of a "class variable," which uses the ## sigil. In practice, there is almost never a reasonable use case for this language construct. Typically the goal can be better achieved using a class-level instance variable, as shown here.
Here, I create a local variable in class scope:
class MyClass
x = 1
puts x
end
It prints 1 even if I don't create any instances of MyClass.
Correct. The class definition body is executed when it is read. It's just code like any other code, there is nothing special about class definition bodies.
Ask yourself: how would methods like attr_reader/attr_writer/attr_accessor, alias_method, public/protected/private work otherwise? Heck, how would def work otherwise if it didn't get executed when the class is defined? (After all, def is just an expression like any other expression!)
That's why you can do stuff like this:
class FileReader
if operating_system == :windows
def blah; end
else
def blubb; end
end
end
I want to use x in some method:
class MyClass
x = 1
def method
puts x
end
end
m = MyClass.new
m.method
And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?
No, it is not. There are 4 scopes in Ruby: script scope, module/class definition scope, method definition scope, and block/lambda scope. Only blocks/lambdas nest, all the others create new scopes.
I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.
Honestly, I don't fully understand what you are saying, but no, class definition scope is not method definition scope, class definition scope is class definition scope, and method definition scope is method definition scope.
It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?
Like I said above: there are 4 scopes in Ruby. There is nothing like block scope in C. (The Ruby concept of "block" is something completely different than the C concept of "block.") The closest thing you can get is a JavaScript-inspired immediately-invoked lambda-literal, something like this:
foo = 1
-> {
bar = 2
foo + bar
}.()
# => 3
bar
# NameError
In general, that is not necessary in Ruby. In well-factored code, methods will be so small, that keeping track of local variables and their scopes and lifetimes is really not a big deal.
So just creating a class without any instances will lead to something
actually executing in runtime (even allocating may be)? That is very
not like C++. –
Check out this code:
Dog = Class.new do
attr_accessor :name
def initialize(name)
#name = name
end
end
If you execute that code, there won't be any output, but something still happened. For instance, a global variable named Dog was created, and it has a value. Here's the proof:
Dog = Class.new do
attr_accessor :name
def initialize(name)
#name = name
end
end
dog = Dog.new("Ralph")
puts dog.name
--output:--
Ralph
The assignment to the Dog constant above is equivalent to writing:
class Dog
...
...
end
And, in fact, ruby steps through each line inside the class definition and executes each line--unless the line of code is inside a def. The def is created but the code inside a def doesn't execute until the def is called.
A very common line you will see inside a class definition is:
attr_accessor :name
...which can be rewritten as:
attr_accessor(:name)
...which makes it obvious that it's a method call. Ruby executes that line--and calls the method--when you run a file containing the class definition. The attr_accessor method then dynamically creates and inserts a getter and a setter method into the class. At runtime. Yeah, this ain't C++ land anymore--welcome to NeverNever Land.
I get that class definition creates a scope, but why is it not
accessible in the method?
Because that is the way Matz decided things should be: a def creates a new scope, blocking visibility of variables outside the def. However, there are ways to open up the scope gates, so to speak: blocks can see the variables defined in the surrounding scope. Check out define_method():
class MyClass
x = 1
define_method(:do_stuff) do
puts x
end
end
m = MyClass.new
m.do_stuff
--output:--
1
The block is everything between do...end. In ruby, a block is a closure, which means that when a block is created, it captures the variables in the surrounding scope, and carries those variables with it until the the block is executed. A block is like an anonymous function, which gets passed to a method as an argument.
Note that if you use the Class.new trick, you can open two scope gates:
x = 1
MyClass = Class.new do
define_method(:do_stuff) do
puts x
end
end
m = MyClass.new
m.do_stuff
--output:--
1
Generally, ruby allows a programmer to do whatever they want, rules be damned.

Why do I need to call attr_writer methods with `self` to get them to work inside a class? [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 7 years ago.
This does not output anything:
class Test
attr_accessor :value
def run
set_value
puts value
end
def set_value
value = 6 # No 'self'
end
end
Test.new.run
Whereas this outputs '6'
class Test
attr_accessor :value
def run
set_value
puts value
end
def set_value
self.value = 6 # With 'self'
end
end
Test.new.run
Why do I need self when the method is defined already? Surely Ruby should use the method rather than creating a local variable in the set_value function?
Why do I need self when the method is defined already?
Assignment Methods
When using method assignment you must always have a receiver. If you do not have a receiver Ruby assumes you are assigning to a local variable
You have to create an instance variable with the '#' character :
value = 6 # create a local variable 'value' and set it to 6
#value = 6 # create an instance variable named 'value' and set it to 6, it will be accessible through the accessor #value
EDIT
self.value = 6
calls the method #value=(new_value), implicitly declared from attr_accessor, which sets the #value instance variable to new_value (possible from other scopes)
#value = 6
directly sets the instance variable to 6 (only possible from instance scope)
You always need to use self so ruby can know if you're not instantiating a new variable:
self.value = 6 #ruby knows it's your class variable
value = 6 #ruby thinks you're creating a new variable called value
Assignment Methods

Ruby: Why is an instance variable defined inside a class nil?

class Something
#b = [4432]
def screen
puts #b.class
end
end
s = Something.new
s.screen
outputs 'Nilclass'. Was wondering, why does an instance variable which is defined inside a class always part of NilClass?
Instance variables belong to an object (aka an instance), that's why they are called instance variables. Every instance has its own instance variables.
In your case, there are two objects: Something (which is an instance of Class) and s (which is an instance of Something). Each of those two objects has its own set of instance variables. Something has an instance variable called #b which points to [4432]. s has no instance variable named #b because you never assign to it, and uninitialized instance variables evaluate to nil.
You need to set it like this:
class Something
def initialize
#b = [4432]
end
def screen
puts #b.class
end
end
The way you did it, the variable belongs to Something class itself, not its instance. Observe:
class Something
#b = [4432]
end
s = Something.new
s.instance_variable_get(:#b) # => nil # !> instance variable #b not initialized
Something.instance_variable_get(:#b) # => [4432]
Generally the instance variable must be defined inside the constructor whereas in ruby the default constructor is initialize the syntax is
def initialize
end #these is the default constructor in ruby
so when we define the insatnce variable inside the constructor and when we create the instance of a class then that instance/object will contain the copy of instance variables
most important thing is that though the instance/object contains the instance variable the instance/object cannot access it why because by default the instance data is private so in order to access it we need to define the getters and setter for those instance variable
class Something
attr_accessor:b
def initialize
#b = [4432]
end
s=Something.new
puts"#{s.b}"
Because the variable #b does not exist!. For e.g. the following would produce the same results you see.
class Something
#b = [4432]
def screen
puts #a.class #=> note #a which is non-existent
end
end
s = Something.new
s.screen
Whereas
class Something
#b = [4432]
def screen
puts #a.class
end
def self.screen
puts #b.class
end
end
s = Something.new
s.screen #=> NilClass
Something.screen #=> Array
if you initialize #b outside the initializer, you want #b scope to be a class variable, so you have to call it ##b :
##b has the same value for all Instance of your Something Class
like :
class Somthing
##b = [4432]
def initialize
#[...]
end
def screen
puts ##b.class
end
end
#Jörg W Mittag answer is correct. I just wont to add, that defining an instance variable in class != defining instance variable in an instance of that class. To create an instance variable, in your case in s instance you need to add an initialize method witch gets triggered when new method is called on a class.
def initialize(b_value = default_value)
#b = b_value
end

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].

Can I initialize a class variable using a global variable? (ruby)

Do I have create extra method for this kind of assignment? ##variable = #global_variable Why? I want to have some variables that hold values and definitions to be accessible all through my script and have only one place of definition.
#global_variable = 'test'
class Test
##variable = #global_variable
def self.display
puts ##variable
end
end
Test.display #gives nil
In Ruby, global variables are prefixed with a $, not a #.
$global = 123
class Foo
##var = $global
def self.display
puts ##var
end
end
Foo.display
correctly outputs 123.
What you've done is assign an instance variable to the Module or Object class (not sure which); that instance variable is not in scope of the class you've defined.

Resources