Using the Setter Method Beyond the Initialize Method in Ruby.
Before I started using classes to group characteristics of an object. I became familiar with attr_accessors and the initialize method that allow me to overlook the need to write methods beyond using the micro and method. This was perfectly well, until I realize that I might want to change the state of the object in the future. When you need to set or change an object’s state at some point in your program other than the initialize method, the heart of the matter is assigning (or reassigning) values to instance variables. You can, of course, change any instance variable’s value in any method. For example, if we wanted tickets to have the ability to discount themselves, we could write an instance method like this inside the Ticket class definition:
def discount(percent)
@price = @price * (100 - percent) / 100.0
end
But the most common case is the simplest: calling a setter method with an argument and setting the appropriate instance variable to the argument. That’s what set_name does in the Person class example:
def set_name(string)
puts "Setting person's name..."
@name = string
end
The argument string is passed into set_name and assigned to @name. There’s more to setter methods, though. Ruby has some specialized method-naming conventions that let you write setter methods in a way that’s more elegant than sticking set_ in front of a descriptive word like name. We’ll make another attempt at Ticket, this time with a focus on setter methods and the techniques available for streaming them.
The Equal Sign (=) in Method Names
Let’s say we want a way to set the price of a ticket. As a starting point, the price can be set along with everything else at object-creation time:
class Ticket
def initialize(venue,date,price)
@venue = venue
@date = date
@price = price
end
# etc.
def price
@price
end
# etc.
end
th = Ticket.new("Town Hall", "2020-12-12", 63.00)
The initialization command is getting long, though, and requires that we remember what order to put the many arguments in so we don’t end up with a ticket whose price is “Town Hall”. And we still don’t have a way to change a ticket’s price later.
Let’s solve the problem, initially, with a set_price method that allows us to set, or reset, the price of an existing ticket. We’ll also rewrite the initialize method so that it doesn’t expect a price figure:
class Ticket
def initialize(venue, date)
@venue = venue
@date = date
end
def set_price(amount)
@price = amount
end
def price
@price
end
end
Here’s some price manipulation in action:
ticket = Ticket.new("Town Hall", "2020-12-12")
ticket.set_price(63.00)
puts "The ticket costs $#{"%.2f" % ticket.price}." KL
ticket.set_price(72.50)
puts "Whoops -- it just went up. It now costs $#{"%.2f" % ticket.price}."
- (KL) Formats price to two decimal places
The output is
The ticket costs $63.00.
Whoops -- it just went up. It now costs $72.50.
This technique works: you can write all the set_property methods you need, and the instance variable–based retrieval methods to go with them. But there’s a nicer way.
Ruby allows you to define methods that end with an equal sign (=). Let’s replace set_price with a method called price= (“price” plus an equal sign):
def price=(amount)
@price = amount
end
price= does exactly what set_price did. You can call price= like any other method. Thus you can update ticket.set_price(63.00) and ticket.set_price(72.50) to ticket.price=(63.00) and ticket.price=(72.50), respectively. The equal sign gives you that familiar “assigning a value to something” feeling, so you know you’re dealing with a setter method. It still looks odd, though; but Ruby takes care of that, too.
Syntactic sugar for Assignment-Like Methods
Programmers use the term syntactic sugar to refer to special rules that let you write your code in a way that doesn’t correspond to the normal rules but that’s easier to remember how to do and looks better.
Ruby gives you some syntactic sugar for calling setter methods. Instead of
ticket.price=(63.00)
you’re allowed to do this:
ticket.price = 63.00
When the interpreter sees this sequence of code, it automatically ignores the space before the equal sign and reads price = as the single message price= (a call to the method whose name is price=, which we’ve defined). As for the right-hand side, parentheses are optional for method arguments, as long as there’s no ambiguity. So you can put 63.00 there, and it will be picked up as the argument to the price= method.
The intent behind the inclusion of this special syntax is to allow you to write method calls that look like assignments. If you just saw ticket.price = 63.00 in a program, you might assume that ticket.price is some kind of l-value to which the value 63.00 is being assigned. But it isn’t. The whole thing is a method call. The receiver is ticket, the method is price=, and the single argument is 63.00.
The more you use this setter style of method, the more you’ll appreciate how much better the sugared version looks. This kind of attention to appearance is typical of Ruby.