Set instance variable - ruby

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

Related

How do I pull a name from a class in an Array?

Okay, this is a little hard to explain but I will try (For starters I am only just learning to code so it may be something super simple I'm missing..)
I created a few classes, I put a name in those classes, I put them in an array, I then chose one at random and try to puts the name, and it outputs blank.
Am I doing this all completely wrong? I've been learning ruby for about 3 months now so I'm sure there is a lot I don't know.
class A
attr :name
def set_name
#name = "Aaa"
end
def get_name
return #name
end
end
class B
attr :name
def set_name
#name = "Bbb"
end
def get_name
return #name
end
end
class C
attr :name
def set_name
#name = "Ccc"
end
def get_name
return #name
end
end
name_a = A.new
name_b = B.new
name_c = C.new
which_name = Array.new
which_name[0] = name_a
which_name[1] = name_b
which_name[2] = name_c
roll = rand(max 3)
puts which_name[roll].get_name
I then chose one at random and try to puts the name, and it outputs
blank.
You never called the #set_name method in your code. You can add this:
name_a.set_name
name_b.set_name
name_c.set_name
Also, you probably want to look into #attr_accessor.

Ruby: self keyword in class definition

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">

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

Undefined local variable error with simple program that adds items to array

class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names

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"

Resources