Having trouble with this exercise - ruby

I am working on Head First Ruby. Here is my code:
class Employee
attr_reader :name
def name=(name)
end
def print_name
puts "Name: #{name}"
end
end
class SalariedEmployee < Employee
attr_reader :salary
def salary=(salary)
# code to validate and set #salary
end
def print_pay_stub
print_name
pay_for_period = (salary / 365.0) * 14
formatted_pay = format("$%.2f", pay_for_period)
puts "Pay this period: #{formatted_pay}"
end
end
class HourlyEmployee < Employee
attr_reader :hourly_wage, :hours_per_week
def hourly_wage=(hourly_wage)
# code to validate and set #hourly_wage
end
def hours_per_week=(hours_per_week)
# code to validate and set #hours_per_week
end
def print_pay_stub
print_name
pay_for_period = hourly_wage * hours_per_week * 2
formatted_pay = format("$%.2f", pay_for_period)
puts "pay This Period: #{formatted_pay}"
end
end
I cannot get this exercise to work. I get this error:
employee.rb:42:in `print_pay_stub': undefined method `*' for nil:NilClass (NoMethodError)
from employee.rb:56:in `<main>'
Could someone look over this code and tell what is going wrong?

Edit: My mistake -- either hourly_wage or hours_per_week is nil. Make sure those are set.
It looks like salary is nil -- at least, that's the only line there with an *. You need to make sure salary is set
Edit: clarification
Edit 2: correction

Related

Receiving undefined method error in ruby (no rails)

I am really new to this so I apologize for my ignorance and I have searched for resources before asking.
I am using regular ruby and I am using an API.
I keep receiving undefined method error when I run things and I cannot figure out why.
So, this is my code.... the issue is the last two methods I think... but I don't understand what it is that is cause the method I am calling #print_lighting_time to come up as undefined. Other resources have stated that it is usually an issue with an object but I guess that isn't making sense to me...
here is the code for the CLI that isn't working
class Cli
def start
puts "Shabbat Shalom!"
Api.get_data
check_date_options
end
def check_date_options
puts "Curious when to light your candles for Shabbos? Type 'Dates' to find out!"
check_date
end
def check_date
input = get_input
if input == "Dates"
list_dates
else
invalid_date
end
end
def get_input
gets.chomp
end
def invalid_date
puts "Invalid date! Check your date and reenter!"
binding.pry
end
def list_dates
CandleLighting.all.each_with_index do |title, index|
puts "#{index}. #{title.date}"
end
lighting_times_info_list
end
def lighting_times_info_list
puts "Select the date to view the lighting time!"
lighting_times_info
end
def lighting_times_info
input = get_input
if input.to_i.between?(0, 60)
index = input.to_i
date = CandleLighting.all[index]
print_lighting_time(date)
else
invalid_date
lighting_times_info_list
end
def print_lighting_time(date)
puts "Shabbos is:#{date}"
puts "Light candles by: #{date.title}"
end
end
end
and here is the code for the CandleLighting class
class CandleLighting
attr_accessor :title, :date
##all = []
def initialize(title, date)
#title = title
#date = date
##all << self
end
def self.all
##all
end
end
and the code for the API
class Api
def self.get_data
load_candlelightings
end
def self.load_candlelightings
response = RestClient.get("https://www.hebcal.com/hebcal?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=5128581&m=50&s=on")
data = JSON.parse(response.body)
data["items"].each do |hash|
CandleLighting.new(hash["title"], hash["date"]) if hash["title"].include?("Candle lighting")
end
end
end
and finally the error message that relates to line 52 of the CLI the line being "print_lighting_time(date)
Traceback (most recent call last):
6: from bin/run:4:in `<main>'
5: from /Users/allisonperry/Development/code/Mod1/candle-lighting-times/lib/cli.rb:5:in `start'
4: from /Users/allisonperry/Development/code/Mod1/candle-lighting-times/lib/cli.rb:10:in `check_date_options'
3: from /Users/allisonperry/Development/code/Mod1/candle-lighting-times/lib/cli.rb:17:in `check_date'
2: from /Users/allisonperry/Development/code/Mod1/candle-lighting-times/lib/cli.rb:37:in `list_dates'
1: from /Users/allisonperry/Development/code/Mod1/candle-lighting-times/lib/cli.rb:42:in `lighting_times_info_list'
/Users/allisonperry/Development/code/Mod1/candle-lighting-times/lib/cli.rb:52:in `lighting_times_info': undefined method `print_lighting_time' for #<Cli:0x00007fa94f883e48> (NoMethodError)
I am not sure if all this code is even necessary in order to help... but I have been trying to fix this for quite some time and its not happening.
Thank you in advance!
Putting this into a code editor and properly indenting it reveals the problem. print_lighting_time is defined inside lighting_times_info.
def lighting_times_info
input = get_input
if input.to_i.between?(0, 60)
index = input.to_i
date = CandleLighting.all[index]
print_lighting_time(date)
else
invalid_date
lighting_times_info_list
end
def print_lighting_time(date)
puts "Shabbos is:#{date}"
puts "Light candles by: #{date.title}"
end
end
It should instead be...
def lighting_times_info
input = get_input
if input.to_i.between?(0, 60)
index = input.to_i
date = CandleLighting.all[index]
print_lighting_time(date)
else
invalid_date
lighting_times_info_list
end
end
def print_lighting_time(date)
puts "Shabbos is:#{date}"
puts "Light candles by: #{date.title}"
end
Indentation is an important visual guide to prevent these sorts of mistakes.
A good editor like Atom or VSCode will indent for you and can warn you of common mistakes. Tools such as rubocop will audit your code for common mistakes.

Avoiding initialize method in ruby

I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end

Possible help in code refactoring

Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?

Fix "no id given" message in method_missing

The following Ruby code raises the confusing error "no id given" shown at the end. How do I avoid this problem?
class Asset; end
class Proxy < Asset
def initialize(asset)
#asset
end
def method_missing(property,*args)
property = property.to_s
property.sub!(/=$/,'') if property.end_with?('=')
if #asset.respond_to?(property)
# irrelevant code here
else
super
end
end
end
Proxy.new(42).foobar
#=> /Users/phrogz/test.rb:13:in `method_missing': no id given (ArgumentError)
#=> from /Users/phrogz/test.rb:13:in `method_missing'
#=> from /Users/phrogz/test.rb:19:in `<main>'
The core of this problem can be shown with this simple test:
def method_missing(a,*b)
a = 17
super
end
foobar #=> `method_missing': no id given (ArgumentError)
This error arises when you call super inside method_missing after changing the value of the first parameter to something other than a symbol. The fix? Don't do that. For example, the method from the original question can be rewritten as:
def method_missing(property,*args)
name = property.to_s
name.sub!(/=$/,'') if name.end_with?('=')
if #asset.respond_to?(name)
# irrelevant code here
else
super
end
end
Alternatively, be sure to explicitly pass a symbol as the first parameter to super:
def method_missing(property,*args)
property = property.to_s
# ...
if #asset.respond_to?(property)
# ...
else
super( property.to_sym, *args )
end
end

Ruby: issues with an undefined method error

I am working on a ruby banking problem and I keep coming across this error when trying to write the code for the deposit method. I would like the deposit method to put out a puts statement saying this person has enough cash to make this deposit and state amount, or it states they do not have enough cash to deposit. It says this error in my irb:
NoMethodError: undefined method `<' for nil:NilClass
from banking2.rb:30:in `deposit'
from banking2.rb:59:in `<top (required)>'
Can someone please help me find my error? I've tried several options but am not able to figure it out.
class Person
attr_accessor :name, :cash_amount
def initialize(name, cash_amount)
#name = name
#cash_amount = #cash_amount
puts "Hi, #{#name}. You have #{cash_amount}!"
end
end
class Bank
attr_accessor :balance, :bank_name
def initialize(bank_name)
#bank_name = bank_name
#balance = {} #creates a hash that will have the person's account balances
puts "#{bank_name} bank was just created."
end
def open_account(person)
#balance[person.name]=0 #adds the person to the balance hash and gives their balance 0 starting off.
puts "#{person.name}, thanks for opening an account at #{bank_name}."
end
def deposit(person, amount)
#deposit section I can't get to work
if person.cash_amount < amount
puts "You do not have enough funds to deposit this #{amount}."
else
puts "#{person.name} deposited #{amount} to #{bank_name}. #{person.name} has #{person.cash_amount}. #{person.name}'s account has #{#balance}."
end
end
def withdraw(person, amount)
#yet to write this code
# expected sentence puts "#{person.name} withdrew $#{amount} from #{bank_name}. #{person.name} has #{person.cash_amount} cash remaining. #{person.name}'s account has #{#balance}. "
end
def transfer(bank_name)
end
end
chase = Bank.new("JP Morgan Chase")
wells_fargo = Bank.new("Wells Fargo")
person1 = Person.new("Chris", 500)
chase.open_account(person1)
wells_fargo.open_account(person1)
chase.deposit(person1, 200)
chase.withdraw(person1, 1000)
Change this in your initialize method on Person:
#cash_amount = #cash_amount
To this:
#cash_amount = cash_amount
You added an extra # sign, so you set #cash_amount to #cash_amount. The default value for an uninitialized instance variable is nil in Ruby.
The only place you have a < is in person.cash_amount < amount, so the error is coming from there - person.cash_amount is nil.
Look at where cash_amount is being defined in your Person initializer - you are passing in def initialize(name, cash_amount) but then you are calling #cash_amount = #cash_amount!
Remove the second # so you are actually assigning #cash_amount with the value you are passing in in cash_amount.

Resources