I'm learning Ruby by reading Programming Ruby, the Pragmatic Programmers Guide. I really like how terse the syntax is.
I can't understand the role of the = in setter method names:
def price=(new_price)
#price = new_price
end
How is that function definition any different than this:
def price(new_price)
What difference does = make? The book says it makes direct assignment possible. But it's already possible with a normal setter method that does not have a =... ?
Here's the rest of the class:
class BookInStock
attr_reader :isbn
attr_accessor :price
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
book.price = book.price * 0.75
It gives you 'syntactical sugar' to write code as follows:
class Book
price=(new_price)
#price = new_price
# do something else
end
end
book = Book.new
book.price = 1
This code will be translated to
book.price=(1)
Actually attr_writer and attr_accessor methods generate setter (price=) methods for your class (attr_reader and attr_accessor generates getter methods as well). So your BookInStock class is similar to:
class BookInStock
def isbn val
#isbn = val
end
def price val
#price
end
def price= val
#price = val
end
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
You need to write methods with = only if you are going to add some logic into it (like validation). In other cases just use attr_writer or attr_accessor.
Related
Im struggling on understanding (after googling) on how to implement this: I have a class:
class Student
# constructor method
def initialize(name,age)
#name, #age = name, age
end
# accessor methods
def getName
#name
end
def getAge
#age
end
# setter methods
def setName=(value)
#name = value
end
def setAge=(value)
#age = value
end
end
And lets say I have another class which inherits from Student
class Grade < Student
#constructor method
def initialize(grade)
super
#grade = grade
end
# accessor methods
def getGrade
#grade
end
# setter methods
def setGrade=(value)
#grade = value
end
end
I understand how to build an abject:
student = Student.new(name, age)
How can I build this Student (that I have just created) a Grade object associated with the student and how would I call the inherited object, for example i wanted to:
puts 'student name and associated grade'
I know I can place the grade variable within the Student class, but for the purpose of learning im doing it this way.
This code would do what you wanted:
class Grade
attr_accessor :value
def initialize value
#value = value
end
end
class Student
attr_accessor :name, :age, :grade
def initialize name, age, grade
#name, #age, #grade = name, age, Grade.new(grade)
end
end
st = Student.new 'John', 18, 5
puts "student #{st.name} and associated grade #{st.grade.value}"
First off, no need to define accessors in Ruby like that, it's far from idiomatic. Let's clean that up first:
class Student
attr_accessor :name, :age
def initialize(name, age)
#name =name
#age = age
end
end
class Grade
attr_accessor :value
def initialize(grade)
#value = grade
end
end
Secondly it doesn't seem like Grade should inherit from Student at all, just adjust the latter to also store a Grade instance variable:
class Student
attr_accessor :name, :age, :grade
def initialize(name, age, grade = nil)
#name =name
#age = age
#grade = grade
end
end
You can then instantiate a student like this:
student = Student.new("Test", 18, Grade.new(1))
Or because of the default value you leave off the grade and assign it later:
student = Student.new("Test", 18)
# later
student.grade = Grade.new(1)
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
I have these two classes in Ruby:
Prod.rb
class Prod
attr_reader :code, :price
def initialize code, price
#code = code
#price = price
end
end
Buy.rb
class Buy
def initialize()
#items = []
end
def addToBasket item
#items << item
end
def changePrice
#items.each do |item|
item.price = 0.00
end
end
end
When I am testing the app with the code below, I get this error pointing to the item.price = 0.00 above:
test_1(MyTest): NoMethodError: undefined method 'price=' for #<Prod:0x24d76e8>
I can print the value of item.price but I cannot update it. Any ideas?
MyTest.rb
def setup
#prod1 = Prod.new("1", 19.95)
end
def test_1
b = Buy.new()
b.addToBasket(#prod1)
[...]
end
This is because you don't have a price= method defined in class Prod. You only defined a getter with attr_reader :code, :price. If you to create both getter and setter, user attr_accessor in your Prod class:
class Prod
attr_accessor :code, :price
def initialize code, price
#code = code
#price = price
end
end
You can learn more about getters and setters in ruby in my article: Ruby for Admins: Objects.
I'm studying Ruby and try to implement method_missing, but it's not working. For example, I want to print method name after find_ but ruby raises "undefined method 'find_hello'" when i call it on Book instance.
TEST_05.RB
module Searchable
def self.method_missing(m, *args)
method = m.to_s
if method.start_with?("find_")
attr = method[5..-1]
puts attr
else
super
end
end
end
class Book
include Searchable
BOOKS = []
attr_accessor :author, :title, :year
def initialize(name = "Undefined", author = "Undefined", year = 1970)
#name = name
#author = author
#year = year
end
end
book = Book.new
book.find_hello
You are calling method on object which looks for instance_level method. So you need to define instance_level method_missing method:
module Searchable
def method_missing(m, *args)
method = m.to_s
if method.start_with?("find_")
attr = method[5..-1]
puts attr
else
super
end
end
end
class Book
include Searchable
BOOKS = []
attr_accessor :author, :title, :year
def initialize(name = "Undefined", author = "Undefined", year = 1970)
#name = name
#author = author
#year = year
end
end
book = Book.new
book.find_hello #=> hello
When you use self with method definition. It is defined as class level method. In your case Book.find_hello will output hello.
You've defined method_missing as a class method on Searchable, but you're trying to invoke it as an instance method. To invoke the method as it is, run it against the class:
Book.find_hello
If your intention is to find something from the entire collection of books, this is the canonical way it's done. ActiveRecord uses this approach.
You could similarly have a find_* instance method that would search the current book instance for something. If that's your intention, then change def self.method_missing to def method_missing.
I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?