Why doesn't this method initialize the variables shown? - ruby

I get nil, nil but I expected the result to be 9, 'My Soduko'
class Soduko
attr_accessor :name, :rows, :columns
def initialize
rows = 9
columns = 9
name = 'My Soduko'
end
end
new_soduko= Soduko.new
puts new_soduko.rows
puts new_soduko.name
$ ruby soduko.rb
nil
nil
I thought new would use the initialize method and set those attributes?

What you need is an instance variable in the initialize method. You create an instance variable by prefixing the name with #. When you make a new Soduko object with new_soduko= Soduko.new you want to set instance variables for that particular object. Without the # you have just created local variables in the initialize method.
class Soduko
attr_accessor :name, :rows, :columns
def initialize
#rows = 9 # adding # makes these instance variables.
#columns = 9
#name = 'My Soduko'
end
end
new_soduko= Soduko.new
puts new_soduko.rows
puts new_soduko.name

Related

Using a "?" when initialising an instance variable

I am trying to have sushiable? return an instance variable set in initialise but I can't seem to get it to work. What am I doing wrong?
attr_accessor :weight, :value, :sushiable?
def initialize (weight,value, sushiable?)
#weight = weight
#value = value
#sushiable = sushiable?
end
# def sushiable?
# false
# end
Using ? is only valid for methods names, not for variables. So, the correct way would be:
attr_accessor :weight, :value, :sushiable
def initialize (weight, value, sushiable)
#weight = weight
#value = value
#sushiable = sushiable
end
def sushiable?
sushiable
end

Ruby inheritance nil class

I'm trying to make a game but I am having problems settinga default value for an attribute and having a different default value for each subclass.
Here is the problem:
class Player
attr_accessor :hp
#hp = 2
end
class Harper < Player
#hp = 5
end
bill = Harper.new.hp #=>nil
I'm expecting Harper.new.hp to be 5 but it's showing nil instead, and I don't understand why.
The problem with your initialization is that it exists at the class level. That is, you are creating a class instance variable (confusing?) not an object-instance variable as you expect.
In order to create an instance variable you need to do it in a method run at the instance level, like the initialize method which runs when you create an object with the "new" method.
Example:
class Hello
#world = "World!"
def initialize
#to_be_or_not_to_be = "be!"
end
end
=> :initialize
inst = Hello.new
inst.instance_variables
=> [:#to_be_or_not_to_be]
Hello.instance_variables
=> [:#world]
inst.class.instance_variables
=> [:#world]
You need to place your assignments on an initialize function:
class Player
attr_accessor :hp
def initialize
#hp = 2
end
end
class Harper < Player
def initialize
super ## May not be necessary for now.
#hp = 5
end
end
bill = Harper.new.hp
# => 5
new class method runs instance method initialize, so your code should look like:
class Harper < Player
def initialize
#hp = 5
end
end

Ruby make 2 different constructors with same ammount of parameters [duplicate]

This question already has answers here:
In Ruby is there a way to overload the initialize constructor?
(7 answers)
Closed 9 years ago.
I've got a class just like this:
class Equipment
attr_reader :text
attr_reader :name
attr_reader :array1
attr_reader :number
end
then, I want to make 2 constructors with 3 parameters each:
1º one -> (text,name,array1)
2º one -> (text, name,number)
The first one as an argument has an array and the other one has an integer (1,2...), so I need to define both constructors so when I create an object of this class it makes a difference between array or integer as the 3º argument.
Any ideas?
EDIT.: I thought this:
def initialize(text = "", name = "", array = array.new, number =0)
#text = text
#name = name
#array1 = array
#number = number
end
(initializing all of them) then:
def Equipment.newc_witharray(sometext, somename, somearray)
#text = sometext
#name = somename
#array1 = somearray
end
def Equipment.newc_withint(sometext, somename, somenumber)
#text = text
#name = name
#number = somenumber
end
and finally calling objects like this:
character1 = Equipment.newc_withint("Barbarian", "conan", 3)
shouldn't this work?
You can create as many constructors as you want on the class with whatever name you want. There is one constructor new, which is inherited from Object, and that can be used to write other constructors. What other answers mention as the constructor, namely the instance method initialize is not a constructor. That is the method called by the constructor method new by default.
class Foo
def self.new1 text, name, array1
obj = new
# do something on obj with text, name, array1
obj
end
def self.new2 text, name, number
obj = new
# do something on obj with text, name, number
obj
end
end
Foo.new1(text, name, array1)
Foo.new2(text, name, number)
There are various ways to achieve this.
Hash arguments
You could pass a hash and extract the values you're interested in:
def initialize(options={})
#text = options.fetch(:text) # raises KeyError if :text is missing
#name = options.fetch(:name) # raises KeyError if :name is missing
#array = options.fetch(:array, []) # returns [] if :array is missing
#number = options.fetch(:number, 0) # returns 0 if :number is missing
end
Keyword arguments
In Ruby 2.0 you can use keyword arguments with default values:
def initialize(text: text, name: name, array: [], number: 0)
#text = text
#name = name
#array = array
#number = number
end
Switching on argument type
This makes the method harder to read, but would work, too:
def initialize(text, name, number_or_array)
#text = text
#name = name
#number = 0
#array = []
case number_or_array
when Integer then #number = number_or_array
when Array then #array = number_or_array
else
raise TypeError, "number_or_array must be a number or an array"
end
end
Built into the language, no, Ruby does not give you that ability.
However, if you want that ability, I would create an initialize method which takes a hash as its parameter. Then you could create an instance of the class using any number of parameters.
E.g:
class Equipment
attr_reader :text, :name, :array1, :number
def initialize(options)
[:text, :name, :array1, :number].each do |sym|
self.send(sym) = options[sum]
end
end
end
The ruby interpreter wouldn't be able to differentiate between the constructors, as the types are not known until runtime :(
However, you can use a very nice workaround:
class Foobar
def initialize(h) # <-- h is a hash
# pass combination of params into the hash, do what you like with them
end
end
and then, using this pattern, you can pass any combination of params into the constructor:
foobar = Foobar.new(:foo => '5', :bar => 10, :baz => 'what?')

Why are my instance variables not existing across methods of the same class in ruby?

I am doing the ruby koans and I am on the DiceSet project. I've made the DiceSet class but my instance variables don't seem to persist with the instance like I thought they would. My code is
class DiceSet
attr_reader :values
#values = []
puts #values.class
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
The koan then uses my DiceSet class with
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is?(Array)
I put the puts commands in there to follow whats happening with the #values instance variable and only the first puts #values.class says its an Array class. All the others are returning NilClass. Am I using instance variables incorrectly or is there something else I am missing? Do instance variables get deallocated after a method call?
EDIT: My class works correctly now that I have put #values = [] in the roll method as suggested below. My only question now, is why the roll method thinks that #values is a NilClass instead of an array when I put #values = [] in an initialize method
In Ruby everything are objects. The Ruby interpreter assumes that all instance variables belong to the current object self. This is also true in a class definition. The role of self belongs to the class itself, so the instance variable #values belongs to the class. Don’t get confused! Instance variables of the class are different from instance
variables of that class’s objects. Also you don't need specify return keyword explicitly Try this:
class DiceSet
attr_accessor :values
def roll(number_of_rolls)
#values = []
(1..number_of_rolls).each do |roll|
#values << (1..6).to_a.sample
end
#values
end
end
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is_a?(Array)
Each DiceSet instance has its own #values, and furthermore, the class DiceSet also has its own #values. They are all different from one another. If you want the instances and the class to share the same variable, you should use a class variable ##values.
Just put the declaration of #values = [] in the initialized method and your code should work as expected.
class DiceSet
attr_reader :values
def initialize()
#values = []
end
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
Try this:
class Cat
attr_accessor :age
def initialize
#age = 12
end
#age = 6
def meow
puts "I'm #{#age}"
end
def self.meow
puts "I'm #{#age}, going on #{#age+1}"
end
end
Cat.age = 4 # => NoMethodError: undefined method `age=' for Cat:Class
p Cat.age # =? NoMethodError: undefined method `age' for Cat:Class
Cat.meow # => I'm 6, going on 7
cat = Cat.new
p cat.age # => 12
cat.meow # => I'm 12
cat.age = 20 # => 20
cat.meow # => I'm 20
Were I to add
class << self
attr_accessor :age
end
the first three lines of output would become:
Cat.age = 4 # => 4
p Cat.age # => 4
Cat.meow # => I'm 4, going on 5

Ruby metaprogramming: adding #variables to existing 'initialize' method (using class_eval) [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Ruby.Metaprogramming. class_eval
I have this little project, the goal is to create a 'attr_accessor_with_history' method, that will keep a record of every single value assigned to variables created by it. Here's the code :
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s # make sure it's a string
attr_reader attr_name # create the attribute's getter
attr_reader attr_name+"_history" # create bar_history getter
a = %Q{
def initialize
##{attr_name}_history = [nil]
end
def #{attr_name}
##{attr_name}
end
def #{attr_name}=(new_value)
##{attr_name}=new_value
##{attr_name}_history.push(new_value)
end }
puts a
class_eval(a)
end
end
Now, when I test the script for one variable. It works fine. But when I try to create two or more variables (like this) ....
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :lab
end
a = Foo.new
a.bar = 45
a.bar = 5
a.bar = 'taat'
puts a.bar_history
b = Foo.new
b.lab = 4
b.lab = 145
b.lab = 'tatu'
puts b.lab_history
....Ruby gives out a "no-existing 'push' method for (class_eval) bar_history.push(new_value)". I think that 'initialize' method gets overriden on the second call of attr_accessor_with_history, so the record for the first variable gets destroyed.
I have no idea how to get around this. I already tried calling 'super' . Any clue ?
In your setter method just check if the the history instance variable is already initialized:
def #{attr_name}=(new_value)
##{attr_name}=new_value
##{attr_name}_history ||= [nil]
##{attr_name}_history.push(new_value)
end
You'll need another getter for your history variable that sets your default value if it was not set before:
def #{attr_name}_history
##{attr_name}_history ||= [nil]
end
Then you could remove your initialize method, that was btw vulnerable to be overwritten.

Resources