I am doing a task that requires me add some products together and give a 10% discount providing the total is above £60. I have done the following:
class Checkout
def initialize (rules)
#rules = rules
#cart = []
end
def scan (item)
if product == Product.find(item)
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
def total
#cart = #rules.apply #cart
end
def self.find item
[item]
end
co = Checkout.new(Promotional_Rules.new)
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(3)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(3)
co.scan(1)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(1)
co.scan(3)
puts "Total price: #{co.total}"
puts
However when I run this in irb I get undefined variable or method product. Sounds a bit daft but this should work.
You're using one too many equal signs
def scan (item)
# if product == Product.find(item)
if product = Product.find(item) # <--- should be this
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
Of course, then you'll get a different error since find doesn't exist on Product yet... which I think you're trying to define here:
def self.find item # self should be changed to Product
[item]
end
Then you're going to get an error for apply not existing for Promotional_Rules ...
One of the best ways to debug these errors is follow the stack traces. So for the last error I get the following message:
test.rb:53:in `total': undefined method `apply' for #<Promotional_Rules:0x007f94f48bc7a8> (NoMethodError)
from test.rb:72:in `<main>'
That's basically saying that at line 53 you'll find apply hasn't been defined for #rules which is an instance of Promotional_Rules. Looking at the Promotional_Rules class you've clearly defined that method as apply_to_item and not apply. If you keep following and fixing the rabbit trails like this for stack traces you'll be able to debug your program with ease!
Related
I am new to Ruby and have been following a couple of tutorials to learn more about it.
Here's the baseline code. I have a Publisher and a class, Checkout, that includes this Publisher.
module MyPublisher
def subscribe(subscribers)
#subscribers += subscribers
end
def publish(event, *payload)
#subscribers ||= []
#subscribers.each do |subscriber|
subscriber.public_send(event.to_sym, *payload) if subscriber.respond_to?(event)
end
end
end
class Checkout
include MyPublisher
end
checkout = Checkout.new
items = []
checkout.subscribe "event-item-added" do |data|
# How can I get here inside this listener? So that "item << data" is executed?
puts "checkout received an item!"
items << data
end
queue.publish "event-item-added", 52
queue.publish "event-item-added", 90
queue.publish "event-item-added", 16
My question is: how to get the data that is published inside the ...do |data| block/listener? (I also put a comment to show where I am talking about).
I'm trying to create a new hash (group) to which I'll pass values for name, groceries, fuel_and_accommodations and recreational_activities. Actually, eventually I'll need a hash nested within the group hash (for each traveler). My issue right now is that I get this message:
undefined local variable or method `group' for main:Object
(repl):5:in `user_name'
(repl):18:in `block in enter_expenses'
(repl):15:in `times'
(repl):15:in `enter_expenses'
(repl):34:in `'
I'm just learning Ruby. Any help would be greatly appreciated!
group = Hash.new
def user_name
puts "What is the name of this traveler?"
group["name"]= gets.chomp.capitalize
end
def enter_expenses
puts "Welcome to the Expense Tracker System!\n".upcase
puts "__________________________________________"
puts "\nUse this system to track your group's expenses when traveling."
print "Ready to get started? Enter yes to continue"
ready_to_expense = gets.chomp.downcase
4.times do
if ready_to_expense == "yes"
puts "Welcome #{user_name}! Enter your expenses below:\n"
puts "Amount spent on groceries:"
group["groceries"]= gets.chomp.to_f
puts "Amount spent on fuel & accommodations:"
group["fuel_and_accommodations"]= gets.chomp.to_f
puts "Amount spent recreational activities:"
group["recreational_activities"] = gets.chomp.to_f
elsif "Please come back when ready to enter your expenses."
end
end
end
enter_expenses
create_travelers
puts "__________________________________________"
puts "Thanks for using the expense tracker system!".upcase
Local variables in Ruby does not get into methods; methods declare their own scope, they don’t act like closures. You might use instance variable instead:
#group = Hash.new # NOTE #
...
def enter_expenses
...
4.times do
if ready_to_expense == "yes"
#group["groceries"]= gets.chomp.to_f # NOTE #
...
end
end
end
So I copied this code from a youtube video https://www.youtube.com/watch?v=V9OySOWLYIg
He ran it in his video no problem, but when I run it, it gives me an error
C:/rails/11.rb:17:in create': Unknown hero (RuntimeError)
from C:/rails/11.rb:6:inblock in initialize'
from C:/rails/11.rb:6:in times'
from C:/rails/11.rb:6:ininitialize'
from C:/rails/11.rb:22:in new'
from C:/rails/11.rb:22:in'
class Party
attr_reader :members
def initialize(number, occupation)
#members = []
number.times { members << create(occupation)}
end
end
class PartyFactory < Party
def create(occupation)
if occupation == :warrior
Warrior.new
elseif occupation == :mage
Mage.new
else
raise "Unknown hero"
end
end
end
party = PartyFactory.new(2, :mage)
Another question I have is what if intead of Mage.new , I do Mage.new("fred"), to set the name for mage, where does the "fred" part end up?
Sorry, I am very new to Ruby and can not find a working example to understand how to set up factory methods.
Change elseif to elsif (without the second e).
Then make sure to initialize Mage and Warrior classes as you'll get a NameError if you don't.
I have the following code with two classes
class DeliveryService
attr_reader :cities
def initialize *cities
#cities = cities
end
end
class Product
attr_accessor :name
def initialize name
#name = name
end
def deliver_to delivery_service, city
if delivery_service.cities.include?(city)
puts "Product has been delivered"
else
puts "Сontact another delivery service"
end
end
end
I want to deliver_to method throw something like "Choose valid delivery service" when invalid delivery_service provided (which doesn't exist), but instead I get NameError
I tried to put this in different places in my code
rescue NameError
puts "Choose valid delivery service"
but it doesn't work
irb(main):001:0> require './DeliveryService.rb'
=> true
irb(main):002:0> fedex = DeliveryService.new "Moscow", "Saint-Petersburg"
=> #<DeliveryService:0x007f66ded31520 #cities=["Moscow", "Saint-Petersburg"]>
irb(main):003:0> product = Product.new "mug"
=> #<Product:0x007f66ded1e498 #name="mug">
irb(main):004:0> product.deliver_to fedex, "Moscow"
Product has been delivered
=> nil
irb(main):005:0> product.deliver_to dhl, "Moscow"
NameError: undefined local variable or method `dhl' for main:Object
from (irb):6
from /home/budkin/.rbenv/versions/2.1.2/bin/irb:11:in `<main>'
irb(main):006:0>
Use Dependency Injection on the Correct Object
This is a design problem, in that you aren't getting NameError from within your class; it's being raised when you call product.deliver_to dhl, "Moscow" from outside the class. Even if you have a rescue clause as part of your class definition, the NameError is going to be raised by the caller.
The right way to fix this is to pass a valid Product object to a valid DeliveryService object instead. A product shouldn't know anything about deliveries anyway; it's a violation of the single responsibility principle. So:
class DeliveryService
def deliver product
end
end
This will give you an instance method that takes a Product, and delivers it through the current delivery service. For example:
product = Product.new :widget
dhl = DeliveryService.new 'Moscow'
dhl.deliver product
Even if you perform this inversion, you will have problems if you pass invalid objects around, so don't do that. :)
dh1 does not exist, so you need to wrap your part of the code that attempts to dereference dh1.
begin
product.deliver_to dhl, "Moscow"
rescue NameError => e
puts e.message
end
Although this is quite a messy approach. A better way would be to create an array or set containing all your companies, and then check if the company is defined in that set/array.
Hi I made it to the lase exercise os Learn Ruby The Hard Way, and I come at the wall...
Here is the test code:
def test_gothon_map()
assert_equal(START.go('shoot!'), generic_death)
assert_equal(START.go('dodge!'), generic_death)
room = START.go("tell a joke")
assert_equal(room, laser_weapon_armory)
end
And here is the code of the file it should test:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def ==(other)
self.name==other.name&&self.description==other.description&&self.paths==other.paths
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
generic_death = Room.new("death", "You died.")
And when I try to launch the test file I get an error:
generic_death = Room.new("death", "You died.")
I tried to set the "generic_death = Room.new("death", "You died.")" in test_gothon_map method and it worked but the problem is that description of the next object is extremely long, so my questions are:
why assertion doesn't not respond to defined object?
can it be done different way then by putting whole object to testing method, since description of the next object is extremely long...
The nature of local variable is that they are, well, local. This means that they are not available outside the scope they were defined.
That's why ruby does not know what generic_death means in your test.
You can solve this in a couple of ways:
define rooms as constants in the Room class:
class Room
# ...
GENERIC_DEATH = Room.new("death", "You died.")
LASER_WEAPON_ARMORY = Room.new(...)
end
def test_gothon_map()
assert_equal(Room::START.go('shoot!'), Room::GENERIC_DEATH)
assert_equal(Room::START.go('dodge!'), Room::GENERIC_DEATH)
room = Room::START.go("tell a joke")
assert_equal(room, Room::LASER_WEAPON_ARMORY)
end
assert the room by its name, or some other identifier:
def test_gothon_map()
assert_equal(START.go('shoot!').name, "death")
assert_equal(START.go('dodge!').name, "death")
room = START.go("tell a joke")
assert_equal(room.name, "laser weapon armory")
end