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 thenew
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
=> #
>>
c
.
class
=> Calculator
>>
c
.
divide
(
10
,
2
)
=> 5
Note that defining a method inside aclass
definition adds the method to instances of
that class, not tomain
:
>>
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
=> #
>>
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 thesuper
keyword:
>>
class
BinaryMultiplyingCalculator
<
MultiplyingCalculator
def
multiply
(
x
,
y
)
result
=
super
(
x
,
y
)
result
.
to_s
(
2
)
end
end
=> nil
>>
bmc
=
BinaryMultiplyingCalculator
.
new
=> #
>>
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
=> #
>>
ac
.
add
(
10
,
2
)
=> 12
Here’s a grab bag of useful Ruby features that we’ll need for the
example code in this book.
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
Strings can be
single- or double-quoted. Ruby automatically performs
interpolation
on double-quoted strings, replacing
any#{
withexpression
}
its result:
>>
"hello
#{
'dlrow'
.
reverse
}
"
=> "hello world"
If an interpolated expression returns an
object that isn’t a string, that object is automatically
sent ato_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
=> #
>>
def
o
.
to_s
'a new object'
end
=> nil
>>
"here is
#{
o
}
"
=> "here is a new object"
Something
similar happens whenever IRB needs to display an object: the object is sent theinspect
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
=> #
>>
def
o
.
inspect
'[my object]'
end
=> nil
>>
o
=> [my object]
The#puts
method is
available to every Ruby object (includingmain
), 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
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
=> "."
A
block
is a piece of Ruby
code surrounded bydo
/end
or
curly brackets. Methods can take an implicit block
argument and call the code in that block with
theyield
keyword:
>>
def
do_three_times
yield
yield
yield
end
=> nil
>>
do_three_times
{
puts
'hello'
}
hello
hello
hello
=> nil
Blocks can take
arguments:
>>
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"
Ruby has a built-in module
calledEnumerable
that’s included byArray
,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 ofmessage
writing the block{ |object|
:
object.message
}
>>
(
1
.
.
10
)
.
select
(
&
:even?
)
=> [2, 4, 6, 8, 10]
>>
[
'one'
,
'two'
,
'three'
].
map
(
&
:upcase
)
=> ["ONE", "TWO", "THREE"]
One ofEnumerable
’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"