Ruby Classes; Error: Uninitialized constant BookInStock::ArguementError (NameError) - ruby

I'm new to ruby but trying my best to grasp it. The ques is...
Define a class BookInStock which represents a book with an isbn number, isbn, and price of the book as a floating-point number, price, as attributes. The constructor should accept the ISBN number (a string) as the first argument and price as second argument, and should raise ArgumentError (one of Ruby's built-in exception types) if the ISBN number is the empty string or if the price is less than or equal to zero.
Include the proper getters and setters for these attributes. Include a method price_as_string that returns the price of the book with a leading dollar sign and trailing zeros, that is, a price of 20 should display as "$20.00" and a price of 33.8 should display as "$33.80".
My code:-
class BookInStock
#getters and setters
attr_reader :isbn, :price
def isbn= (isbn)
if isbn == ''
raise ArguementError.new("Must have an ISBN number")
end
#isbn = isbn
end
def price= (price)
price = price.to_f
if price <= 0
raise ArguementError.new("Must have price")
end
#price = price
end
def initialize(isbn, price)
self.isbn = #isbn
self.price = #price
end
def self.price_as_string
return "$" + sprinff("%.2f", #price)
end
end
However, I'm getting an error when I try
BookInStock.new('',9.00)
I get a Uninitialized constant BookInStock::ArgumentError (NameError)
Upon doing some research on the error it says that I may be referring to a class or module that doesn't exist?
PLease help, thanks

You have made a small typo.
Its ArgumentError not ArguementError
Please refer
http://apidock.com/ruby/ArgumentError
Alright. You have few more problems too. Lets take them one by one.
Your Constructor:
Inside your constructor you have the code,
self.isbn = #isbn
self.price = #price
When you write a variable with the # character, it referes to the instance variable. So when you are writing self.isbn = #isbn, you are actually doing #isbn=#isbn and you are not assigning the argument passed to the constructor to #isbn. So you should do:
self.isbn = isbn
self.price = price
Next is your price_as_string method. When you declare a method as self.method_name it becomes a class method. Such methods are called using the Class like so - BookInStock.price_as_string. But the attribute used inside this method(#price) is not a class variable but an instance variable. And hence your price_as_string should be either a instance method (written without self.) or this self.price_as_string must take price as an argument. like so - self.price_as_string(price). But a good Object Oriented design would be to have this as an instance method.
Thanks #arup

Related

Why am I getting a nil value (Ruby)

I have 2 classes, When initializing the 'Shop' class I am giving the class a currency. How can I have "#checkout_currency" from my second class adopt the '#currency' value from the first class? #checkout_currency currently equals nil and I want it to equal "USD"
Here are my classes.
First..
require_relative 'convert'
class Shop
DEFAULT_CURRENCY = "USD"
def initialize(currency = DEFAULT_CURRENCY,
convert = Convert.new)
#currency = currency
#convert = convert
end
def self.currency
#currency
end
end
Second..
require_relative 'shop'
class Convert
attr_reader :checkout_currency
def initialize(checkout_currency = Shop.currency)
#checkout_currency = checkout_currency
end
end
Yes, defining a method with self will make it a method on the class rather than a method on any instance of the class. But #currency is an instance variable – it’s different for every instance of Shop, and not defined for the Shop class itself. What if you had 3 Shop objects with different currencies?
The thing that is really wrong is your composition of objects – is Convert something that Shop constructs, or is it something that must be passed in fully formed? If you restructure to either of these, your problem goes away.
class Shop
def initialize(convert, currency=DEFAULT_CURRENCY)
#convert = convert
#currency = currency
end
end
convert = Convert.new(Shop.DEFAULT_CURRENCY)
shop = Shop.new(convert)
Or maybe:
class Shop
def initialize(currency=DEFAULT_CURRENCY, convert_class=Convert)
#convert = convert_class.new(currency)
#currency = currency
end
end
shop = Shop.new

Confused by Ruby's class constructors and virtual accessors

As someone who previously had only a limited exposure to programming and this was mainly in Python and C++, I find Ruby to be really refreshing and enjoyable language, however I am having a little trouble understanding Ruby's use of class constructors and virtual accessors, specifically with what is and what is not considered to be a constructor and whether I understand virtual accessors correctly.
Here's an example (credit is due to the Pragmatic Bookshelf, Programming Ruby 1.9 & 2.0):
class BookInStock
attr_accessor :isbn, :price # is this part of the class constructor?
def initialize(isbn, price) # and is the initialize method part of a constructor or just a regular method?
#isbn = isbn
#price = price
end
def price_in_cents
Integer(price*100+0.5)
end
def price_in_cents=(cents) # this is a 'virtual accessor' method, trough which I am able to update the price down the road...?
#price = cents / 100.0
end
end
book = BookInStock.new('isbn1', 23.50)
puts "Price: #{book.price}"
puts "Price in cents: #{book.price_in_cents}"
book.price_in_cents = 1234 # here I am updating the value thanks to the 'virtual accessor' declared earlier, as I understand it
puts "New price: #{book.price}"
puts "New price in cents: #{book.price_in_cents}"
Thanks for all the help I could get understanding this piece of code.
1 attr_accessor :isbn, :price # is this part of the class constructor?
This line is to keep concept of access private member through method only philosophy. It's in effect equivalent to declare two private members and their getter and setter methods, has nothing to do with constructor.
2 def initialize(isbn, price) # and is the initialize method part of a constructor or just a regular method?
To put it in the easy way, this is THE constructor. The method get called upon 'new' keyword
3 def price_in_cents=(cents) # this is a 'virtual accessor' method, trough which I am able to update the price down the road...?
It's just an alternative of price= method, which takes argument in different format, the price= method is the setter method automatically generated by your question line 1.
4 book.price_in_cents = 1234 # here I am updating the value thanks to the 'virtual accessor' declared earlier, as I understand it
Keep in mind this feels like assigning a variable but really is accessing a setter method, in consistent with the concept reflected in your question line 1 and 3.

Confusion between local variables, instance variables, and symbols

class​ BookInStock
attr_reader :isbn, :price
​def​ initialize(isbn, price)
#isbn = isbn
#price = Float(price)
​end​
“This is the first time we’ve used ​ symbols​ in this chapter. As we discussed, symbols are just a convenient way of referencing a name. In this code, you can think of ​:isbn​ as meaning the ​name ​ ​isbn​ and think of plain ​isbn​ as meaning the ​value​ of the variable. In this example, we named the accessor methods ​isbn​ and ​price​. The corresponding instance variables are ​#isbn​ and ​#price​. ”
Question: I understand that the local variables are assigned instance variables, so that they don't disappear when the the initialize method returns. But why do I have to use symbols with attr_accessor? What if I just want normal variables instead? I noticed that removing the colons for :isbn and :price in attr_accessor causes it to stop working.
Symbols aren't really variables; they are more like special strings. For instance, your code would work just fine if you used strings instead:
class​ BookInStock
attr_reader 'isbn', 'price'
​def​ initialize(isbn, price)
#isbn = isbn
#price = Float(price)
​end
end
It's more a matter of convention than of functionality really. On idiomatic Ruby symbols are commonly used to name options, for example.
To get an extensive explanation on the differences between symbols and strings, check out this blog post: The Difference Between Ruby Symbols and Strings.
I rewrite your code as follows:
class BookInStock
attr_name1 = "isbn"
attr_name2 = :price
attr_reader attr_name1, attr_name2
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
b = BookInStock.new("23456", 30.3)
puts b.isbn
puts b.price
Statament in class definiton body is executed the same way as statement out of class definition body. "attr_reader attr_name1, attr_name2" is not a delaration, it's just a normal statement which calls method attr_reader with two arguments, attr_name1 and attr_name. attr_reader is a method defined in Module.

Why can't I set the value of an instance variable like this

I'm trying to understand how to use setter methods in Ruby but I don't understand why this code does not work. Is it not working because I already set he price of book when I created the book object? If I change the line in question to book.price = book.price + 10.00 it works as expected. Why? Why can't I just change the value by just passing in a different parameter?
class BookInStock
attr_reader :isbn
attr_accessor :price
def initialize(isbn,price)
#isbn = isbn
#price = Float(price)
end
def isbn
#isbn
end
def to_s
"ISBN: #{#isbn}, price: #{#price}"
end
end
book = BookInStock.new("isbn",38.5)
puts "The books cost: #{book.price} and the name is: #{book.isbn}"
book.price = book.price 150 # THIS LINE IS BROKEN WHY?
puts "The new price is "
puts "The new price of the book is #{book.price}"
In short, because
book.price
is a method taking ZERO arguments returning the price of the book. However
book.price=
is a method of ONE argument that sets the value.
The latter method can be called like this:
book.price = 150
You were trying to call the getter with an argument. You can't call book.price 150.
You do it like this:
book.price = 150
The attribute reader doesn't take any parameters and book.price is not the name of the writer, that's price=.
If you want to pass the new price as a more obvious parameter to your writer, one way would be a call like this:
book.send 'price=', 160

What convention should I use for instance variables?

Is there any instance variable convention used in Ruby code? Namely, I noticed that, in the examples section, instance variable are initialized using the '#' and then the code uses a variable name without the '#':
class BookInStock
attr_reader: isbn
attr_accessor: price
def initialize (isbn, price)
#isbn = isbn
#price = Float (price)
end
def price_in_cents
Integer (price * 100 + 0.5)
end
end
And there are examples where the code, the instance variable is used all the time with the prefix '#'
class BookStock
def set_price (price)
#price = price
end
def get_price
#price
end
end
What is the difference these records? When should I use '#' only in the initialization of an object and when in all the class methods?
You can only use the instance variable name without # if you have defined an attribute reader for this variable (with attr_reader or attr_accessor). This is a lightweight helper to create a method that returns the instance variable's value.
The difference is a question between using the public interface of your class or accessing private data. It is recommended to use the public interface in subclasses or included modules, for example. This ensures encapsulation. So you should not access instance variables from within subclasses or modules.
In the class itself, ask yourself this question: if the implementation of the attribute changes, should my usage the attribute change as well? If the answer is yes, use the instance variable directly; if it's no, use the public attribute.
An example. Suppose we have a Person class that represents a person and should contain that person's name. First there is this version, with only a simple name attribute:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def blank?
!#name
end
def to_s
name
end
end
Note how we use the instance variable for the blank? method, but the public attribute for the to_s method. This is intentional!
Let's say that in a refactoring step we decide that the Person class should keep track of a first_name and last_name seperately.
class Person
def initialize(first_name, last_name)
#first_name, #last_name = first_name, last_name
end
def name
"#{#first_name} #{#last_name}"
end
def blank?
!#first_name and !#last_name
end
def to_s
name
end
end
We now change the name attribute to a regular method, which composes the name from a person's first and last name. We can now see that it is possible to keep the same implementation for to_s. However, blank? needs to be changed because it was dependent on the underlying data. This is the reason why to_s used the attribute, but blank? used the instance variable.
Of course, the distinction is not always easy to make. When in doubt, choose the public API over accessing private data.
In your first example, #price is an instance variable.
In the method definition for price_in_cents, price is actually a method call to price(). Because of Ruby's syntactic sugar, () is omitted.
It looks like there's no explicit definition for price(), but attr_accessor :price defined both price() and price=(value) a.k.a. getter and setter. You can try to comment out the attr_accessor :price line, you will get an "undefined local variable or method" exception when the BookInStock's instance calls price_in_cents.
An instance variable is always have an # prefix. If no accessor methods defined, you can not use that name without # prefix.
Using name = value is an error, because that creates a local variable named name. You must use self.name = value.
As for convention, you can only get away with using #name if you can guarantee that the accessors will always be lightweight attr_accessors. In all other cases, using #name over self.name will violate encapsulation and give yourself a headache. You gave the exact reason in your question — if there is extra logic in the getter/setter, you must duplicate it if you access the instance variable directly.

Resources