52
CHAPTER 2
Objects, methods, and local variables
As you can see from table 2.2, the arguments you send to methods are assigned to variables—specifically, local variables, visible and usable for the duration of the method.
Assignment of local variables through method argument binding is just one case of the
general process of local variable assignment, a process that we’ll look at in detail next.
2.6
Local variables and variable assignment
Local variable names start with a lowercase letter or an underscore and are made up
of alphanumeric characters and underscores. All of these are valid local variable
names, including the lone underscore:
x
_x
name
first_name
plan9
user_ID
_
The local in local variables pertains to the fact that they have limited scope: a local variable is only visible in a limited part of a program, such as a method definition. Local
variable names can be reused in different scopes. You can use, say, the variable name x
in more than one place, and as long as those places have different scopes, the two x
variables are treated as completely separate. (Remember that conventional Ruby style
prefers under_score names over camelCase names for local variables.)
Scope is an important topic in its own right, and we’ll get deeply into it in
chapter 5. You can start getting familiar with some key aspects of it now, though, as
you examine how local variables come and go. The classic case of local scope is a
method definition. Watch what happens with x in this example:
def say_goodbye
x = "Goodbye"
puts x
end
B
def start_here
x = "Hello"
puts x
say_goodbye
puts "Let's check whether x remained the same:"
puts x
end
C
D
E
start_here
F
The output from this program is as follows:
Hello
Goodbye
Let's check whether x remained the same:
Hello
Licensed to sam kaplan
Local variables and variable assignment
53
When you call start_here F, the method start_here is executed. Inside that
method, the string “Hello” is assigned to x C—that is, to this x, the x in scope inside
the method.
start_here prints out its x (“Hello”) and then calls the method say_goodbye D.
In say_goodbye, something similar happens: a string (“Goodbye”) is assigned to x B.
But this is a different x—as you see when the call to say_goodbye is finished and control returns to start_here: Ruby prints out this x, and the value is still “Hello” E.
Using x as a local variable name in the scope of one method didn’t affect its value in
the scope of the other.
The local variables in this last example are created through explicit assignment.
(Local variables can come into being, as you’ve seen, through the binding of method
arguments to method parameters.) But what happens when the assignment or initialization takes place? What exactly is the relation between a variable and the object that
it represents?
2.6.1
Variables, objects, and references
Variable assignments give the appearance, and have the apparent effect, of causing
the variable on the left to be set equal to the object on the right. After this assignment,
for example:
str = "Hello"
statements like puts str will deliver the string “Hello” for printing and processing.
Now, look at this example:
str = "Hello"
abc = str
puts abc
This, too, prints “Hello”. Apparently the variable abc also contains “Hello”, thanks to
having had str assigned to it.
But there’s more to it. The next example involves a method called replace, which
does an in-place replacement of a string’s content with new text:
str = "Hello"
abc = str
str.replace("Goodbye")
puts str
puts abc
Look closely at the output:
Goodbye
Goodbye
The first “Goodbye” is str; the second is abc. But we only replaced str. How did the
string in abc get replaced?
Licensed to sam kaplan
54
CHAPTER 2
Objects, methods, and local variables
ENTER REFERENCES
The answer is that variables in Ruby (with a few exceptions, most notably variables
bound to integers) don’t hold object values. str doesn’t contain “Hello”. Rather, str
contains a reference to a string object. It’s the string object that has the characteristic of
containing the letters that make up “Hello”.
In an assignment with a variable name on the left and an object on the right, the
variable receives a reference to the object. In an assignment from one variable to
another (abc = str), the variable on the left receives a copy of the reference stored in
the variable on the right, with the result that both variables now contain references to
the same object.
The fact that variables hold references to objects has implications for operations
that change objects. The string-replace operation
str.replace("Goodbye")
replaces the characters of the string to which str is a reference with the text “Goodbye”. The variable abc contains another reference to the same string object. Even
though the replace message goes to str, it causes a change to the object to which the
reference in abc refers. When you print out abc, you see the result: the contents of the
string have changed.
The un-reference: immediate values
Some objects in Ruby are stored in variables as immediate values. These include integers, symbols (which look like :this), and the special objects true, false, and
nil. When you assign one of these values to a variable (x = 1), the variable holds
the value itself, rather than a reference to it.
In practical terms, this doesn’t matter (and it will often be left as implied, rather than
spelled out repeatedly, in discussions of references and related topics in this book).
Ruby handles the dereferencing of object references automatically; you don’t have to
do any extra work to send a message to an object that contains, say, a reference to
a string, as opposed to an object that contains an immediate integer value.
But the immediate-value representation rule has a couple of interesting ramifications, especially when it comes to integers. For one thing, any object that’s represented as an immediate value is always exactly the same object, no matter how many
variables it’s assigned to. There’s only one object 100, only one object false, and
so forth.
The immediate, unique nature of integer-bound variables is behind Ruby’s lack of preand post-increment operators—which is to say, you can’t do this in Ruby:
x = 1
x++
# No such operator
The reason is that, due to the immediate presence of 1 in x, x++ would be like 1++,
which means you’d be changing the number 1 to the number 2—and that makes no
sense. (We’ll return to the topic of the absence of these operators in Ruby in the sidebar in section 7.2.)
Licensed to sam kaplan
Local variables and variable assignment
55
For every object in Ruby, there can and must be one or more references to that
object. If there are no references, the object is considered defunct, and its memory
space is released and reused.
If you have two or more variables containing references to a single object, you can
use any of them, on an equal basis, to send messages to the object. References have a
many-to-one relationship to their objects. But if you assign a completely new object to
a variable that’s already referring to an object, things change.
2.6.2
References in variable assignment and reassignment
Every time you assign to a variable—every time you put a variable name to the left of
an equal sign and something else on the right—you start from scratch: the variable is
wiped clean, and a new assignment is made.
Here’s a new version of our earlier example, illustrating this point:
str = "Hello"
abc = str
str = "Goodbye"
puts str
puts abc
This time the output is as follows:
Goodbye
Hello
The second assignment to str gives str a reference to a different string object. str
and abc part company at that point. abc still refers to the old string (the one whose
contents are “Hello”), but str now refers to a different string (a string whose contents
are “Goodbye”).
The first version of the program changed a single string; but the second version
has two separate strings. After it’s reused, the variable str has nothing further to do
with the object it referred to previously. But reusing str has no effect on abc, which
still contains a reference to the original string.
NOTE
The examples use local variables to demonstrate what does and doesn’t
happen when you assign to a variable that’s already been assigned to.
But the rules and behaviors you’re seeing here aren’t just for local variables. Class, global, and instance variables follow the same rules. (So do
so-called constants, which you can assign to more than once, oddly
enough!) All of these categories of identifier are l-values: they can serve
as the left-hand side, or target, of an assignment. (Compare with, say,
100 = 10, which fails because 100 isn’t an l-value.) And they all behave
the same with respect to how they bind to their right-hand side and what
happens when you use a given one more than once.
Ruby variables are often described as labels or names for objects. It’s a useful comparison. Say you have two names for your dog. “I’m taking Fido to the vet” and “I’m taking Rover to the vet” refer to the same animal. But if you get a new dog and transfer
Licensed to sam kaplan
56
CHAPTER 2
Objects, methods, and local variables
the name Fido to him, then the name-to-dog bindings have changed. Fido and Rover
no longer refer to the same animal, and the name Fido has no further connection
with the first dog. And the new Fido doesn’t even have to be a dog; you could stop calling your dog Fido and start using the name for your car instead. It’s the same when
you do x = 1 followed by x = "A string". You’re reusing the identifier x.
The semantics of references and (re)assignment have important implications for
how things play out when you call a method with arguments. What does the method
receive? And what can the method do with it?
2.6.3
References and method arguments
Let’s stick with a string-based example, because strings are easy to change and track.
Here’s a method that takes one argument:
def change_string(str)
str.replace("New string content!")
end
Next, create a string and send it to change_string:
s = "Original string content!"
change_string(s)
Now, examine s:
puts s
The examination reveals that the contents of the string to which s refers have
changed:
New string content!
This tells you that inside the change_string method, the variable str is assigned a reference to the string also referred to by s. When you call a method with arguments,
you’re really trafficking in object references. And once the method has hold of a reference, any changes it makes to the object through the reference are visible when you
examine the object through any of its references.
Ruby gives you some techniques for protecting objects from being changed,
should you wish or need to do so.
DUPING AND FREEZING OBJECTS
If you want to protect objects from being changed inside methods to which you send
them, you can use the dup method, which duplicates an object:
s = "Original string content!"
change_string(s.dup)
puts s
Original string content!
You can also freeze an object, which prevents it from undergoing further change:
s = "Original string content!"
s.freeze
change_string(s)
RuntimeError: can’t modify frozen string
Licensed to sam kaplan
Local variables and variable assignment
57
To complete the picture, there’s also a method called clone. It’s a lot like dup. The difference is that if you clone a frozen object, the clone is also frozen—whereas if you
dup a frozen object, the duplicate isn’t frozen.
With these tools in hand—dup, clone, and freeze—you can protect your objects
against most rogue change operations. Some dangers still lurk, though. Even if you
freeze an array, it’s still possible to change the objects inside the array (assuming
they’re not frozen).
>> numbers = ["one", "two", "three"]
=> ["one", "two", "three"]
>> numbers.freeze
=> ["one", "two", "three"]
>> numbers[2] = "four"
RuntimeError: can't modify frozen array
from (irb):24:in `[]='
from (irb):24
from /usr/local/bin/irb19:12:in `'
>> numbers[2].replace("four")
=> "four"
>> numbers
=> ["one", "two", "four"]
B
C
D
In this example, the fact that the numbers array is frozen means you can’t change the
array B. But the strings inside the array aren’t frozen. If you do a replace operation
on the string “three”, mischievously turning it into “four” C, the new contents of the
string are revealed when you reexamine the (still frozen!) array D.
Be careful with references, and remember that a reference to an object inside a
collection isn’t the same as a reference to the collection. (You’ll get a strong feel for
collections as objects in their own right when we look at them in detail in chapter 9.)
A final point about variables—local variables in particular—involves their physical
resemblance to method calls, and how Ruby figures out what you mean when you
throw a plain, unadorned identifier at it.
2.6.4
Local variables and the things that look like them
When Ruby sees a plain word sitting there—a bareword identifier, like s, ticket,
puts, or user_name—it interprets it as one of three things:
■
■
■
A local variable
A keyword
A method call
Keywords are special reserved words that you can’t use as variable names. def is a keyword; the only thing you can use it for is to start a method definition. (Strictly speaking, you can trick Ruby into naming a method def. But … well … don’t.) if is also a
keyword; lots of Ruby code involves conditional clauses that start with if, so it would
be confusing to also allow the use of if as a variable name. A sequence like if = 3
would be difficult for Ruby to parse.
Licensed to sam kaplan
58
CHAPTER 2
Objects, methods, and local variables
Like local variables, method calls can be plain words. You’ve seen several examples,
including puts and print. If the method call includes arguments in parentheses, then
it’s clear that it’s not a local variable. In other cases, there may be some ambiguity—
and Ruby has to figure it out.
Here’s how Ruby decides what it’s seeing when it encounters a plain identifier:
1
2
3
If the identifier is a keyword, it’s a keyword (Ruby has an internal list of these
and recognizes them).
If there’s an equal sign (=) to the right of the identifier, it’s a local variable
undergoing an assignment.
Otherwise, the identifier is assumed to be a method call.
If you use an identifier that isn’t any of these three things, then Ruby will complain
and halt execution with a fatal error. The error message you get when this happens is
instructive:
$ ruby -e "x"
-e:1: undefined local variable or method 'x' for
main:Object (NameError)
Note that Ruby can’t tell whether you thought x was a variable or a method. It knows
that x isn’t a keyword, but it could be either of the other two. So the error message
includes both.
At this point you’ve got a large, and growing, store of knowledge about objects and
variables and how they’re related. We’ll turn next to the topic of how to create objects
in a structured, scalable way with classes.
2.7
Summary
We’ve covered a lot of ground in this chapter. You’ve learned about creating a new
object and defining methods for it. You’ve learned about the message-sending mechanism by which you send requests to objects for information or action. You also learned
how to use some of the important built-in methods that every Ruby object comes with:
object_id, respond_to?, and send. And we looked in some detail at the syntax for
method argument lists, including the use of required, optional, and default-valued
arguments.
Finally, we examined local variables and variable assignment. You saw that keywords and method calls can look like local variables, and Ruby has ways of figuring out
what it’s seeing. You also learned that variables receive references to objects, and more
than one variable can refer to the same object.
The chapter included some comments about object orientation and how objects
do or don’t represent a modeling of some sector or quadrant of the real world; and
we’ll end with a couple of follow-up comments in that vein. Writing a Ruby program
can involve thinking about how you might map elements of a domain (even a modest
one-entity domain like “a ticket to an event”) onto a system of objects, such that those
objects can store information and perform tasks. At the same time, it’s important not
Licensed to sam kaplan
Summary
59
to think too rigidly about the relation between objects and the real world. Objectoriented languages certainly offer a strong component of real-world modeling; but
Ruby, at the same time, is extremely elastic in its modeling facilities—as you can see
from how easy it is to enhance a given object’s behavior. The chief goal in designing a
program and the objects inside it is to come up with a system that works and that has
internal consistency.
And, of course, the language offers lots of facilities for developing program structure. Creating objects one by one, as we’ve been doing in this chapter, isn’t much more
than the tip of the iceberg. We’ll expand the discussion exponentially next, by looking
at how to create objects on a multiple, more automated basis using Ruby classes.
Licensed to sam kaplan
Organizing objects
with classes
In this chapter
■
Creating multiple objects with classes
■
Setting and reading object state
■
Automating creation of attribute read and write
methods
■
Class inheritance mechanics
■
Syntax and semantics of Ruby constants
Creating a new object with Object.new—and equipping that object with its own
methods, one method at a time—is a great way to get a feel for the objectcenteredness of Ruby programming. But this approach doesn’t exactly scale; if
you’re running an online box office and your database has to process records for
tickets by the hundreds, you’ve got to find another way to create and manipulate
ticket-like objects in your Ruby programs.
Sure enough, Ruby gives you a full suite of programming techniques for creating objects on a batch basis. You don’t have to define a separate price method for
60
Licensed to sam kaplan
Classes and instances
61
every ticket. Instead, you can define a ticket class, engineered in such a way that every
individual ticket object automatically has the price method.
Defining a class lets you group behaviors (methods) into convenient bundles, so
that you can quickly create many objects that behave essentially the same way. You can
also add methods to individual objects, if that’s appropriate for what you’re trying to
do in your program. But you don’t have to do that with every object, if you model your
domain into classes.
Everything you handle in Ruby is an object; and every object is an instance of some
class. This fact holds true even where it might at first seem a little odd. Integers are
instances of a class, and classes themselves are objects. You’ll learn in this chapter how
this pervasive aspect of the design of Ruby operates.
It’s important to remember that talking about classes doesn’t mean not talking
about objects; that’s why this chapter has the title it has, rather than, say, “Ruby
Classes.” Much of what we’ll look at here pertains to objects and methods—but that’s
because classes are, at heart, a way to organize objects and methods. We’ll be looking
at the kinds of things you can and will do inside classes, as well as looking at what
classes themselves are.
3.1
Classes and instances
In most cases, a class consists chiefly of a collection of method definitions. The class
exists (also in most cases) for the purpose of being instantiated: that is, of having
objects created that are instances of the class.
You’ve already seen instantiation in action. It’s our old signature tune:
obj = Object.new
Object is a built-in Ruby class. When you use the dot notation on a class, you send a
message to the class. Classes can respond to messages, just like objects; in fact, as you’ll
have reason to be aware of in any number of situations, classes are objects. The new
method is a constructor: a method whose purpose is to manufacture and return to you a
new instance of the class, a newly minted object.
You define a class with the class keyword. Classes are named with constants; a constant is a special type of identifier, recognizable by the fact that it begins with a capital
letter. Constants are used to store information and values that don’t change over the
course of a program run.
WARNING
Constants can change: they’re not as constant as their name implies. But
if you assign a new value to a constant, Ruby prints a warning. The best
practice is to avoid assigning new values to constants that you've already
assigned a value to. (See section 3.7.2 for more information about reassignment to constants.)
Let’s define a Ticket class. Inside the class definition, we define a single, simple
method:
class Ticket
def event
Licensed to sam kaplan
62
CHAPTER 3
Organizing objects with classes
"Can't really be specified yet..."
end
end
Now we can create a new ticket object and ask it (pointlessly, but to see the process)
to describe its event:
ticket = Ticket.new
puts ticket.event
The method call ticket.event results in the execution of our event method and,
consequently, the printing out of the (rather uninformative) string specified inside
that method:
Can't really be specified yet...
The information is vague, but the process is fully operational: we have written and
executed an instance method.
Meaning what, exactly?
3.1.1
Instance methods
The examples of method definitions in chapter 2 involved defining methods directly
on individual objects:
def ticket.event
The event method in the previous example, however, is defined in a general way,
inside the Ticket class:
def event
That’s because this event method will be shared by all tickets—that is, by all instances
of Ticket. Methods of this kind, defined inside a class and intended for use by all
instances of the class, are called instance methods. They don’t belong only to one object.
Instead, any instance of the class can call them.
NOTE
Methods that you define for one particular object—as in def
ticket.price—are called singleton methods. You’ve already seen examples, and we’ll look in more depth at how singleton methods work in
chapter 13. An object that has a price method doesn’t much care
whether it’s calling a singleton method or an instance method of its class.
But the distinction is important from the programmer perspective.
Once you’ve defined an instance method in a class, nothing stops you from defining it
again—that is, overriding the first definition with a new one.
3.1.2
Overriding methods
Here’s an example of defining the same method twice in one class:
class C
def m
puts "First definition of method m"
Licensed to sam kaplan