Chapter 1. Just Enough Ruby
The code in this book is written in Ruby, a programming language that was designed to be simple, friendly, and fun. I’ve chosen it because of its clarity and flexibility, but nothing in the book relies on special features of Ruby, so you should be able to translate the code examples into whatever language you prefer—especially another dynamic language like Python or JavaScript—if that helps to make the ideas clearer.
All of the example code is compatible with both Ruby 2.0 and Ruby 1.9. You can find out more about Ruby, and download an official implementation, at the official Ruby website.
Let’s take a quick tour of Ruby’s features. We’ll concentrate on the parts of the language that are used in this book; if you want to learn more, O’Reilly’s The Ruby Programming Language is a good place to start.
Note
If you already know Ruby, you can safely skip to Chapter 2 without missing anything.
Interactive Ruby Shell
One of Ruby’s friendliest features is its interactive console, IRB, which lets us enter pieces of Ruby code and immediately see the results. In this book, we’ll use IRB extensively to interact with the code we’re writing and explore how it works.
You can run IRB on your development machine by typing irb
at the
command line. IRB shows a >>
prompt when it expects you to provide a Ruby expression. After you type an expression and
hit Enter, the code gets evaluated, and the result is shown at a =>
prompt:
$
irb --simple-prompt
>>
1
+
2
=> 3
>>
'hello world'
.
length
=> 11
Whenever we see these >>
and =>
prompts in the book, we’re interacting with IRB. To make
longer code listings easier to read, they’ll be shown without the prompts, but we’ll still
assume that the code in these listings has been typed or pasted into IRB. So once the book has
shown some Ruby code like this…
x
=
2
y
=
3
z
=
x
+
y
…then we’ll be able to play with its results in IRB:
>>
x
*
y
*
z
=> 30
Values
Ruby is an expression-oriented language: every valid piece of code produces a value when it’s executed. Here’s a quick overview of the different kinds of Ruby value.
Basic Data
As we’d expect, Ruby supports Booleans, numbers, and strings, all of which come with the usual operations:
>>
(
true
&&
false
)
||
true
=> true
>>
(
3
+
3
)
*
(
14
/
2
)
=> 42
>>
'hello'
+
' world'
=> "hello world"
>>
'hello world'
.
slice
(
6
)
=> "w"
A Ruby symbol is a lightweight, immutable value representing a name. Symbols are widely used in Ruby as simpler and less memory-intensive alternatives to strings, most often as keys in hashes (see Data Structures). Symbol literals are written with a colon at the beginning:
>>
:my_symbol
=> :my_symbol
>>
:my_symbol
==
:my_symbol
=> true
>>
:my_symbol
==
:another_symbol
=> false
The special value nil
is used
to indicate the absence of any useful value:
>>
'hello world'
.
slice
(
11
)
=> nil
Data Structures
Ruby array literals are written as a comma-separated list of values surrounded by square brackets:
>>
numbers
=
[
'zero'
,
'one'
,
'two'
]
=> ["zero", "one", "two"]
>>
numbers
[
1
]
=> "one"
>>
numbers
.
push
(
'three'
,
'four'
)
=> ["zero", "one", "two", "three", "four"]
>>
numbers
=> ["zero", "one", "two", "three", "four"]
>>
numbers
.
drop
(
2
)
=> ["two", "three", "four"]
A range represents a collection of values between a minimum and a maximum. Ranges are written by putting a pair of dots between two values:
>>
ages
=
18
.
.
30
=> 18..30
>>
ages
.
entries
=> [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>
ages
.
include?
(
25
)
=> true
>>
ages
.
include?
(
33
)
=> false
A hash is a collection in which every value is associated with a key;
some programming languages call this data structure a “map,”
“dictionary,” or “associative array.” A hash literal is written as a
comma-separated list of
pairs inside curly
brackets:key
=>
value
>>
fruit
=
{
'a'
=>
'apple'
,
'b'
=>
'banana'
,
'c'
=>
'coconut'
}
=> {"a"=>"apple", "b"=>"banana", "c"=>"coconut"}
>>
fruit
[
'b'
]
=> "banana"
>>
fruit
[
'd'
]
=
'date'
=> "date"
>>
fruit
=> {"a"=>"apple", "b"=>"banana", "c"=>"coconut", "d"=>"date"}
Hashes often have symbols as keys, so Ruby provides an alternative
syntax for writing key-value pairs where the
key is a symbol. This is more compact than the key
:
value
syntax
and looks a lot like the popular JSON format for JavaScript objects:key
=> value
>>
dimensions
=
{
width
:
1000
,
height
:
2250
,
depth
:
250
}
=> {:width=>1000, :height=>2250, :depth=>250}
>>
dimensions
[
:depth
]
=> 250
Procs
A proc is an unevaluated chunk of Ruby code that can be passed around
and evaluated on demand; other languages call this an “anonymous
function” or “lambda.” There are several ways of writing a proc literal,
the most compact of which is the ->
syntax:arguments
{ body
}
>>
multiply
=
->
x
,
y
{
x
*
y
}
=> #<Proc (lambda)>
>>
multiply
.
call
(
6
,
9
)
=> 54
>>
multiply
.
call
(
2
,
3
)
=> 6
As well as the .call
syntax,
procs can be called by using square brackets:
>>
multiply
[
3
,
4
]
=> 12
Control Flow
Ruby has if
, case
, and while
expressions, which work in the usual
way:
>>
if
2
<
3
'less'
else
'more'
end
=> "less"
>>
quantify
=
->
number
{
case
number
when
1
'one'
when
2
'a couple'
else
'many'
end
}
=> #<Proc (lambda)>
>>
quantify
.
call
(
2
)
=> "a couple"
>>
quantify
.
call
(
10
)
=> "many"
>>
x
=
1
=> 1
>>
while
x
<
1000
x
=
x
*
2
end
=> nil
>>
x
=> 1024
Objects and Methods
Ruby looks like other dynamic programming languages but it’s unusual in an important way: every value is an object, and objects communicate by sending messages to each other.[1] Each object has its own collection of methods that determine how it responds to particular messages.
A message has a name and, optionally, some arguments. When an object receives a message, its
corresponding method is executed with the arguments from the message. This is how
all work gets done in Ruby; even 1 + 2
means “send the object 1
a message
called +
with the argument 2
,” and the object 1
has a #+
method for handling that message.
We can define our own methods with the def
keyword:
>>
o
=
Object
.
new
=> #<Object>
>>
def
o
.
add
(
x
,
y
)
x
+
y
end
=> nil
>>
o
.
add
(
2
,
3
)
=> 5
Here we’re making a new object by sending the new
message to a special built-in object
called Object
; once the
new object’s been created, we define an #add
method on it. The #add
method adds its two arguments together and
returns the result—an explicit return
isn’t necessary, because the value of the last expression to be executed
in a method is automatically returned. When we send that object the
add
message with 2
and 3
as
arguments, its #add
method is executed
and we get back the answer we wanted.
We’ll usually send a message to an object by writing the receiving object and the message name separated by a dot (e.g., o.add
),
but Ruby always keeps track of the current object (called self
) and will allow us to send a message to that object by writing a message
name on its own, leaving the receiver implicit. For example, inside a method definition the
current object is always the object that received the message that caused the method to
execute, so within a particular object’s method, we can send other messages to the same object
without referring to it explicitly:
>>
def
o
.
add_twice
(
x
,
y
)
add
(
x
,
y
)
+
add
(
x
,
y
)
end
=> nil
>>
o
.
add_twice
(
2
,
3
)
=> 10
Notice that we can send the add
message to o
from within the #add_twice
method by writing add(x, y)
instead of o.add(x, y)
, because o
is the object that the add_twice
message was sent to.
Outside of any method definition, the current object is a special
top-level object called main
, and
any messages that don’t specify a receiver are sent to it; similarly, any
method definitions that don’t specify an object will be made available
through main
:
>>
def
multiply
(
a
,
b
)
a
*
b
end
=> nil
>>
multiply
(
2
,
3
)
=> 6
Classes and Modules
It’s convenient to be able to share method definitions between many objects. In Ruby, we can put method
definitions inside a class, then create new objects by sending the new
message to that class. The objects we get back are instances of the class and incorporate its methods. For
example:
>>
class
Calculator
def
divide
(
x
,
y
)
x
/
y
end
end
=> nil
>>
c
=
Calculator
.
new
=> #<Calculator>
>>
c
.
class
=> Calculator
>>
c
.
divide
(
10
,
2
)
=> 5
Note that defining a method inside a class
definition adds the method to instances of
that class, not to main
:
>>
divide
(
10
,
2
)
NoMethodError: undefined method `divide' for main:Object
One class can bring in another class’s method definitions through inheritance:
>>
class
MultiplyingCalculator
<
Calculator
def
multiply
(
x
,
y
)
x
*
y
end
end
=> nil
>>
mc
=
MultiplyingCalculator
.
new
=> #<MultiplyingCalculator>
>>
mc
.
class
=> MultiplyingCalculator
>>
mc
.
class
.
superclass
=> Calculator
>>
mc
.
multiply
(
10
,
2
)
=> 20
>>
mc
.
divide
(
10
,
2
)
=> 5
A method in a subclass can call a superclass method of the same name by using the super
keyword:
>>
class
BinaryMultiplyingCalculator
<
MultiplyingCalculator
def
multiply
(
x
,
y
)
result
=
super
(
x
,
y
)
result
.
to_s
(
2
)
end
end
=> nil
>>
bmc
=
BinaryMultiplyingCalculator
.
new
=> #<BinaryMultiplyingCalculator>
>>
bmc
.
multiply
(
10
,
2
)
=> "10100"
Another way of sharing method definitions is to declare them in a module, which can then be included by any class:
>>
module
Addition
def
add
(
x
,
y
)
x
+
y
end
end
=> nil
>>
class
AddingCalculator
include
Addition
end
=> AddingCalculator
>>
ac
=
AddingCalculator
.
new
=> #<AddingCalculator>
>>
ac
.
add
(
10
,
2
)
=> 12
Miscellaneous Features
Here’s a grab bag of useful Ruby features that we’ll need for the example code in this book.
Local Variables and Assignment
As we’ve already seen, Ruby lets us declare local variables just by assigning a value to them:
>>
greeting
=
'hello'
=> "hello"
>>
greeting
=> "hello"
We can also use parallel assignment to assign values to several variables at once by breaking apart an array:
>>
width
,
height
,
depth
=
[
1000
,
2250
,
250
]
=> [1000, 2250, 250]
>>
height
=> 2250
String Interpolation
Strings can be single- or double-quoted. Ruby automatically performs
interpolation on double-quoted strings, replacing
any #{
with
its result:expression
}
>>
"hello
#{
'dlrow'
.
reverse
}
"
=> "hello world"
If an interpolated expression returns an object that isn’t a string, that object is automatically
sent a to_s
message and is expected to return a string that can be used in
its place. We can use this to control how interpolated objects
appear:
>>
o
=
Object
.
new
=> #<Object>
>>
def
o
.
to_s
'a new object'
end
=> nil
>>
"here is
#{
o
}
"
=> "here is a new object"
Inspecting Objects
Something similar happens whenever IRB needs to display an object: the object is sent the
inspect
message and should return a string
representation of itself. All objects in Ruby have sensible default implementations of
#inspect
, but by providing our own definition, we can
control how an object appears on the console:
>>
o
=
Object
.
new
=> #<Object>
>>
def
o
.
inspect
'[my object]'
end
=> nil
>>
o
=> [my object]
Printing Strings
The #puts
method is
available to every Ruby object (including main
), and can be used to print strings to
standard output:
>>
x
=
128
=> 128
>>
while
x
<
1000
puts
"x is
#{
x
}
"
x
=
x
*
2
end
x is 128
x is 256
x is 512
=> nil
Variadic Methods
Method definitions can use the *
operator to support
a variable number of arguments:
>>
def
join_with_commas
(
*
words
)
words
.
join
(
', '
)
end
=> nil
>>
join_with_commas
(
'one'
,
'two'
,
'three'
)
=> "one, two, three"
A method definition can’t have more than one variable-length parameter, but normal parameters may appear on either side of it:
>>
def
join_with_commas
(
before
,
*
words
,
after
)
before
+
words
.
join
(
', '
)
+
after
end
=> nil
>>
join_with_commas
(
'Testing: '
,
'one'
,
'two'
,
'three'
,
'.'
)
=> "Testing: one, two, three."
The *
operator can also be used
to treat each element of an array as a separate argument when sending a
message:
>>
arguments
=
[
'Testing: '
,
'one'
,
'two'
,
'three'
,
'.'
]
=> ["Testing: ", "one", "two", "three", "."]
>>
join_with_commas
(
*
arguments
)
=> "Testing: one, two, three."
And finally, *
works in
parallel assignment too:
>>
before
,
*
words
,
after
=
[
'Testing: '
,
'one'
,
'two'
,
'three'
,
'.'
]
=> ["Testing: ", "one", "two", "three", "."]
>>
before
=> "Testing: "
>>
words
=> ["one", "two", "three"]
>>
after
=> "."
Blocks
A block is a piece of Ruby code surrounded by do
/end
or
curly brackets. Methods can take an implicit block
argument and call the code in that block with the yield
keyword:
>>
def
do_three_times
yield
yield
yield
end
=> nil
>>
do_three_times
{
puts
'hello'
}
hello
hello
hello
=> nil
>>
def
do_three_times
yield
(
'first'
)
yield
(
'second'
)
yield
(
'third'
)
end
=> nil
>>
do_three_times
{
|
n
|
puts
"
#{
n
}
: hello"
}
first: hello
second: hello
third: hello
=> nil
yield
returns the result of
executing the block:
>>
def
number_names
[
yield
(
'one'
),
yield
(
'two'
),
yield
(
'three'
)
].
join
(
', '
)
end
=> nil
>>
number_names
{
|
name
|
name
.
upcase
.
reverse
}
=> "ENO, OWT, EERHT"
Enumerable
Ruby has a built-in module called Enumerable
that’s included by Array
, Hash
, Range
, and other classes that represent collections of values.
Enumerable
provides helpful methods
for traversing, searching, and sorting collections, many of which expect
to be called with a block. Usually the code in the block will be run
against some or all values in the collection as part of whatever job the
method does. For example:
>>
(
1
.
.
10
)
.
count
{
|
number
|
number
.
even?
}
=> 5
>>
(
1
.
.
10
)
.
select
{
|
number
|
number
.
even?
}
=> [2, 4, 6, 8, 10]
>>
(
1
.
.
10
)
.
any?
{
|
number
|
number
<
8
}
=> true
>>
(
1
.
.
10
)
.
all?
{
|
number
|
number
<
8
}
=> false
>>
(
1
.
.
5
)
.
each
do
|
number
|
if
number
.
even?
puts
"
#{
number
}
is even"
else
puts
"
#{
number
}
is odd"
end
end
1 is odd
2 is even
3 is odd
4 is even
5 is odd
=> 1..5
>>
(
1
.
.
10
)
.
map
{
|
number
|
number
*
3
}
=> [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
It’s common for the block to take one argument and send it one message with no
arguments, so Ruby provides a &:
shorthand as a more concise way of
writing the block message
{ |object|
object.
:message
}
>>
(
1
.
.
10
)
.
select
(
&
:even?
)
=> [2, 4, 6, 8, 10]
>>
[
'one'
,
'two'
,
'three'
].
map
(
&
:upcase
)
=> ["ONE", "TWO", "THREE"]
One of Enumerable
’s methods, #flat_map
, can be used to evaluate an array-producing block for every value in a collection and
concatenate the results:
>>
[
'one'
,
'two'
,
'three'
].
map
(
&
:chars
)
=> [["o", "n", "e"], ["t", "w", "o"], ["t", "h", "r", "e", "e"]]
>>
[
'one'
,
'two'
,
'three'
].
flat_map
(
&
:chars
)
=> ["o", "n", "e", "t", "w", "o", "t", "h", "r", "e", "e"]
Another useful method is #inject
, which evaluates a block for every
value in a collection and accumulates a final result:
>>
(
1
.
.
10
)
.
inject
(
0
)
{
|
result
,
number
|
result
+
number
}
=> 55
>>
(
1
.
.
10
)
.
inject
(
1
)
{
|
result
,
number
|
result
*
number
}
=> 3628800
>>
[
'one'
,
'two'
,
'three'
].
inject
(
'Words:'
)
{
|
result
,
word
|
"
#{
result
}
#{
word
}
"
}
=> "Words: one two three"
Struct
Struct
is a special Ruby class whose job is to generate other classes. A class
generated by Struct
contains getter
and setter methods for each of the attribute names passed into Struct.new
. The conventional way to use a
Struct
-generated class is to subclass
it; the subclass can be given a name, and it provides a convenient place
to define any additional methods. For example, to make a class called
Point
with attributes called x
and y
, we
can write:
class
Point
<
Struct
.
new
(
:x
,
:y
)
def
+
(
other_point
)
Point
.
new
(
x
+
other_point
.
x
,
y
+
other_point
.
y
)
end
def
inspect
"#<Point (
#{
x
}
,
#{
y
}
)>"
end
end
Now we can create instances of Point
, inspect them in IRB, and send them
messages:
>>
a
=
Point
.
new
(
2
,
3
)
=> #<Point (2, 3)>
>>
b
=
Point
.
new
(
10
,
20
)
=> #<Point (10, 20)>
>>
a
+
b
=> #<Point (12, 23)>
As well as whatever methods we define, a Point
instance responds to the messages
x
and x=
to get and set the value of its x
attribute, and similarly for y
and y=
:
>>
a
.
x
=> 2
>>
a
.
x
=
35
=> 35
>>
a
+
b
=> #<Point (45, 23)>
Classes generated by Struct.new
have other useful
functionality, like an implementation of the equality method #==
, which compares the
attributes of two Struct
s to see if they’re equal:
>>
Point
.
new
(
4
,
5
)
==
Point
.
new
(
4
,
5
)
=> true
>>
Point
.
new
(
4
,
5
)
==
Point
.
new
(
6
,
7
)
=> false
Monkey Patching
New methods can be added to an existing class or module at any time. This is a powerful feature, usually called monkey patching, which lets us extend the behavior of existing classes:
>>
class
Point
def
-
(
other_point
)
Point
.
new
(
x
-
other_point
.
x
,
y
-
other_point
.
y
)
end
end
=> nil
>>
Point
.
new
(
10
,
15
)
-
Point
.
new
(
1
,
1
)
=> #<Point (9, 14)>
We can even monkey patch Ruby’s built-in classes:
>>
class
String
def
shout
upcase
+
'!!!'
end
end
=> nil
>>
'hello world'
.
shout
=> "HELLO WORLD!!!"
Defining Constants
Ruby supports a special kind of variable, called a constant, which should not be reassigned once it’s been created. (Ruby won’t prevent a constant from being reassigned, but it will generate a warning so we know we’re doing something bad.) Any variable whose name begins with a capital letter is a constant. New constants can be defined at the top level or within a class or module:
>>
NUMBERS
=
[
4
,
8
,
15
,
16
,
23
,
42
]
=> [4, 8, 15, 16, 23, 42]
>>
class
Greetings
ENGLISH
=
'hello'
FRENCH
=
'bonjour'
GERMAN
=
'guten Tag'
end
=> "guten Tag"
>>
NUMBERS
.
last
=> 42
>>
Greetings
::
FRENCH
=> "bonjour"
Class and module names always begin with a capital letter, so class and module names are constants too.
Removing Constants
When we’re exploring an idea with IRB it can be useful to ask Ruby to
forget about a constant altogether, especially if that constant is the
name of a class or module that we want to redefine from scratch instead
of monkey patching its existing definition. A top-level constant can be
removed by sending the remove_const
message
to Object
, passing the constant’s
name as a symbol:
>>
NUMBERS
.
last
=> 42
>>
Object
.
send
(
:remove_const
,
:NUMBERS
)
=> [4, 8, 15, 16, 23, 42]
>>
NUMBERS
.
last
NameError: uninitialized constant NUMBERS
>>
Greetings
::
GERMAN
=> "guten Tag"
>>
Object
.
send
(
:remove_const
,
:Greetings
)
=> Greetings
>>
Greetings
::
GERMAN
NameError: uninitialized constant Greetings
We have to use
Object.send(:remove_const,
:
instead of just
NAME
)Object.
remove_
const
(:
,
because NAME
)remove_const
is a private
method that ordinarily can only be called by sending a message from inside the Object
class itself; using Object.send
allows us to bypass this restriction temporarily.
[1] This style comes from the Smalltalk programming language, which had a direct influence on the design of Ruby.
Get Understanding Computation now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.