Object not returning instance variable value - ruby

For some reason, when I call the method that tells me the object's size, I get nothing. This is my code:
def getSize(name)
data = Hash.new
data[:aarakocra] = "medium"
print "Got size"
return data[:aarakocra]
end
class Monster
def initialize(name, statStr, statDex, statCon, statInt, statWis, statCha)
#name = name
#statStr = statStr
#statDex = statDex
#statCon = statCon
#statInt = statInt
#statWis = statWis
#statCha = statCha
end
#size = "medium"
def displayStats
return [#statStr, #statDex, #statCon, #statInt, #statWis, #statCha]
end
def displaySize
return #size
end
end
aarakocra = Monster.new("Aarakocra", 10, 14, 10, 11, 12, 11)
if aarakocra.displaySize == "medium"
puts "Hello"
else
puts "Not true"
end
I'm expecting the output to be hello.
If someone could explain what's happening, I'd really appreciate it.

#Thrayna seems like you are initializing the instance variable #size outside of the initialize method, try moving the variable inside it. Since it is an instance variable, it gets initialized at object creation (Calling Moster.new) or when calling another method; note, that instance variables are nil by default until you initialize them.

Related

Method_missing not running when it should

I have a Team class in my program and I am trying to use method_missing
but instead of running the function when the method doesn't exist, it gives me an error:"undefined method `hawks' for Team:Class (NoMethodError)"
My code is as follows:
class Team
attr_accessor :cust_roster, :cust_total_per, :cust_name, :cust_best_player
##teams = []
def initialize(stats = {})
#cust_roster = stats.fetch(:roster) || []
#cust_total_per = stats.fetch(:per)
#cust_name = stats.fetch(:name)
#cust_best_player = stats.fetch(:best)
##teams << self
end
def method_missing(methId)
str = methID.id2name
Team.new(roster:[], per: 0, name: str.uppercase, best: 0)
end
class <<self
def all_teams
##teams
end
end
end
hawks = Team.hawks
There are a number of problems with your code. Let's go through one by one.
From the documentation,
method_missing(*args) private
Invoked by Ruby when obj is sent a message it cannot handle.
Here message refers to the method. In ruby, whenever you're calling a method on an object, you're actually sending a message to the object
To better understand this, try this in the irb shell.
1+2
=> 3
1.send(:+,2)
=> 3
Here 1 and 2 are objects of Fixnum class. You can confirm that by using 1.class. Ok, back to your question. So, a method_missing method should be called on an instance.
team = Team.new
team.hawks
If you try the above piece of code, you'll get an error saying 'fetch': key not found: :roster (KeyError)
You can get around this by passing a default value as the second parameter to fetch method. Replace your initialize method with
def initialize(stats = {})
#cust_roster = stats.fetch(:roster, [])
#cust_total_per = stats.fetch(:per, 0)
#cust_name = stats.fetch(:name, "anon")
#cust_best_player = stats.fetch(:best, "anon")
##teams << self
end
If you execute the script, you'll get a stack level too deep (SystemStackError) because of a small typo in this line.
str = methID.id2name
In the method definition, you're receiving an argument with the name of methId but inside you're trying to call methID. Fix it with
str = methId.id2name
If you execute your script, you'll again get an error saying undefined method uppercase for "hawks":String (NoMethodError)
This is because there is no uppercase method on strings. You should instead use the upcase method.
Team.new(roster:[], per: 0, name: str.upcase, best: 0)
and you should be good to go.
For more, see http://apidock.com/ruby/BasicObject/method_missing
Hope this helps!
class Team
attr_accessor :cust_roster, :cust_total_per, :cust_name, :cust_best_player
##teams = []
def initialize(stats = {roster: [], per: 0, name: "", best: 0}) # I added the default values here.
#cust_roster = stats.fetch(:roster)
#cust_total_per = stats.fetch(:per)
#cust_name = stats.fetch(:name)
#cust_best_player = stats.fetch(:best)
##teams << self
end
def method_missing(name, *args)
self.cust_name = name.to_s.upcase
end
class << self
def all_teams
##teams
end
end
end
team_hawks = Team.new #=> create an instance of Team class, I renamed the object to avoid confusions.
team_hawks.hawks #=> called method_missing which assigned the cust_name variable to "HAWKS"
team_hawks.cust_name #=> HAWKS, so cust_name is assigned to be hawks. This is to check if the assignment worked.
Hope this is what you are looking for.

Error working with oop (classes)

I just started with ruby, and just started learning oop today, after making a class, I am trying to print to console yet I keep getting this error. Does anyone know what's wrong?
undefined method `set_brand_name=' for # (NoMethodError)
Here is the code causing this error:
class Laptop
def set_brand_name(brand_name)
#brand = brand_name
end
def get_brand_name
return #brand
end
def set_color(color)
#color = color
end
def get_color
return #color
end
def set_processor(processor)
#processor = processor
end
def get_processor
return #processor
end
def set_storage(hard_drive)
#storage = hard_drive
end
def get_storage
return #storage
end
def set_memory(ram)
#memory = ram
end
def get_memory
return #memory
end
end
my_laptop = Laptop.new
my_laptop.set_brand_name = "HP"
my_laptop.set_processor = 'i7-4700k'
my_laptop.set_memory = '16gb'
my_laptop.set_storage = '750gb'
my_laptop.set_color = 'Silver'
brand = my_laptop.get_brand_name
color = my_laptop.get_color
processor = my_laptop.get_processor
memory = my_laptop.get_memory
storage = my_laptop.get_storage
This should output the message:
"""The Laptop I want is an #{brand}, it has a #{processor},
#{memory} of ram, a #{storage}, and it #{color}!!!"""
What am I doing wrong?
The problem is that you are not calling the method names as you've defined them. You defined set_brand_name without an equal sign so use:
my_laptop.set_brand_name("HP")
I would simply the getters and setters like so:
class Laptop
def brand_name=(brand_name)
#brand_name = brand_name
end
def brand_name
#brand_name
end
end
Or even better:
class Laptop
attr_accessor :brand_name
end
Then you can use it the same way:
my_laptop = Laptop.new
my_laptop.brand_name = "HP"
puts my_laptop.brand_name # => "HP"
In line 45, you are calling the method set_brand_name=, but your Laptop class doesn't have a method with that name. You need to either call the method which you do have (set_brand_name), or rename the set_brand_name method to set_brand_name=.
Note that neither of those two is idiomatic, though. Idiomatically, the method should be named brand_name= (without the set_ prefix, the "setting" part is already implied by the = sign), and you shouldn't define it manually, but programmatically using the Module#attr_writer method.
Your entire code can be condensed to:
Laptop = Struct.new(:brand_name, :color, :processor, :storage, :memory)
my_laptop = Laptop.new('HP', 'Silver', 'i7-4700k', '750gb', '16gb')
brand = my_laptop.brand_name
color = my_laptop.color
processor = my_laptop.processor
memory = my_laptop.memory
storage = my_laptop.storage
puts "The Laptop I want is an #{brand}, it has a #{processor}, #{memory} of ram, a #{storage}, and it's #{color}!!!"
Your setter methods are defined incorrectly.
Here's your definition of the set_brand_name method:
def set_brand_name(brand_name)
#brand = brand_name
end
And here's how you're calling it:
my_laptop.set_brand_name = "HP"
You're calling the method incorrectly. If you'd like to keep your definition, you should be calling it like this:
my_laptop.set_brand_name("HP")
Or, if you'd like to use the equals sign, you should define your method like this:
def set_brand_name=(brand_name)
#brand = brand_name
end
Notice the equals in the method definition? You're required to use it when you want the setter to look like a regular assignment.
However, for most trivial cases you don't need to define getters and setters manually. You can just use attr_accessor on the class and pass it the properties you want to define. Here's what your class would look like with attr_accessor:
class Laptop
attr_accessor: :brand_name, :color, :processor, :storage, :memory
end
my_laptop = Laptop.new
my_laptop.brand_name = "HP"
my_laptop.processor = 'i7-4700k'
my_laptop.memory = '16gb'
my_laptop.storage = '750gb'
my_laptop.color = 'Silver'
brand = my_laptop.brand_name
color = my_laptop.color
processor = my_laptop.processor
memory = my_laptop.memory
storage = my_laptop.storage
puts """The Laptop I want is an #{brand}, it has a #{processor},
#{memory} of ram, a #{storage}, and it #{color}!!!"""
I encourage you to try it.

Ruby Instance Methods and Variables

There is something that i don't understand about ruby class instance variable or methods**.
So i have this code that keeps on giving me this error and i cant understand
Looks ruby thinks that i am trying to call for Float.in_celsius but I want to make this call within my class instance.
#-----------------------------------
def ftoc(fr)
fr = fr.to_f
if (fr == 32)
c = 0
elsif (fr == 212)
c = 100
else
c = (fr-32.0)*(5.0/9.0)
end
return c
end
def ctof (cl)
cl = cl.to_f
f = (cl*(9.0/5.0))+32.0
return f
end
#-----------------------------------
class Temperature
attr_accessor :in_celsius, :in_fahrenheit
#class metods
def self.from_celsius(cel)
puts "from celsious\n"
puts "cel: #{cel}\n"
#in_fahrenheit = cel
#in_celsius = ctof(cel)
puts "==============================\n"
return #in_celsius
end
def self.in_celsius
#in_celsius
end
end
puts "==============================\n"
puts Temperature.from_celsius(50).in_celsius
puts Temperature.from_celsius(50).in_fahrenheit
and Error is
test.rb:54: in '<main>' : undefined method 'in_celsius' for 122.0:float (noMethod Error)
enter code here
You have a fundamental misunderstanding of how classes work in Ruby. Right now all of your variables and methods are defined at class level. That means that everything you do in the methods is acting directly on the class itself. Instead, you should create instances of Temperature.
class Temperature
# special method called when creating a new instance
def initialize celsius
#in_celsius = celsius
#in_fahrenheit = celsius * 9 / 5.0 + 32
end
def self.from_celsius celsius
new celsius # built in method to create an instance, passes argument to initialize
end
# we defined initialize using celsius, so here we must convert
def self.from_fahrenheit fahrenheit
new((fahrenheit - 32) * 5 / 9.0)
end
private_class_method :new # people must use from_celsius or from_fahrenheit
# make instance variables readable outside the class
attr_accessor :in_celsius, :in_fahrenheit
end
Temperature.from_celsius(50).in_celsius
This code isn't perfect (from_fahrenheit does a redundant conversion) but it should give you the idea of how to redesign your class.
from_celsius is returning a float which doesn't have an in_celsius method. You need it to return an instance of Temperature which would have that method.
Got to say your intent is a tad confusing, unless you have some other uses for the class Temperature, so it's bit hard to say which way you should go.
Let's see the code puts Temperature.from_celsius(50).in_celsius in details:
Call to singleton method ::from_celsius of Temperature class. That is ok (with some stranges), and t returns as instance of Float class because of result of #ctof method.
Call to instance method #in_celsius of object, which is returned from the previous method. Since it was the Float, the ruby interpreter searches for its instance method #in_celsius, hasn't find it out, and throws the NoMethodError exception.
How to fix.
Since you treat ::from_celsius as a constructor of Temperature class, I believe, that you shell to pass the floating value into the new method, and return created object. You will have class code as follows:
def initialize( value )
#in_fahrenheit = cel
#in_celsius = ctof(cel)
end
def self.from_celsius(cel)
puts "from celsious\n"
puts "cel: #{cel}\n"
temp = Temperature.new( cel )
puts "==============================\n"
return temp
end

How does this ruby custom accessor work

So the method below in class_eval dynamically creates accessors for attributes defined at runtime. It can be used, for example, to create configuration objects with attributes read from a config file (and unknown until runtime). I understanding all of it except for the else branch. If I am correct the else branch returns the attribute value (val[0]) if there is one value passed in *val. However the way its written I would expect it to return an array (val) if there is more then one value passed in *var. In particular, if I have something like the following:
value = 5
then from reading the code I would expect #value to be [=,5]. However #value returns 5 and not the array [=,5]. How is this possible?
class Module
def dsl_accessor(*symbols)
symbols.each do |sym|
class_eval %{
def #{sym}(*val)
if val.empty?
##{sym}
else
##{sym} = val.size == 1 ? val[0] : val
end
end
}
end
end
end
An equals sign is not an argument for the method, it's a part of the method name. Actually you can call an assignment like this:
value=(5)
So only the integer 5 is an argument for the function.
class Module
def dsl_accessor(*symbols)
symbols.each do |sym|
class_eval %{
def #{sym}
##{sym}
end
def #{sym}=(val)
##{sym} = val
end
}
end
end
end

Dynamically creating class in Ruby

I have a class that should look something like this:
class Family_Type1
#people = Array.new(3)
#people[0] = Policeman.new('Peter', 0)
#people[1] = Accountant.new('Paul', 0)
#people[2] = Policeman.new('Mary', 0)
def initialize(*ages)
for i in 0 ... #people.length
#people[i].age = ages[i]
end
end
end
I want to be able to define a bunch of classes similar to this one at runtime (define them once at startup) where the size of the array and the type assigned to each parameter is defined at runtime from an external specification file.
I sort of got it to work using evals but this is really ugly. Any better way?
From what I understand, you need meta-programming. Here is a snippet of code for creating classes dynamically (on the fly) with initialize method that initializes instance variables-
class_name = 'foo'.capitalize
klass = Object.const_set(class_name,Class.new)
names = ['instance1', 'instance2'] # Array of instance vars
klass.class_eval do
attr_accessor *names
define_method(:initialize) do |*values|
names.each_with_index do |name,i|
instance_variable_set("#"+name, values[i])
end
end
# more...
end
Hope you can tweak it to suit your requirements.
First off, part of the reason your example code isn't working for you is that you have two different #people variables - one is an instance variable and the other is a class instance variable.
class Example
# we're in the context of the Example class, so
# instance variables used here belong to the actual class object,
# not instances of that class
self.class #=> Class
self == Example #=> true
#iv = "I'm a class instance variable"
def initialize
# within instance methods, we're in the context
# of an _instance_ of the Example class, so
# instance variables used here belong to that instance.
self.class #=> Example
self == Example #=> false
#iv = "I'm an instance variable"
end
def iv
# another instance method uses the context of the instance
#iv #=> "I'm an instance variable"
end
def self.iv
# a class method, uses the context of the class
#iv #=> "I'm a class instance variable"
end
end
If you want to create variables one time in a class to use in instance methods of that class, use constants or class variables.
class Example
# ruby constants start with a capital letter. Ruby prints warnings if you
# try to assign a different object to an already-defined constant
CONSTANT_VARIABLE = "i'm a constant"
# though it's legit to modify the current object
CONSTANT_VARIABLE.capitalize!
CONSTANT_VARIABLE #=> "I'm a constant"
# class variables start with a ##
##class_variable = "I'm a class variable"
def c_and_c
[ ##class_variable, CONSTANT_VARIABLE ] #=> [ "I'm a class variable", "I'm a constant" ]
end
end
Even so, in the context of your code, you probably don't want all your instances of Family_Type1 to refer to the same Policemen and Accountants right? Or do you?
If we switch to using class variables:
class Family_Type1
# since we're initializing ##people one time, that means
# all the Family_Type1 objects will share the same people
##people = [ Policeman.new('Peter', 0), Accountant.new('Paul', 0), Policeman.new('Mary', 0) ]
def initialize(*ages)
##people.zip(ages).each { |person, age| person.age = age }
end
# just an accessor method
def [](person_index)
##people[person_index]
end
end
fam = Family_Type1.new( 12, 13, 14 )
fam[0].age == 12 #=> true
# this can lead to unexpected side-effects
fam2 = Family_Type1.new( 31, 32, 29 )
fam[0].age == 12 #=> false
fam2[0].age == 31 #=> true
fam[0].age == 31 #=> true
The runtime initialization can be done with metaprogramming, as Chirantan said, but if you are only initializing a few classes, and you know what their name is, you can also do it just by using whatever you read from the file:
PARAMS = File.read('params.csv').split("\n").map { |line| line.split(',') }
make_people = proc do |klasses, params|
klasses.zip(params).map { |klass,name| klass.new(name, 0) }
end
class Example0
##people = make_people([ Fireman, Accountant, Fireman ], PARAMS[0])
end
class Example1
##people = make_people([ Butcher, Baker, Candlestickmaker ], PARAMS[0])
end
Assuming you want to create different classes per type/array size at runtime:
If (like in Python) a Ruby class is defined when executed (I think it is), then you can do this:
Define your class inside a function. Have the function recieve array size and type as parameters and return the class in its result. That way, you have a sort of class factory to call for each definition in your spec file :)
If on the other hand you want to just initialize #params based on actual data, keep in mind, that Ruby is a dynamically typed language: Just reassign #params in your constructor to the new array!

Resources