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.
Related
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.
Here's the code:
class Dungeon
attr_accessor :player
def initialize(player_name)
#player = Player.new(player_name)
end
end
Now, if I write:
dungeon = Dungeon.new("Robert")
puts dungeon.player.name
it's obviously going to spit out Robert.
I'm a little new to Ruby, so forgive me if this question is obvious to you all. I'm struggling to wrap my head around it still. I am learning about "instance", "class", and global variables and my question is: How does Ruby know that :player in the code above refers to #player? Why isn't the code written, instead, :#player?
Does that make any sense?
attr_accessor is just a method that defines a public getter/setter. #player is the actual instance variable that stores values. The getter it generates is based on the symbol name, so :player creates these methods:
def player
#player
end
def player=(player)
#player = player
end
If you only want the getter you can use attr_reader. If you only want the setter you can use attr_writer. attr_accessor combines these two methods and gives you both methods.
One thing that is easy to overlook when first learning Ruby is that the construct
attr_accessor :property_name
is just a shorter way of writing getter and setter methods for a class's instance variables:
# getter
def property_name
#property_name
end
# setter
def property_name=( some_value )
#property_name = some_value
end
It's basically an example of the "metaprogramming" support in Ruby, but it feels like something more mysterious.
Also, you do not have to use the symbol style, you could instead have:
attr_accessor "property_name"
The symbol version just "reads better" for most people.
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
When we are defining getter and setter methods for name in the following code:
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
end
what is happening internally? How is Ruby able to take a symbol and match it with a variable?
It uses instance_variable_get and instance_variable_set for that.
Update: as Andrew Marshall noted, not exactly. Most ruby core methods are defined in C, but the result is essentially the same.
One could define attr_accessor and friends as follows:
def self.attr_accessor(*names)
names.each do |name|
define_method(name) do
instance_variable_get("##{name}")
end
define_method("#{name}=") do |new_value|
instance_variable_set("##{name}", new_value)
end
end
end
Note that I've also used define_method here to define the accessor methods.
I recommend that you take a look at some introductory texts about ruby metaprogramming, such as "Don’t Know Metaprogramming In Ruby?".
It doesn't match it with a variable, attr_accessor does essentially this (it's actually written in C, but I've written it's roughly Ruby translation here):
def attr_accessor(var_name)
ivar_name = :"##{var_name}"
define_method(var_name) do
instance_variable_get ivar_name
end
define_method("#{var_name}=") do |value|
instance_variable_set ivar_name, value
end
end
As you can see, it simply uses the symbol passed to create two methods. It doesn't do anything fancy with the symbol, and is mostly just passing it through. Note, also, that all instance variables "exist" by default and are set to nil, so this will never blow up.
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.