O'Reilly logo

Ruby Cookbook, 2nd Edition by Leonard Richardson, Lucas Carlson

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Ruby 2.1

When the first edition of Ruby Cookbook was published in 2006, Ruby 1.8.4 was the state of the art and Rails had just reached 1.0. Eight years and more than 100 stable releases later, the latest version is now Ruby 2.1.1 and Rails has just reached 4.1.0. Over the last eight years, a lot has changed, both big and small:

  • A bytecode interpreter replaced the old Ruby MRI.

  • RubyGems and Rake became part of the standard library.

  • SOAP and Curses have moved out of the standard library into RubyGems.

  • New syntax primitives have been added for hashes, procs, and more.

  • New methods like Object#tap and String#prepend have been added.

  • New classes like BasicObject, Fiber, and TracePoint have been added.

  • The MD5 standard library was renamed Digest::MD5.

  • And much more…

The end result is a cleaner language that runs faster and more efficiently than ever before. For example, a simple Rails application is 167–200% faster in Ruby 2.1 than 1.8.

For all that has changed, there is thankfully very little that has been broken in terms of backward compatibility. The vast majority of code written for Ruby 1.8 will work in Ruby 2.1 without any modifications. However, and somewhat obviously, if you write code for Ruby 2.1, it will likely not work in Ruby 1.8 with some of the syntax changes introduced.

In between Ruby 1.8 and 2.1 were two other major releases: 1.9 and 2.0. In this chapter, we will group all the changes from versions 1.9 through 2.1 together instead of pointing out the specific dot release in which a feature was added or modified. For example, the YARV bytecode interpreter was added only in Ruby 1.9.4, but we will talk about it as just one of the many differences between Ruby 1.8 and 2.1.

1.1 What’s Different Between Ruby 1.8 and 2.1?

Problem

You want to know the major differences between Ruby 1.8 and 2.1.

Solution

Table 1-1 shows the major changes between Ruby 1.8 and 2.1.

Table 1-1. Major changes by type between Ruby 1.8 and 2.1
Type About Note

New syntax

The operator can replace lambda for brevity.

New syntax

Array

You can use %i(foo bar baz) to specify [:foo, :bar, :baz] for brevity.

New syntax

def

You can define methods like def foo(x: 1); puts x; end.

New class

BasicObject

New root in class hierarchy.

New syntax

Hash

You can use {a: 1, b: 2}, which is like {:a => 1, :b => 2}, for brevity.

New syntax

r

You can apply the r suffix to numbers to specify rationals like 1.2r.

New class

GC::Profiler

Profiles the garbage collector.

New class

Encoding

Represents a character encoding.

New class

Enumerator::Lazy

Delays running enumerations until absolutely necessary.

New class

Fiber

Lightweight processes.

New class

Gem

RubyGems.

New class

Random

Pseudorandom number generator.

New class

RubyVM

The Ruby interpreter.

New class

Socket::Ifaddr

Interface address class.

New class

TracePoint

DTrace-like inspection class.

New method

Array.try_convert

Tries to convert obj into an array.

New method

Array#rotate

Creates a new array by rotating the existing array.

New method

Array#keep_if

Deletes every element where the block evaluates to false.

New method

Array#sample

Chooses a random element.

New method

Array#repeated_permutation

All repeated permutations.

New method

Array#repeated_combination

All repeated combinations.

New method

Hash#to_h

Ubiquitous hash conversion.

New method

Hash#default_proc=

You can now set the default proc after initialization.

New method

Hash#key

An inverted hash lookup.

New method

Hash#keep_if

Deletes every key-value pair where the block evaluates to false.

New method

Hash#assoc

Searches through the hash comparing obj with the key using ==.

New method

Hash#rassoc

Searches through the hash comparing obj with the value using ==.

New method

Hash#flatten

A one-dimensional flattening of this hash.

New method

Hash#compare_by_identity

Compares hashes by their identity.

New method

Enumerable#to_h

Ubiquitous hash conversion.

New method

Enumerable#flat_map

Creates array with the concatenated results of running block once for every element in enum.

New method

Enumerable#each_entry

Calls block once for each element in self, passing that element as a parameter, converting multiple values from yield to an array.

New method

Enumerable#each_with_object

Iterates the given block for each element with an arbitrary object, and returns the initially given object.

New method

Enumerable#chunk

Enumerates over the items, chunking them together based on the return value of the block.

New method

Enumerable#slice_before

Creates an enumerator for each chunked element.

New method

Enumerable#lazy

Delays running enumerations until absolutely necessary.

New method

Exception#cause

Keeps track of the root cause of raised errors.

New method

GC.stat

Inspects the garbage collector.

New method

Kernel#dir

Director name of FILE.

New method

Kernel#callee

Called name of the current method as a symbol.

New method

Kernel#caller_locations

Array of backtrace location objects.

New method

Kernel#spawn

Similar to Kernel.system but doesn’t wait for the command to finish

New method

Kernel#require_relative

Tries to load the library named string relative to the requiring file’s path.

New method

Kernel#Hash

Ubiquitous hash instantiator.

New method

Kernel#Rational

Ubiquitous rational instantiator.

New method

Kernel#Complex

Ubiquitous complex instantiator.

New method

Module#class_variable_get

Gets class variable.

New method

Module#class_variable_set

Sets class variable.

New method

Module#remove_class_variable

Removes class variable.

New method

Module#public_constant

Makes a list of existing constants public.

New method

Module#private_constant

Makes a list of existing constants private.

New method

Module#singleton_class?

Is it a singleton?

New method

Module#prepend

An alternative to Module#include that appends (overwrites) class methods.

New method

Module#public_instance_method

Public instance methods.

New method

Module#refine

Allows you to refine an existing class.

New method

Module#using

Allows you to apply monkey patches in a scoped way.

New method

IO#wait_writable

Waits until a file becomes writable.

New method

Object#!~

Returns true if two objects do not match (using the =~ method).

New method

Object#singleton_class

Returns the singleton class of obj.

New method

Object#untrust

Marks obj as untrusted.

New method

Object#untrusted?

Returns true if the object is untrusted.

New method

Object#trust

Removes the untrusted mark from obj.

New method

Object#remove_instance_variable

Removes the named instance variable from obj.

New method

Object#public_send

Unlike Object#send, this calls public methods only.

New method

Object#public_method

Similar to method, this searches public method only.

New method

Object#singleton_methods

Lists one-off methods.

New method

Object#define_singleton_method

Creates a one-off method.

New method

Object#tap

Taps into a method chain to perform operations on intermediate results.

New method

Range#bsearch

Binary search available in arrays.

New method

Range#cover?

Is obj between the begin and end of the range?

New method

Socket.getifaddrs

Accesses network interfaces.

New method

String#ascii_only?

Returns true for a string that has only ASCII characters.

New method

String#clear

Makes a string empty.

New method

String#chr

A one-character string at the beginning of the string.

New method

String#encode

Encodes a string with an encoding.

New method

String#getbyte

Returns a byte as an integer.

New method

String#setbyte

Modifies a byte as integer.

New method

String#byteslice

A substring of one byte at a position.

New method

String#scrub

Removes garbage bytes from strings.

New method

String#codepoints

Integer ordinals of the characters in str.

New method

String#prepend

Prepends a given string.

New method

String#ord

Returns the integer ordinal of a one-character string.

New method

String#each_codepoint

Enumerates the integerized values of the string.

New method

String#encoding

An encoding object that represents the encoding of the string.

New method

String#force_encoding

Forces an encoding.

New method

String#b

A copied string whose encoding is ASCII-8BIT.

New method

String#valid_encoding?

True for a string that is encoded correctly.

New method

String#to_r

Returns rational number.

New method

String#to_c

Returns complex number.

Removed method

Array#nitems

Removed.

Removed method

Array#indexes

Removed in favor of Array#values_at.

Removed method

Array#indeces

Removed in favor of Array#values_at.

Removed method

Hash#indexes

Removed in favor of Hash#select.

Removed method

Hash#indeces

Removed in favor of Hash#select.

Removed method

Object#id

Removed in favor of Object#object_id.

Removed method

Object#type

Removed in favor of Object#class.

Removed method

Object#to_a

Removed in favor of Kernel#Array.

Removed method

String#each

Removed in favor of String#each_byte and String#each_char.

Removed method

Enumerable#enum_slice

Removed in favor of Enumerable#each_slice.

Removed method

Enumerable#enum_cons

Removed in favor of Enumerable#each_cons.

Removed method

Enumerable#enum_with_index

Removed in favor of Enumerable#each_with_index.

New standard library

rake

No longer an external library.

New standard library

json

No longer an external library.

New standard library

psych

A YAML parser and emitter that leverages libyaml.

New standard library

securerandom

Pseudosecure random number generator.

New standard library

io/console

Add console capabilities to IO.

New standard library

io/nonblock

Add nonblock capabilities to IO.

New standard library

cmath

Trigonometric and transcendental functions for complex numbers.

New standard library

debug

Provides debugger and breakpoints.

New standard library

e2mmap

Exceptions to messages map.

New standard library

fiddle

A libffi wrapper.

New standard library

minitest

Drop-in replacement for test unit.

New standard library

objspace

Object allocation tracing profiling tool.

New standard library

prime

The set of all prime numbers.

New standard library

ripper

Parses your Ruby code into a symbolic expression tree.

New standard library

shellwords

Manipulates strings according to the word parsing rules of bash.

Moved to core

rubygems

No longer an external library and no need to require rubygems

Moved to core

complex

Now part of core; no need to require.

Moved to core

enumerator

Now part of core; no need to require.

Moved to core

rational

Now part of core; no need to require.

Moved to core

thread

Now part of core; no need to require.

Moved to RubyGems

soap

You can use gem install soap.

Moved to RubyGems

curses

You can use gem install curses.

Moved to RubyGems

iconv

You can use gem install iconv.

Moved to RubyGems

parsedate

You can use gem install rubysl-parsedate.

Moved to RubyGems

rinda

You can use gem install rubysl-rinda.

Removed library

finalize

Replaced by objspace.

Removed library

jcode

UTF-8 support is now default; $KCODE is not necessary.

Removed library

wsdl

No longer a standard library.

Removed library

ftools

Merged into fileutils.

Removed library

generator

No longer a standard library.

Removed library

importenv

No longer a standard library.

Removed library

mailread

No longer a standard library.

Removed library

ping

No longer a standard library.

Removed library

runit

No longer a standard library.

Removed library

tcltklib

No longer a standard library.

Removed library

Win32API

No longer a standard library.

Removed library

xsd

No longer a standard library.

1.2 YARV (Yet Another Ruby VM) Bytecode Interpreter

Problem

You want to understand more about the Ruby interpreter changes between Ruby 1.8 and 2.1.

Solution

Since Ruby started in 1995, it originally used the MRI (Matz’s Ruby Interpreter) to interpret Ruby code. Written in C, the MRI (also known as CRuby) was the de facto reference implementation of the Ruby spec until Ruby 1.9.0 was released in 2007. With Ruby 1.9.0, the interpreter was changed from MRI to YARV (Yet Another Ruby VM).

One of the biggest differences between MRI and YARV was the introduction of a bytecode interpreter. With any programming language, the first step to running your code is to tokenize and parse its syntax. The MRI would mix parsing syntax with executing your code, which ended up being prone to memory leaks and slow execution times. The YARV interpreter separates parsing from the running of your code.

The bytecode interpreter takes the syntax tree and passes it to a virtual machine emulator that knows how to translate the bytecode into machine code. The emulator is tuned and optimized for the underlying hardware and knows how to translate instructions to PowerPC or x86 instructions. The result in more efficient execution, less memory usage, and a faster language.

Discussion

To understand bytecode interpreters better, let’s examine a simple Ruby syntax tree (also known as S-expressions):

require 'ripper'
Ripper.sexp("1+1")
# => [:program, [[:binary, [:@int, "1", [1, 0]], :+, [:@int, "1", [1, 2]]]]]

If you have any familiarity with Lisp, you may notice some similarities between a syntax tree and any Lisp dialect. For example, let’s replace the brackets with parentheses and see if the code looks any more familiar:

(program
    (binary
        (int 1 (1 0))
        +
        (int 1 (1 2))
    )
)

The reason that S-expressions look like Lisp is because essentially Lisp is a programming language built directly with S-expressions.

The YARV RubyVM takes these S-expressions and turns them into bytecode. To see what Ruby bytecode looks like, you can use the RubyVM class:

require 'pp'
pp RubyVM::InstructionSequence.compile('1+1').to_a
# ["YARVInstructionSequence/SimpleDataFormat",
#  2,
#  0,
#  1,
#  {:arg_size=>0, :local_size=>1, :stack_max=>2},
#  "<compiled>",
#  "<compiled>",
#  nil,
#  1,
#  :top,
#  [],
#  0,
#  [],
#  [1,
#   [:trace, 1],
#   [:putobject_OP_INT2FIX_O_1_C_],
#   [:putobject_OP_INT2FIX_O_1_C_],
#   [:opt_plus, {:mid=>:+, :flag=>256, :orig_argc=>1, :blockptr=>nil}],
#   [:leave]]]

Bytecode is not nearly as easy to read as S-expressions because the bytecode is the actual instructions sent to the VM, which turn into processor instructions.

The YARV bytecode interpreter is not the only interpreter available to Ruby developers. There is JRuby, Rubinius, MagLev, MacRuby, IronRuby, and Ruby Enterprise Edition (aka REE). Each one is built for a different purpose. For example, JRuby takes pure Ruby syntax and compiles it into Java bytecode instead of YARV bytecode. This allows you to run nearly any Ruby code on any machine running Java.

1.3 Syntax Changes

Problem

You want to know the syntax changes between Ruby 1.8 and 2.1.

Solution

There were three major and two minor syntax additions to Ruby between 1.8 and 2.1.

The three major additions were defining hashes, defining methods, and defining procs.

The two minor additions were in arrays of symbols and defining rationals.

The most obvious syntax addition is for defining hashes. Here is the new way you can do it:

old_way = {:foo => "bar", :one => 1}
new_way = {foo: "bar", one: 1}

You can also apply the same hash syntax when calling methods that take hashes:

def some_method(hash = {})
    # do stuff
end

some_method(:foo => "bar")
some_method(foo: "bar")

You can visually see how this can save you 25% of your keystrokes. Fewer keystrokes leads to fewer typos and bugs. Therefore, this new way of specifying hashes is being quickly adopted and you will see it throughout this book. The old way still works and is not deprecated, but the new way will save you a lot of time over your career with Ruby.

This new syntax for defining hashes has also inspired new keyword arguments for method definitions:

# OLD
def old_way(options={})
    return options[:foo]
end
# => nil

old_way(:foo => "bar")
# => "bar"

old_way
# => nil


# NEW UNNAMED KEYWORD ARGUMENTS
def new_way(**options)
    return options[:foo]
end
# => :new_way

new_way(foo: "bar")
# => "bar"

new_way
# => nil


# NEW NAMED KEYWORD ARGUMENTS
def new_way(foo:)
    return foo
end
# => :new_way

new_way(foo: "bar")
# => "bar"

new_way
# ArgumentError: missing keyword: foo

It is interesting to note that def now returns the symbolic name of the method instead of nil. This allows you to string together private and public calls when defining your classes:

class Foo
    private def baz
        return "yay"
    end

    def bar
        baz
    end
end

Foo.new.baz
# NoMethodError: private method `bar' called for #<Foo:0x007f6b4abbbc98>

Foo.new.bar
# => "yay"

The last big syntax addition is a new way to define procs:

old_way = Proc.new { |a, b| a + b }
old_way.call(1, 2)
# => 3

new_way = ->(a, b) { a + b }
new_way.call(1, 2)
# => 3

This is not only shorter to implement (fewer characters), but it is also consistent with the def method of listing arguments (i.e., it uses parentheses instead of pipes).

The first smaller addition to Ruby syntax is specifying arrays of symbols:

old_way = [:foo, :bar, :baz]
new_way = %i(foo bar baz)

The second smaller addition to Ruby syntax is a shortcut for defining Rational numbers:

old_way = Rational(6, 5)
new_way = 1.2r

All of the syntax additions share the same goal: brevity in keystrokes.

1.4 Keyword Arguments

Problem

You want to know how to specify keyword arguments when defining a method.

Solution

As of Ruby 2.0, you can define Ruby methods in new ways thanks to the idea of keyword arguments. Here is an example of the most complicated method definition you can possibly do now that has every permutation in it:

def foo(a, b="b_default", *c, d:, e: "e_default", **f, &g)
    # do stuff
end
  • a: Required positional argument

  • b: Optional positional argument with a default value

  • c: Splat positional arguments that lack default values

  • d: Declared keyword argument

  • e: Declared keyword argument with a default value

  • f: Double splat keyword arguments that lack default values

  • g: Block argument

Discussion

In Ruby 2.1, hashes were upgraded in many ways. For example, the old trick of using def foo(bar={}) to accept keyword arguments was made into a first-class citizen with the double-splat (**) syntax.

Another way in which hashes were improved was that they preserved their internal order. In Ruby 1.8, the order in which you inserted items into a hash would have no correlation to the order in which they were stored, and when you iterated over a hash, the results could appear totally random. Now hashes preserve the order of insertion, which is clearly useful when you are using them for keyword arguments in method definitions.

The new keyword arguments are a great way to save time while coding. Even a few keystrokes per method can add up quickly.

1.5 Performance Enhancements

Problem

You want to know in which areas there are significant performance enhancements in Ruby 2.1 over Ruby 1.8.

Solution

There are few places that haven’t been internally improved over the last eight years: however, we will touch on a few major areas of enhancements.

The biggest performance enhancements came from the new YARV interpreter, which was discussed in Recipe 1.2.

One of the other large performance-enhancing features of Ruby has been the addition of the lazy method to many basic classes, like Array and Hash, through the Enumerator class:

array = [1,2,3].lazy.map { |x| x * 10 }.select { |x| x > 10 }
# => #<Enumerator::Lazy>

# No calculations are performed until a method is called to the array object

array.to_a
# => [20, 30]

For small arrays like this, the benefit is not clear. However, as you deal with large data and start chaining multiple enumeration methods together, the use of lazy evaluation prevents you from using unnecessary amounts of memory in temporary variables. Here is an example:

def search_file(file_name, term)
  File.open(file_name) do |file|
    file.each.flat_map(&:split).grep(term)
  end
end

The flat_map implementation internally uses lazy enumeration automatically. This means that you are going to iterate over the array only once, instead of twice as you might expect since you run two chained enumeration methods.

Another area where lazy evaluation has had a dramatic effect is in increasing performance with the Ruby garbage collector, since fewer objects are created to clean up in the first place. A lot more has also changed in GC between Ruby 1.8 and 2.1, including a new algorithm for garbage collection called Bitmap Marking. The new algorithm implements a “lazy sweep,” which dramatically reduces overall memory consumption by all Ruby processes.

Another area of improvement is in the require method and File and Pathname classes. They were refactored, which helps considerably for the initial loading times to start complicated frameworks like Rails. One example of the refactoring was that Ruby 1.8 rechecked the $LOAD_PATH to make sure it is all expanded on every require. This change led to a 35% reduction in initial loading time for a simple Rails app.

Stack tracing performance has improved up to 100× between Ruby 1.8 and 2.1 by allowing you to limit the number of frames requested.

The test/unit library was updated to be able to run in parallel, which speeds up unit testing.

There have been many more areas of performance improvements, but these contribute most to the nearly 2× better performance of Ruby 2.1 over Ruby 1.8.

See Also

1.6 Refinements

Problem

You want to monkey-patch some code, but do not want your monkey patches to affect other code.

Solution

As of Ruby 2.0, you can use the refine and using methods to monkey-patch safely within a given context. Here is an example:

module MyMonkeyPatches
  refine String do
    def length
      30
    end
  end
end

class TestMyMonkey
  using MyMonkeyPatches

  def string_length(string)
    string.length
  end
end

string = "foobar"

string.length
# => 6

TestMyMonkey.new.string_length(string)
# => 30

string.length
# => 6

Notice that the entire scope of your monkey-patching stays within your class.

Discussion

Refinements were an experimental feature until Ruby 2.1, but are now mainstream. The ability to dynamically add and modify functionality of classes at any time is both powerful and dangerous. If you don’t like the way something works in Ruby, you can always monkey-patch it. However, the dangerous part is the side effects that you do not anticipate.

In the example within this recipe, you can clearly see that changing the way String#length works to be static can be a bad idea. However, when it is scoped to a special module to encapsulate the refinement, the potential damage is strictly limited.

1.7 Debugging with DTrace and TracePoint

Problem

You want to debug your Ruby app in real time.

Solution

Ruby 2.1 gives you two new and powerful ways to debug your Ruby application: DTrace and TracePoint.

With DTrace, you use the D language for making queries about a running process. Here is the basic syntax for the D language:

probe /test/ { action }

A probe runs the test and if it passes, runs the action. A probe looks like this:

provider:module:function:name

Modules and functions are optional. There are a number of different probe names available within Ruby, but for this example, we will just use the method-entry probe:

$ sudo dtrace -q -n 'ruby*:::method-entry \
  { printf("%s\n", copyinstr(arg0)) }' -c "rake environment"
rake aborted!
No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)

(See full trace by running task with --trace)
RbConfig
RbConfig
RbConfig
RbConfig
RbConfig
RbConfig
...

$ sudo dtrace -q -n 'ruby*:::method-entry \
  { @[copyinstr(arg0), copyinstr(arg1)] = count(); }' -c "rake environment"
rake aborted!
No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)

(See full trace by running task with --trace)

  FileUtils        commands                      1
  Gem              clear_paths                   1
  Gem              default_path                  1
  Gem              detect_gemdeps                1
  Gem              find_home                     1
  Gem              marshal_version               1
...

DTrace is very powerful, but you need to learn the D language to use it effectively. Alternatively, you can use TracePoint, which is built in to Ruby 2.1 as part of the core library. Here is an example of how to use TracePoint:

trace = TracePoint.new(:raise) do |t|
  puts t.inspect
end

trace.enable

require 'doesnt_exit'
# => #<TracePoint:raise@[...]/kernel_require.rb:55>
# => #<TracePoint:raise@[...]/kernel_require.rb:141>
# => #<TracePoint:raise@[...]/workspace.rb:86>
# => LoadError: cannot load such file -- doesnt_exit

Discussion

DTrace is a dynamic tracing framework created by Sun originally to debug both kernel and app code in real time. It is a very sophisticated and flexible tool, but the learning curve is steep because you have to become familiar with a new system.

TracePoint is part of core Ruby and available in every Ruby 2.1 environment. Its wide availability combined with the fact that it is written in Ruby make it an easy way for any Ruby developer to debug his or her application.

If you want to debug your application such that any raised error will dump you into an interactive Ruby environment automatically, you can combine TracePoint with the debug library by adding this simple code to your app:

# fun_with_debug.rb
trace = TracePoint.new(:raise) do |t|
  require 'debug'
end

trace.enable

require 'doesnt_exit'

And then you can see the code in action by just running it:

$ ruby fun_with_debug.rb
Debug.rb
Emacs support available.

[...]/kernel_require.rb:57: RUBYGEMS_ACTIVATION_MONITOR.enter
(rdb:1)

1.8 Module Prepending

Problem

You want to allow modifications to class methods while retaining setup and teardown logic for those methods. For example:

module MyHelper
    def save
        puts "before"
        super
        puts "after"
    end
end

class MyBadClass
    include MyHelper

    def save
        puts "my code"
    end
end

MyBadClass.new.save
# => my code

Notice that you were hoping that the before and after text showed up.

Solution

Ruby 2.1 has a new alternative to include called prepend:

module MyHelper
    def save
        puts "before"
        super
        puts "after"
    end
end

class MyGoodClass
    prepend MyHelper

    def save
        puts "my code"
    end
end

MyGoodClass.new.save
# => before
# => my code
# => after

Discussion

The way that prepend works is pretty simple when you inspect the class hierarchy:

def parents(obj)
  ( (obj.superclass ? parents(obj.superclass) : []) << obj).reverse
end

parents(MyGoodClass)
# => [Class, Object, BasicObject, Module]

parents(MyBadClass)
# => [MyBadClass, BasicObject, Object]

prepend puts the MyHelper module at the top of the class hierarchy, before the definitions in the class itself. include puts the MyHelper at the very bottom of the class hierarchy so it is overwritten when the class is defined.

1.9 New Methods

Problem

You want to know about some of the most useful new methods in Ruby 2.1 since Ruby 1.8.

Solution

With over 70 new methods since Ruby 1.8, it can be hard to figure out which ones merit particular attention. This chapter has already covered some good ones like Enumerable#lazy, Module#refine, and Module#using. However, there are a few more examples of some useful methods you may not have used yet.

People who love O(log n) Array searching will really enjoy Range#bsearch:

ary = [0, 4, 7, 10, 12]
(0...ary.size).bsearch {|i| ary[i] >= 4 } #=> 1
(0...ary.size).bsearch {|i| ary[i] >= 6 } #=> 2
(0...ary.size).bsearch {|i| ary[i] >= 8 } #=> 3
(0...ary.size).bsearch {|i| ary[i] >= 100 } #=> nil

The Exception#cause method keeps track of the root cause of your errors. This is very handy when your rescue code has a bug in it. In Ruby 1.8, the following code would have raised a “method doesn’t exist” error:

begin
  require 'does_not_exist'
rescue
  nil.some_method
end
# LoadError: cannot load such file -- does_not_exist

Gaining insight into the garbage collector is one of the nice capabilities Ruby 2.1 provides:

require 'pp'

pp GC.stat
# {:count=>5,
#  :heap_used=>138,
#  :heap_length=>138,
#  :heap_increment=>0,
#  :heap_live_num=>28500,
#  :heap_free_num=>42165,
#  :heap_final_num=>0,
#  :total_allocated_object=>105777,
#  :total_freed_object=>77277}

One little helper method that is handy is Kernel#dir instead of just Kernel#FILE:

puts __dir__
# /home/user/ruby_app/

Another little helper that is useful is Kernel#require_relative, which allows you to require a local Ruby file:

# old way
require File.expand_path(
  File.join(File.dirname(__FILE__), "..", "lib", "mylib")
)

# new way with __dir__
require File.expand_path(
  File.join(__dir__, "..", "lib", "mylib")
)

# new way with require_relative
require_relative File.join("..", "lib", "mylib")

For sysadmins who need network information, Socket.getifaddrs is your new best friend:

require 'socket'
require 'pp'

pp Socket.getifaddrs
# => [#<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000
	# PACKET[protocol=0 lo hatype=772 HOST hwaddr=00:00:00:00:00:00]>,
#     #<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000
	# 127.0.0.1 netmask=255.0.0.0>,
#     ...

An interesting new method is Enumerable#chunk, which will create subarrays based on repeated information. The next example shows how to use Enumerable#chunk to separate the vowels from the consonants in a sentence. The chunk method is lazy, so no interstitial objects are created in the process of iteration:

"the quick brown fox".each_char.chunk do |letter|
  %w{a e i o u}.include?(letter) ? "vowel" : "consonant"
end.each do |type, letters|
  puts "#{type}: #{letters.join}"
end
# consonant: th
# vowel: e
# consonant:  q
# vowel: ui
# consonant: ck br
# vowel: o
# consonant: wn f
# vowel: o
# consonant: x

And finally, a simple string method, String#prepend, might just make your life a little life easier:

"world".prepend("hello ")
# => "hello world"

1.10 New Classes

Problem

You want to know about some of the most useful new classes in Ruby 2.1 since Ruby 1.8.

Solution

With over nine new classes since Ruby 1.8, it can be hard to figure out which ones merit particular attention. This chapter has already covered some good ones like TracePoint, RubyVM, and Enumerator::Lazy. However, there are a few more examples of some useful classes you may not have used yet.

The Fiber class is an interesting alternative to threads. The biggest difference is that fibers are never preempted and scheduling must be done by the programmer, not the VM. Here is what we mean:

thread = Thread.new do
  puts "Hello world!"
end
# Hello world!

fiber = Fiber.new do
  puts "Hello world!"
end

fiber.resume
# Hello World!

So you can see that Fiber is more in your control than threads, because threads run instantly. However, you can do more with Fiber too:

fiber = Fiber.new do |multiply|
  Fiber.yield multiply * 10
  Fiber.yield multiply * 10_000_000
  "done"
end

fiber.resume(2)
# => 20

fiber.resume(2)
# => 20000000

fiber.resume(2)
# => "done"

fiber.resume(2)
# FiberError: dead fiber called

The Encoding class shows how much Ruby has progressed in terms of character encodings since 1.8. The old hacks are gone, and UTF-8 is now standard with great and simple ways to convert strings natively built into the language:

require 'pp'

pp Encoding.list
# [#<Encoding:ASCII-8BIT>,
#  #<Encoding:UTF-8>,
#  #<Encoding:US-ASCII>,
#  #<Encoding:UTF-16BE (autoload)>,
#  #<Encoding:UTF-16LE (autoload)>,
#  #<Encoding:UTF-32BE (autoload)>,
#  #<Encoding:UTF-32LE (autoload)>,
...

string = "some string \u2764" # <-- this will output a heart

string.encoding
# => #<Encoding:UTF-8>

string = string.encode(Encoding::ISO_8859_1)
# Encoding::UndefinedConversionError: U+2764 from UTF-8 to ISO-8859-1

string = string.force_encoding(Encoding::ISO_8859_1)
# => "some string \xE2\x9D\xA4"

string.encoding
#=> #<Encoding:ISO-8859-1>

The Random class gives you more control over generating random numbers than the simple Kernel#rand method. In fact, the Random.rand method provides the base functionality of Kernel#rand along with better handling of floating-point values:

Random.rand
# => 0.8929923189358412

seed = 1234

random_generator = Random.new(seed)
random_generator.rand
# => 0.1915194503788923
random_generator.rand
# => 0.6221087710398319

random_generator2 = Random.new(seed)
random_generator2.rand
# => 0.1915194503788923
random_generator2.rand
# => 0.6221087710398319

random_generator2.seed
# => 1234

You can see that the Random class allows you to create various generators with arbitrary seeds. In real life, you will want to pick a seed that is as random as possible. You can use Random.new_seed to generate one, but Random.new without any arguments will use Random.new_seed automatically.

1.11 New Standard Libraries

Problem

You want to know the differences between Ruby 1.8 and 2.1.

Solution

With over 16 new standard libraries since Ruby 1.8, it can be hard to figure out which ones merit particular attention. This chapter has already covered some good ones like debug and ripper. However, there are a few more examples of some useful classes you may not have used yet.

The objspace library is an object allocation tracing profiling tool that can be very useful for tracking down memory leaks:

require 'objspace'
require 'pp'
objects = Hash.new(0)
ObjectSpace.each_object{|obj| objects[obj.class] += 1 }
pp objects.sort_by{|k,v| -v}
# [[String, 24389],
#  [Array, 5097],
#  [RubyVM::InstructionSequence, 1027],
#  [Class, 449],
#  [Gem::Version, 327],
#  [Gem::Requirement, 292],
#  [MatchData, 203],
# ...

The prime library has the set of all prime numbers and is lazily enumeratable:

require 'prime'

Prime.each(100) do |prime|
    p prime
end
# => 2, 3, 5, 7, 11, ...., 97

Prime.prime?(1)
# => false

Prime.prime?(2)
# => true

Here is a quick example of cmath for trigonometric and transcendental functions for complex numbers:

require 'cmath'

CMath.sqrt(-9)
# => 0+3.0i

The shellwords library manipulates strings according to the word parsing rules of bash. This is especially helpful for escaping user content for system commands:

require 'shellwords'

argv = Shellwords.split('ls -la')
# => ["ls", "-la"]

argv << Shellwords.escape("special's.txt")
# => ["ls", "-la", "special\\'s.txt"]

command_to_exec = argv.join(" ")

system(command_to_exec)

1.12 What’s Next?

Problem

You want to know what is in store for Ruby 2.2 through Ruby 3.0 and beyond.

Solution

The changes from Ruby 1.8 through Ruby 2.1 have had an intense focus on backward compatibility. Very little has changed to make Ruby 1.8 code not compatible. A few rarely used libraries were removed and a few functions were renamed, but on the whole the focus was compatibility.

One of the big trends that we will continue to see as Ruby evolves is more and more standard libraries moving into gems. The decision to incorporate RubyGems into Core was made to slim down the standard libraries. Between Ruby 1.8 and Ruby 2.1, we saw 17 of 107 (16%) of the standard libraries either moved into RubyGems or removed completely. In the same amount of time, 17 new standard libraries were added, so it ended up as a wash. However, as Ruby development progresses, we will continue to see more library movement into RubyGems.

Another big trend that you can expect to continue to see is new syntax that reduces the number of keystrokes you have to type. The philosophy is that the more you have to type, the more opportunity you have to introduce bugs into your code. All five of the new syntax types added so far accomplished the goal of fewer keystrokes, and we might see more shortening syntax in the future.

There has been a lot of work done on the Ruby garbage collector alogorithms, including two overhauls. We will likely see more work to improve the garbage collection system in the future. We will also see more work on the YARV bytcode interpreter. One speculation is that you may in the future be able to compile your Ruby code into Ruby bytecode files that can be distributed as freely as Ruby source code (like Java bytecode files).

Matz has made it clear that throughout Ruby 2, backward compatibility is key. This has meant that anything that breaks backward compatibility is being explored with in Ruby 3. The roadmap and timeline for Ruby 3 is not clear yet, but you are not likely to see any dramatic changes to Ruby until that time.

See Also

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required