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.
Related
So, I'm trying to initialize multiple objects and add them to a list. What I'm wanting to happen is by running Market.new, I want every item from the api added as an object. Below is the code I thought might work. But, it's adding the same object to the list 100x. Is there a way to accomplish this?
def initialize
data = JSON.parse(open(BASE_URL + "markets?vs_currency=usd").read)
i = 0
# looping until we hit the end of the list. adding them all as objects.
while i < data.length
#id = data[i]["id"].to_s
#name = data[i]["name"].to_s
#symbol = data[i]["symbol"].to_s
#price = data[i]["current_price"].to_s
#price_movement_24h = data[i]["price_change_percentage_24h"].to_s
#market_cap = data[i]["market_cap"].to_s
##market << self
i += 1
end
end
This gives me the same object added to the ##market list 100x.
=> [#<Market:0x0000561b3f853f30
#id="iostoken",
#market_cap="43969067",
#name="IOST",
#price="0.0036523",
#price_movement_24h="-0.76702",
#symbol="iost">,
#<Market:0x0000561b3f853f30
#id="iostoken",
#market_cap="43969067",
#name="IOST",
#price="0.0036523",
#price_movement_24h="-0.76702",
#symbol="iost">,
I want to start by saying this is very strange Ruby code and not something you'd typically do. That's not meant as an insult, just to say that Ruby devs tend to follow the same or similar guidelines on structuring objects and this chunk of code feels like it's ported from another language.
The issue you're seeing is due to the fact that within the initialize method you're not creating any new objects but instead updating the instance variables and pushing self into a class variable. self is referencing this instance directly which means the class variable array is filling up with references to the same object. If you're adamant on keeping the code the same then you should instead push a duplicate of your object after you've updated the instance variables.
##market << self.dup
This creates a duplicate object that has a different memory address and reference.
If you're looking to write more idiomatic code you'd want to use multiple objects and not rely on class variables at all. If you're not interpolating a variable in a string use single quotes instead of double quotes. Keep object methods simple and focused on specific tasks. These are just a few things Ruby developers consider when writing code, but find what works best for you.
Take something like this for instance:
class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap
def initialize(data = {})
#id = data['id'].to_s
#name = data['name'].to_s
#symbol = data['symbol'].to_s
#price = data['current_price'].to_s
#price_movement_24h = data['price_change_percentage_24h'].to_s
#market_cap = data['market_cap'].to_s
end
end
class ImportService
def self.from_api(url)
response = JSON.parse(open(url).read) || []
response.map { |data| Market.new(data) }
end
end
You could then call this as such:
#market_data = ImportService.from_api(BASE_URL + 'markets?vs_currency=usd')
When adding self to the ##market, you should change the code to this
##market << self.dup
However, I don't think it's a good practice to use a class variable here and add self to init an array of object. Instead, you should create a new class (for example MarketImporter)
class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap
def initialize(data = {})
#id = data["id"].to_s
#name = data["name"].to_s
#symbol = data["symbol"].to_s
#price = data["current_price"].to_s
#price_movement_24h = data["price_change_percentage_24h"].to_s
#market_cap = data["market_cap"].to_s
end
end
class MarketImporter
attr_accessor :markets
def initialize
data = JSON.parse(open(BASE_URL + "markets?vs_currency=usd").read)
#markets = data.collect { |item| Market.new(item) }
end
end
Then you can init the collection by
MarketImporter.new
Why can't I access the instance variable?
let(:hotel2) { Hotel.new name: 'Premier Inn', rating: 1,
city: 'Leeds', total_rooms: 15, features: [] }
I am calling it in the initialize but it keeps throwing an incorrect argument error.
def initialize()
#name = name
#rating = rating
#city = city
#total_rooms = total_rooms
#features = features
end
Any ideas?
Your initialization signature does not match your calling signature. You are passing a hash, but not receiving one. There are a number of ways to define argument lists to make this work. Here is one:
class Hotel
def initialize(hash)
#name = hash[:name]
#rating = hash[:rating]
#city = hash[:city]
#total_rooms = hash[:total_rooms]
#features = hash[:features]
end
end
This blog post outlines how to use Ruby V2 keyword arguments. That would be another, perhaps better, way to define your initialization. Here is an example of that:
class Hotel
def initialize(name: , rating:, city:, total_rooms:, features:)
#name = name
#rating = rating
#city = city
#total_rooms = total_rooms
#features = features
end
end
You can set defaults for keyword arguments and make them mandatory. In this example, they are all mandatory.
I am trying to understand how to create a new instance of a class from within an existing class in Ruby. I found one example, on Stack Overflow, but it isn't clear how the values from the existing class get passed to the new instance. (If I am using the terminology right).
In the example below, I have two classes, RaceCar and RaceDriver. When initialized RaceDriver sets the name, experience=0, winnings=0 and makes sure there is no car assigned yet.
When initialized RaceCar sets the number of the car, the type, damage=0, racing_status :ready, and sets the driver.
What I want to do is create a method in the RaceDriver, set_driverscar, that creates a car for the driver, creating RaceCar.new but setting the driver to the name of the RaceDriver. It does not matter if it is in initialization or not, I am just trying to figure out.
How would I do this like in the example below?
I have been beating my head on this for a bit, and I must be missing something obvious but I just cannot figure it out OR find a good example that explains how to do this?
Can you help me with an answer so I can learn what I am missing here?
class RaceDriver
attr_accessor :name, :experience, :winnings, :car
def initialize(attrs = {})
set_name(attrs [:name])
#experience = 0
#winnings = 0
#car = nil
end
private
def set_name(obj)
obj == nil ? no_name : #name = :name
end
def no_name
raise 'There is no name for this driver'
end
end
class RaceCar
attr_accessor :racing_number, :type, :damage, :racing_status, :driver
def initialize(attrs = {})
#racing_number = generate_number
#type = "stock"
#damage = 0
#racing_status = :ready
set_driver(attrs[:driver])
end
def disable
#racing_status = :disabled
end
private
def generate_number
rand(20..99)
end
def set_driver(obj)
obj == nil ? no_driver : #driver = obj
end
def no_driver
raise "A car has to have a driver to compete!"
end
end
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.
I'm making a game with a Board class Cell class. The Board class needs to be initialized with a unique instance variable for each Cell. I can hardcode it so that it works, but it seems inelegant and doesn't allow the size of the board to be chosen by the user at runtime. Here's what I have:
class Board
def initialize
#cell_1 = Cell.new(1)
#cell_2 = Cell.new(2)
#cell_3 = Cell.new(3)
#cell_4 = Cell.new(4)
#cell_5 = Cell.new(5)
#cell_6 = Cell.new(6)
#cell_7 = Cell.new(7)
#cell_8 = Cell.new(8)
#cell_9 = Cell.new(0)
#cells = [#cell_1, #cell_2, #cell_3,
#cell_4, #cell_5, #cell_6,
#cell_7, #cell_8, #cell_9]
end
end
I think I could use a loop to create a hash with unique key names pointing to unique Cell objects, but I don't know how I could make unique instance variables with a loop.
If you don't need to create each instance variables (#cell_1, #cell_2, ...), you can use Enumerable#map:
#cells = [*1..8, 0].map { |i| Cell.new(i) }
If you really need to refer every instance variable by name you can do something like this.
class Board
def initialize
#cells = (1..9).to_a.map { |i| Cell.new(i) }
end
def method_missing(method, *args, &block)
if method =~ /^cell_[1-9][0-9]*$/
index = method[/\d+/].to_i
#cells[index-1]
else
super
end
end
end
In this way you can call:
board = Board.new
board.cell_1 #=> first cell
Of course I'd use the solution proposed by #falsetru.