Ruby: self keyword in class definition - ruby

Is replacing self.name.split with #name.split the same thing?
class Person
attr_accessor :name
def initialize(name)
#name = name
end
def normalize_name
self.name.split(" ").collect{|w| w.capitalize}.join(" ")
end
end

Yes it has the same outcome, because you defined a attr_accessor which gives you getter and setter methods
class Person
def name
#name
end
def name=(str)
#name = str
end
end
so you can call the function name using self receiver but if you get rid of the attr_accessor from your class you can't use self.name to get the value of name
class Person
def initialize(name)
#name = name
end
def normalize_name
self.name.split(" ").collect{|w| w.capitalize}.join(" ")
end
end
p = Person.new('John Doe')
p.normalize_name
NoMethodError: undefined method `name' for #<Person:0x00557e98300f68 #name="John Doe">

Related

How do I get the string value in this string interpolation

I've been trying to get this code to output:
'Mary has a pet called Satan.'
But what I get is:
'Mary has a pet called #<Cat:0x00000002784c20>'
Code Below:
class Person
def initialize(name)
#name = name
#pet = nil
#hobbies = []
end
def describe()
puts "This persons name is #{#name}."
puts "#{#name}'s hobbies are:"
#hobbies.map { |hobby| puts hobby }
if #pet == nil
puts "#{#name} has not got any pets."
else
puts "#{#name} has a pet called #{#pet}"
end
end
attr_accessor :pet, :hobbies
end
class Cat < Animal
def initialize(name)
#name = name
end
end
satan = Cat.new("Satan")
mary = Person.new("Mary")
mary.pet = satan
mary.describe
Thanks for all your help.
In your describe() function, you are calling the object Cat without specifing the name.
But if you call #{pet.name} it will throw:
<undefined method `name' for #<Cat:0x0055d750a1a450 #name="Satan">
You have to allow the access to the variable name in the Cat class first with attr_accessor
class Cat < Animal
attr_accessor :name # First allow access
end
class Person
def describe()
puts "#{#name} has a pet called #{#pet.name}" # Then call the pet's name!
end
end

Ruby access instance variables from another method

I have a class and I try to access an instance variable and I noticed I can do it in both ways by addressing it with # and without it. Why si there no error when I call it without a # puts "name : #{name}"?
class Blabla
attr_accessor :name
def initialize(blabla)
#name = blabla
end
def populate()
puts "name : #{name}"
puts "name : #{#name}"
end
end
As hinted in the comments:
attr_accessor :name
is shorthand for:
def name
#name
end
def name=(name)
#name = name
end
So expanding your code we have:
class Blabla
def name
#name
end
def name=(name)
#name = name
end
def initialize(blabla)
#name = blabla
end
def populate()
puts "name : #{name}"
puts "name : #{#name}"
end
end
#{name} references the method name which returns #name.
#{#name} references #name directly.
Also be sure to understand attr_reader, attr_writer methods.

Adapter Pattern in ruby: Accessing Your Instance Variables

I am studying the adapter pattern implementation in ruby. I want to access an instance variable within the adapter module definition. Take a look at the following code:
module Adapter
module Dog
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: woof!"
end
end
module Cat
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: meow!"
end
end
end
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
def speak
self.adapter.speak
end
def adapter
return #adapter if #adapter
self.adapter = :dog
#adapter
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
To test it out I did the following:
animal = Animal.new("catdog")
animal.adapter = :cat
animal.speak
I want it to return the following:
catdog says: meow!
Instead it says:
Adapter::Cat says: meow!
Any tips on how I can get access to the Animal#name instance method from the adapter module? I think the issue is that my adapter methods are class-level methods.
Thanks!
You need to use your Module as a mixin and provide a way to keep track of which module is active, the methods don't seem to be overwritten by reincluding or reextending so I took the extend and remove methods I found here.
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
module Cat
def speak
puts "#{name} says: meow!"
end
end
def extend mod
#ancestors ||= {}
return if #ancestors[mod]
mod_clone = mod.clone
#ancestors[mod] = mod_clone
super mod_clone
end
def remove mod
mod_clone = #ancestors[mod]
mod_clone.instance_methods.each {|m| mod_clone.module_eval {remove_method m } }
#ancestors[mod] = nil
end
end
class Animal
include Adapter
attr_accessor :name, :adapter
def initialize(name)
#name = name
#adapter = Adapter::Dog
extend Adapter::Dog
end
def adapter=(adapter)
remove #adapter
extend Adapter::const_get(adapter.capitalize)
#adapter = Adapter.const_get(adapter.capitalize)
end
end
animal = Animal.new("catdog")
animal.speak # catdog says: woof!
animal.adapter = :cat
animal.speak # catdog says: meow!
animal.adapter = :dog
animal.speak # catdog says: woof!
This is because name inside of the module context refers to something entirely different than the name you're expecting. The Animal class and the Cat module do not share data, they have no relationship. Coincidentally you're calling Module#name which happens to return Adapter::Cat as that's the name of the module.
In order to get around this you need to do one of two things. Either make your module a mix-in (remove self, then include it as necessary) or share the necessary data by passing it in as an argument to speak.
The first method looks like this:
module Adapter
module Dog
def self.speak(name)
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def speak
self.adapter.speak(#name)
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
That doesn't seem as simple as it could be as they basically live in two different worlds. A more Ruby-esque way is this:
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
extend(#adapter)
end
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

Resources