Ruby self.name or #name [duplicate] - ruby

This question already has answers here:
Instance variable: self vs #
(6 answers)
Closed 5 years ago.
I have a question about what the best way is to reference a class variable in Ruby.
Here is a class I made:
class Person
attr_accessor :name
def initialize(name)
#name = name
end
def say_name
puts #name
end
def say_name2
puts self.name
end
end
bob = Person.new("Bob")
bob.say_name
=> "Bob"
bob.say_name2
=> "Bob"
Both of the "say_name" methods seems to work as intended. Why use the #variable vs the self.variable??

attr_accessor :name just creates method name, which returns #name variable, something like
def name
#name
end
instead of you.
Without attr_accessor both self.name and name will not work and return NoMethodError.
#name will work in all cases.

Related

Calling Custom Writers Within Instance Methods: Why is self not implicit? [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 6 years ago.
I am familiar with the conventional way of using attr_accessors within the initialize method:
class Dog
attr_accessor :name
def initialize(name)
#name = name
end
end
dog = Dog.new("Denver")
p dog.name
=> "Denver"
I was playing around with custom writers and readers. I was suprised to realize the following:
Within instance methods: you can call upon a reader method without explicitly specifying self because self is implicit.
Within instance methods: you MUST explicitly call self to call upon a writer method. self IS NOT implicit for attr_writers
Example 1 to show that self is implicit when calling attr_reader methods within instance methods:
class Dog
def name
#name
end
def name=(val)
#name = val
end
def initialize(name)
#name = name
end
def call_name
# no need to say: p self.name
p name
end
end
dog = Dog.new("Denver")
dog.call_name
=> "Denver"
Example 2 to show that self IS NOT implicit when calling upon attr_writers within instance methods. In other words: the instance variable is not getting set because I did not prepend the writer method below with self:
class Dog
def name
#name
end
def name=(val)
#name = val
end
def initialize(name_val)
# works as expected with: self.name = name_val
name = name_val
end
def change_name
# works as expected with: self.name = "foo"
name = "foo"
end
end
dog = Dog.new("Denver")
p dog.name
dog.change_name
p dog.name
=> nil
=> nil
Question: why isn't self implicit within instance methods when calling attr_writers? Why do I have to explicitly specify self for attr_writers within instance methods?
Question: why isn't self implicit within instance methods for
attr_writers?
Because defining a local variable takes precedence.
So here:
name = "foo"
you do not write an attribute, but defining a local variable name.

How can I create a to_s with an instance variable pointing to an array of objects from another class?

With the code below, I would like to create a to_s method that prints out information as such:
Southside has 3 team members.
Those members are: Dario, who is 22 years old. Ted, who is 21 years old. Bob, who is 44 years old.
Currently, I get this:
Southside has 3 team members.
Those members are:
[#<Person:0x000000025cd6e8 #name="Dario", #age=22>, #<Person:0x000000025cd670 #name="Ted", #age=21>, #<Person:0x000000025cd620 #name="Bob", #age=44>].
#<Team:0x000000025cd7d8>
The part I'm finding difficult is accessing the instance variables of the Person class objects that are in the Team members array.
Here are the two classes:
class Team
attr_accessor :name, :members
def initialize(name)
#name = name
#members = []
end
def <<(person)
members.push person
end
def to_s
puts "#{#name} has #{#members.size} team members."
puts "Those members are: #{#members}."
end
end
class Person
attr_accessor :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
south_side_bowlers = Team.new("Southside")
south_side_bowlers << Person.new("Dario", 22)
south_side_bowlers << Person.new("Ted", 21)
south_side_bowlers << Person.new("Bob", 44)
puts south_side_bowlers
Define to_s ("#{#name}, who is #{#age} years old") for the Person class. Then you can do #members.map{ |m| m.to_s}.join('. ')
First off, you don't want to have puts in a to_s method. Instead just return the string. Second, the variable members is probably not actually what you want outputted in the method. Try this instead
def to_s
%Q(#{#name} has #{#members.size} team members. Those members are #{#members.map{|i| "#{i.name} who is #{i.age}"}.join(', ')})
end

I can not use in-class assigned property of a class in Ruby

Well, I can do this:
class Person
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.name = 'Dave'
p.greeting # "Hello Dave"
but when I decide to assign the property in the class itself it doesnt work:
class Person
attr_accessor :name
#name = "Dave"
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.greeting # "Hello"
This is the default behavior, albeit a confusing one (especially if you're used to other languages in the OOP region).
Instance variables in Ruby starts being available when it is assigned to and normally this happens in the initialize method of your class.
class Person
def initialize(name)
#name = name
end
end
In your examples you're using attr_accessor, this magical method produces a getter and a setter for the property name. A Person#name and Person#name=, method is created which overrides your "inline" instance variable (that's why your first example works and the second one doesn't).
So the proper way to write your expected behaviour would be with the use of a initialize method.
class Person
def initialize(name)
#name = name
end
def greeting
"Hello, #{#name}"
end
end
Edit
After a bit of searching I found this awesome answer, all rep should go to that question.
Think of a Person class as a blueprint that you can create single person instances with. As not all of these person instances will have the name "Dave", you should set this name on the instance itself.
class Person
def initialize(name)
#name = name
end
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
david = Person.new("David")
p david.greeting
# => "Hello David"
mike = Person.new("Mike")
p mike.greeting
# => "Hello Mike"

Set instance variable

If I have a specific value assigned to an instance variable, how do I define a method to reassign the value of this variable? When I run the code below in rspec, I keep getting the original value.
class Test
def name
#name = 'me'
end
def name=(input)
#name = input
end
end
def name
#name = 'me'
end
Every time you call the method above, you set #name to 'me' and return it.
I believe you are looking for the ||= operator
def name
#name ||= 'me' # only set #name to 'me' if it is not already set
end
IMO, the best way to accomplish a default value for #name is:
class Test
attr_accessor :name
def initialize
#name = 'me'
end
end
example:
t = Test.new
t.name
# => "me"
t.name = 'foo'
# => "foo"
t.name
# => "foo"
Because you're setting the #name variable in the getter, where you should only be returning it. Like so:
class Test
def name
#name
end
def name=(input)
#name = input
end
end
Or more simply you should just use the attr_accessor method to declare boilerplate versions of the getter and setter methods. Like so:
class Test
attr_accessor :name
end
The initial value should be set in the constructor method.
class Test
def initialize
#name = 'me'
end
def name
#name
end
def name=(input)
#name = input
end
end
And you could use attr_accessor to make you code simple:
class Test
attr_accessor :name
def initialize
#name = 'me'
end
end

express attr_accessor in the long classic way of writing setter and getter methods?

How can i express the attributes methods using normal method styles (the
long coding way) to define them?
attr_accessor :name
attr_writer :name
attr_reader :legs, :arms
My own try is below. If I'm wrong, then correct it for me by re-typing
it.
My answer is:
def name=(name)
#name = name
end
def name=(legs,arms)
#name = legs, arms
end
def name
#name
end
You're using #name for a few different tasks in your re-written version. Sometimes it stores the name, and sometimes it stores a tuple of legs and arms.
What you really want is the far more verbose:
def name=(name)
#name = name
end
def name()
#name
end
def legs()
#legs
end
def arms()
#arms
end
It's a good thing you never want to assign to legs or arms, because that'd be two more pointless methods.

Resources