Implementation of attr_accessor [duplicate] - ruby

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 5 years ago.
Excuse me for the noob question.Please explain me outputs of the below ruby programme for implementing attr_accessor.
class SimpleService
attr_accessor :name
def initialize(name)
#name = name
end
def process
if false # some condition met
name = 'Akshay'
end
name
end
end
When I execute this class
SimpleService.new('John Doe').process
=> nil
Why is the result nil?
when I use self explicitly to name
def process
if false # some condition met
self.name = 'Akshay'
end
name
end
Now the output is
SimpleService.new('John Doe').process
=> "John Doe"
why is the result now "John Doe"?
I am a beginner in ruby.
Thanks in advance!

The thing is when you call name = you implicitly declare new local variable. Try this:
def process
name = 'Akshay'
puts local_variables.inspect
end
Why is it that way is a complicated question, discussed many times there and here. The setter always requires in explicit receiver. Period.
Once you have the line name = 'Akshay' inside a method, you introduce a new local variable and this method’s scope gets extended with new local variable name, despite how is was declared. It’s basically done by ruby parser.
And local variables take precedence over instance methods. That is why what is returned in the last line is a local variable. That was apparently not set, due to falsey condition above. Hence nil.

Related

Does Ruby's define_method have special access to instance variables? [duplicate]

This question already has answers here:
Calling Instance Variables without #
(4 answers)
Closed 2 years ago.
I am learning Ruby and have stumbled upon some code similar to the one below, which shows the difference between instance variables and class instance variables. I've tested it in my console and it works like described (outputs "John"). What I don't understand is how define_method accesses the #name instance variable without preceding name with a #? Does it have a special capability that allows it to do so?
class User
attr_reader :name
def self.name
"User"
end
def initialize(name)
#name = name
end
define_method(:output_name) do
puts name
end
end
user1 = User.new("John")
user1.output_name #=> “John”
It's about scope
define_method(:output_name) do
puts name
end
The puts name part of this has instance scope.
Therefore it has access to instance methods such as the one generated by
attr_reader :name

Why can I change the method name when I call a setter method from a class? [duplicate]

This question already has answers here:
confused with Ruby accessor methods
(2 answers)
Closed 3 years ago.
I'm looking at this example:
class Person
attr_reader :name, :age #creates getter method's age & name
def initialize(name)
#name = name
end
def age=(a) # creates setter method for age
#age = a
end
end
mike = Person.new('Mike')
mike.age = 20 # calling setter method
mike.age # calling getter method, returns 20
And I'm trying to understand why "mike.age = 20" is the equivalent to saying "mike.age=(20)".
I understand that we don't have to use parentheses in Ruby for the arguments that we're passing into the method. So I know why "mike.age = 20" is the same as "mike.age= 20". However, I'm having trouble understanding why we can put a space after "age". Isn't the equals sign a part of the method name? For example, I know I can't say:
def is_even?(n)
n.even?
end
p is_even ? 3
Because the question mark is a part of my method name, I know that I can't separate it with a space. So why am I able to separate "age" and the equals sign with a space in the setter method in the first example?
Here's the StackOverflow link where 'mu is too short' answers this:
Ruby setter method syntax method=(value) - Comparison to Java
When you add two numbers, for example
4 + 4
This is what's going on
4.send(:+, 4)
The send method just invokes a method. The first argument is the method you want to call passed in as a symbol :+ and the second argument is the argument you want to pass into the method 4
In your case
mike.age = 20
is actually this
mike.send(:age=, 20)
Here, we are calling the age= method and passing in 20 as the argument to age=
Heres the link to the Ruby docs about the send method https://ruby-doc.org/core-2.6.4/Object.html#method-i-send

ruby attr_accessor/instance method confused [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 4 years ago.
I am trying to understand the attr_accessor, and while digging I get pretty confused with the following behaviour:
class Item
def change_price
price=(2)
end
def price=(value)
#price = value
end
def price
#price
end
end
my_item = Item.new
p my_item.price
my_item.change_price
p my_item.price
=> nil
nil
I would expect the price to be set to 2. Clearly I totally misunderstood something that I thought obvious.
Would anybody be kind enough to explain me where I am being thick?
Thank you
Attribute setter (any function trailing with an equal sign) must be called on the explicit receiver. Otherwise, the local variable price is being created and assigned to the value.
Fix:
def change_price
# price=(2)
self.price=(2)
end

Questions about Ruby koans about_classes.rb

Here're the codes from about_classes.rb. I'm not very sure about why the answers are [ ] and [:#name]
class Dog2
def set_name(a_name)
#name = a_name
end
def test_instance_variables_can_be_set_by_assigning_to_them
fido = Dog2.new
assert_equal [ ], fido.instance_variables
#In this case, fido doesn't have any instance_variables,
because it is assigned to a new "Dog2" Hash/class which has none methods?
fido.set_name("Fido")
assert_equal [:#name], fido.instance_variables
#In this case, fido has an instance_variable,
because it uses the set_name methods inherited from "Dog2" classes?
end
assert_raise(SyntaxError) do
eval "fido.#name"
# NOTE: Using eval because the above line is a syntax error.
end
#So Eval here means that if the above returns "fido.#name", give it a SyntaxError?
I added some comments under those 2 cases, see if I understand it correctly.
When the first assert_equal is called, the Dog2 instance (fido) has no instance variables, because none were defined in the initializer or in any other way.
When set_name is called the #name instance variable gets set, so by the time the second assert_equal is called the fido instance does have an instance variable, one, #name, so the fido.instance_variables method returns an array with that one symbol.
Update
In response to the questions you pose in the comments in your code sample:
No, your first two comments are not accurate. It's not because it has no methods, it's because it has no instance variables. The fido instance does have a method, the set_name method.
Your second comment is not accurate because there's no inheritance going on here, fido is an instance of Dog2 and so once the set_name method has been called it has an instance variable, because #name is initialized in that set_name method.
Your final comment/question about the eval is just because if the authors had actually written just fido.#name then the code itself would have failed to run, they wanted it to run but display that if you'd written fido.#name in your code then Ruby would have exited and refused to run your code because of that syntax error.
Another Update
After another question from the OP I wanted to just add, although #name exists inside the set_name method, the point really of this example is to show that in Ruby until set_name is called that #name variable doesn't exist yet. In other languages you would define all the instance variables up front, so they'd always exist for any instantiated object of that class.
Ruby is a much more dynamic language and so until that #name = a_name line of code is actually executed, the #name variable doesn't exist, so is not returned in fido.instance_variables
This would also be true even if that method is called, but that #name = a_name line isn't executed, so, eg.
class Dog2
def set_name a_name
if false
#name = a_name
end
end
end
fido = Dog2.new
fido.set_name "Fido"
fido.instance_variables # => []
Hope that helps.

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

Resources