Test-Driven Infrastructure with Chef (2011)
Chapter 2. An Introduction to Ruby
Before we go any further, I’m going to spend a little time giving you a quick overview of the basics of the Ruby programming language. If you’re an expert, or even a novice Ruby developer, do feel free to skip this section. However, if you’ve never used Ruby, or rarely programmed at all, this should be a helpful introduction. The objective of this section is to make you feel comfortable looking at infrastructure code. The framework we’re focusing our attention on in this book—Chef—is both written in Ruby, and fundamentally is Ruby. Don’t let that scare you—you really only need to know a few things to get started. I’ll also point you to some good resources to take your learning further. Later in the book, we’ll be doing more Ruby, but I will explain pretty much anything that isn’t explicitly covered in this section. Also, remember we were all once in the beginners’ seat. One of the great things about the Chef community is the extent to which it’s supporting and helpful. If you get stuck, hop onto IRC and ask for help.
What Is Ruby?
Let’s start right at the very beginning. What is Ruby? To quote from the very first Ruby book I ever read, the delightfully eccentric Why The Lucky Stiff’s (poignant) Guide to Ruby:
My conscience won’t let me call Ruby a computer language. That would imply that the language works primarily on the computer’s terms. That the language is designed to accommodate the computer, first and foremost. That therefore, we, the coders, are foreigners, seeking citizenship in the computer’s locale. It’s the computer’s language and we are translators for the world.
But what do you call the language when your brain begins to think in that language? When you start to use the language’s own words and colloquialisms to express yourself. Say, the computer can’t do that. How can it be the computer’s language? It is ours, we speak it natively!
We can no longer truthfully call it a computer language. It is coderspeak. It is the language of our thoughts.
— http://bit.ly/1fieouZ
So, Ruby is a very powerful, very friendly language. If you like comparisons, I like to think of Ruby as being a kind of hybrid between LISP, Smalltalk, and Perl. I’ll explain why a bit later. You might already be familiar with a programming language—Perl, or Python, or perhaps C or Java. Maybe even BASIC or Pascal. As an important aside, if you consider yourself to be a system administrator, and don’t know any programming languages, let me reassure you—you already know heaps of languages. Chances are you’ll recognize this:
divert(-1)
divert(0)
VERSIONID(`@(#)sendmail.mc 8.7 (Linux) 3/5/96')
OSTYPE(`linux')
#
# Include support for the local and smtp mail transport protocols.
MAILER(`local')
MAILER(`smtp')
#
FEATURE(rbl)
FEATURE(access_db)
# end
Or possibly this:
Listen 80
<VirtualHost *:80>
DocumentRoot /www/example1
ServerName www.example.com
# Other directives here
</VirtualHost>
<VirtualHost *:80>
DocumentRoot /www/example2
ServerName www.example.org
# Other directives here
</VirtualHost>
What about this?
LOGGER=/usr/bin/logger
DUMP=/sbin/dump
# FSL="/dev/aacd0s1a /dev/aacd0s1g"
FSL="/usr /var"
NOW=$(date +"%a")
LOGFILE="/var/log/dumps/$NOW.dump.log"
TAPE="/dev/sa0"
mk_auto_dump(){
local fs=$1
local level=$2
local tape="$TAPE"
local opts=""
opts="-${level}uanL -f ${tape}"
# run backup
$DUMP ${opts} $fs
if [ "$?" != "0" ];then
$LOGGER "$DUMP $fs FAILED!"
echo "*** DUMP COMMAND FAILED - $DUMP ${opts} $fs. ***"
else
$LOGGER "$DUMP $fs DONE!"
fi
}
Or finally, this:
CC=g++
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=main.cpp hello.cpp factorial.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello
all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
If you’re anything like me, you’ll know what all four of these are right away. You might know exactly what they do. They almost certainly don’t scare you; you will recognize some of it, and you’d know where to go to find out more. My aim is to get you to the same point with Ruby. The thing is, a sysadmin knows a ton of languages; they just mostly suck quite badly. Thankfully, Ruby doesn’t suck at all—Ruby is awesome—it’s easy to use and highly capable.
Grammar and Vocabulary
All languages have grammar and vocabulary. Let’s cover the basic vocabulary and grammar of Ruby. One of the best ways to learn a language is to have a play about in a REPL. REPL stands for “Read, Evaluate, Print, Loop.” A REPL is an interactive environment in which the user writes code, and the shell interprets that code and returns the results immediately. They’re ideal for rapid prototyping and language learning, because the feedback loop is so quick.
The idea of a REPL originated in the world of LISP. Its implementation simply required that three functions be created and enclosed in an infinite loop function. Permit me some hand-waving, as this hides much deep complexity, but at the simplest level the three functions are:
read
Accept an expression from the user, parse it, and store it as a data structure in memory.
eval
Ingest the data structure and evaluate it. This translates to calling the function from the initial expression on each of the arguments provided.
Display the result of the evaluation.
We can actually write a Ruby REPL in one line of code:
$ ruby -e 'loop { p eval gets }'
1+1
2
puts "Hello"
Hello
nil
5.times { print 'Simple REPL' }
Simple REPLSimple REPLSimple REPLSimple REPLSimple REPL5
The first thing to note is every expression has a return value, without exception. The result of the expression 1+1 is 2. The result of the expression "puts "Hello"" is not "hello". The result of the expression is nil, which is Ruby’s way of expressing nothingness. I’m going to dive in right now, and set your expectations. Unlike languages such as Java or C, nil is not a special value or even a keyword. It’s just the same as everything else. In Ruby terms, it’s an object—more on this in a moment. For now, every expression has a return value, and in a REPL, we will always see this.
The functions in our basic REPL should be familiar—we have a loop, we have an eval, the p function prints the output of the eva, and gets reads from the keyboard. Obviously this is a ridiculously primitive REPL, and very brittle and unforgiving:
$ ruby -e 'loop { p eval gets }'
forgive me!
-e:1:in `eval': undefined method `me!' for main:Object (NoMethodError)
from -e:1:in `eval'
from -e:1:in `block in <main>'
from -e:1:in `loop'
from -e:1:in `<main>'
Thankfully Ruby ships with a REPL—Interactive Ruby, or irb. The Ruby REPL is launched by typing irb in a command shell. It also takes the handy command switch --simple-prompt, which declutters the display for our simple use cases.
$ irb
irb(main):001:0> exit
$ irb --simple-prompt
>>
Go ahead and try talking to irb:
>> hello
NameError: undefined local variable or method `hello' for main:Object
from (irb):1
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
Methods and Objects
irb isn’t very communicative. I’d like to draw your attention to two words in the preceding output—method and Object. This introduces one of the most important things to understand about Ruby. Ruby is a pure object-oriented language. Object-oriented languages encourage the idea of modeling the world by designing programs around classes, such as Strings or Files, together with classes we define ourselves. These classes and class hierarchies reflect important general properties of individual nails, horseshoes, horses, kingdoms, or whatever else comes up naturally in the application we’re designing. We create instances of these classes, which we call objects, and work with them.
In object-oriented programming we think in terms of sending and receiving messages between objects. When these instances receive the messages, they need to know what to do with them. The definition of what to do with a message is called a method. I mentioned that Ruby is like Smalltalk; Smalltalk epitomizes this model. Smalltalk allows the programmer to send a message sqrt to an object 2 (called a receiver in Smalltalk), which is a member of the Integer class. To handle the message, Smalltalk finds the appropriate method to compute the required answer for receivers belonging to the Integer class. It produces the answer 1.41421, which is an instance of the Float class. Smalltalk is a 100% pure object-oriented language—absolutely everything in Smalltalk is an object, and every object can send and receive messages. Ruby is almost identical.
We can call methods in Ruby using “dot” syntax—e.g., some_object.my_method. In Ruby everything (pretty much everything) is an object. As such everything (literally everything) has methods, even nil. In Java or C, NULL holds a value to which no valid pointer will ever refer. That means that if you want to check if an object is nil, you compare it with NULL. Not so in Ruby! Let’s check in irb:
>> nil.nil?
=> true
So if everything is an object, what is nil an instance of?
>> nil.class
>> nil.class
=> NilClass
OK, and what about NilClass?
>> NilClass.class
=> Class
Go ahead and try a few others—a number or a string (strings are encased in single or double quotes):
>> 37.class
=> Fixnum
>> "Thirty Seven".class
=> String
In our case, hello isn’t anything—we haven’t assigned it to anything, and it isn’t a keyword in the language, so Ruby doesn’t know what to do. Let’s start, then, with something that Ruby does know about—numbers. Have a go at using Ruby to establish the following:
1. What is 42 multiplied by 412?
2. How many hours are there in a week?
3. If I have 7 students, and they wrote 17,891 lines of code, how many did they write each, on average?
4. >> 42 * 412
5. => 17304
6. >> 24 * 7
7. => 168
8. >> 17891/7
=> 2555
Hang on, that last number doesn’t look right! What’s going on here? Let’s look at the classes of our numbers:
>> 17891.class
=> Fixnum
>> 7.class
=> Fixnum
Ruby only does integer division with Fixnum objects:
>> 2/3
=> 0
Thankfully Fixnum objects have methods to convert them to Floats, which means we can do floating point maths:
>> 2.to_f/3
=> 0.6666666666666666
Let’s try some algebra:
>> hours_per_day = 24
=> 24
>> days_per_week = 7
=> 7
>> hours_per_week = hours_per_day * days_per_week
=> 168
This introduces assignment and variables. Assignment is an operation that binds a local variable (on the left) to an object (on the right). We can see that now hours_per_day is an instance of class Fixnum:
>> hours_per_week.class
=> Fixnum
A variable is a placeholder. And it varies, hence the name:
>> puts "Stephen likes " + drink
Stephen likes Rooibos
=> nil
>> drink = "Beetroot Juice"
=> "Beetroot Juice"
>> puts "Stephen likes " + drink
Stephen likes Beetroot Juice
=> nil
Identifiers
A variable is an example of a Ruby identifier. Wikipedia describes an identifier as follows:
An identifier is a name that identifies (that is, labels the identity of) either a unique object or a unique class of objects, where the “object” or class may be an idea, physical [countable] object (or class thereof), or physical [noncountable] substance (or class thereof).
There are four main kinds of identifiers in Ruby:
1. Variables
2. Constants
3. Keywords
4. Method names
Variables
Looking first at variables, there are actually four types of variables that you’ll encounter in Ruby:
1. Local variables
2. Instance variables
3. Class variables
4. Global variables
You’ll mostly interact with the first two. Local variables begin with a lowercase letter, or an underscore. They may contain only letters, underscores, and/or digits:
>> valid_variable = 9
=> 9
>> bogus-variable - 8
NameError: undefined local variable or method `bogus' for main:Object
from (irb):34
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
>> number9 = "ok"
=> "ok"
>> 9numbers = "not ok"
SyntaxError: (irb):36: syntax error, unexpected tIDENTIFIER, expecting $end
9numbers = "not ok"
^
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
Instance variables store information for an individual instance. They always begin with the “@” sign, and then follow the same rules as local variables.
Class variables are more rarely seen—they store information at the level of the class—i.e., further up the hierarchy than an instance of an object. They begin with “@@”.
Global variables begin with a “$”—these don’t follow the same rules as local variables. You won’t need to use these very often. These can have cryptic looking names such as:
$! # The exception object passed to #raise.
$@ # The stack backtrace generated by the last exception raised.
$& # Depends on $~. The string matched by the last successful match.
$` # Depends on $~. The string to the left of the last successful match.
$' # Depends on $~. The string to the right of the last successful match.
$+ # Depends on $~. The highest group matched by the last successful match.
$1 # Depends on $~. The Nth group of the last successful match. May be > 1.
$~ # The MatchData instance of the last match. Thread and scope local. MAGIC
The preceding global variables are taken from the excellent Ruby quick reference by Ryan Davis (creator and maintainer of Minitest)—I recommend you bookmark it, or print it out.
On the subject of cryptic symbols, I mentioned that Ruby is akin to Perl. Ruby’s creator, Yukihiro Matsumoto (Matz), describes the history of Ruby in an interview with Bruce Stewart:
Back in 1993, I was talking with a colleague about scripting languages. I was pretty impressed by their power and their possibilities. I felt scripting was the way to go.
As a long time object-oriented programming fan, it seemed to me that OO programming was very suitable for scripting, too. Then I looked around the Net. I found that Perl 5, which had not released yet, was going to implement OO features, but it was not really what I wanted. I gave up on Perl as an object-oriented scripting language.
Then I came across Python. It was an interpretive, object-oriented language. But I didn’t feel like it was a “scripting” language. In addition, it was a hybrid language of procedural programming and object-oriented programming.
I wanted a scripting language that was more powerful than Perl, and more object-oriented than Python. That’s why I decided to design my own language.
— http://bit.ly/18FHd3p
He adds:
Ruby’s class library is an object-oriented reorganization of Perl functionality—plus some Smalltalk and Lisp stuff. I used too much I guess. I shouldn’t have inherited $_, $&, and the other, ugly style variables.
If you’re familiar with Perl, I commend to you: comparing Ruby and Perl.
Constants
Constants are like variables, only their value is supposed to remain unchanged. In actual fact, this isn’t enforced by Ruby—it just complains if you waver in your constancy:
>> MY_LOVE = "infinite"
=> "infinite"
>> MY_LOVE = "actually, rather unreliable"
(irb):38: warning: already initialized constant MY_LOVE
=> "actually, rather unreliable"
Constants begin with an uppercase letter—conventionally they may simply be capitalized (Washington), be in all caps (SHOUTING), camelcase (StephenNelsonSmith), or capitalized snakecase (BOA_CONSTRICTOR).
Keywords
Keywords are built-in terms hardcoded into the language. You can find them listed at http://ruby-doc.org/docs/keywords/1.9/. Examples include end, false, unless, super, break. Trying to use these as variables will result in errors:
>> super = "dooper"
SyntaxError: (irb):1: syntax error, unexpected '='
super = "dooper"
^
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
>> false = "hope"
SyntaxError: (irb):2: Can't assign to false
false = "hope"
^
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
>> unless = 63
SyntaxError: (irb):3: syntax error, unexpected '='
unless = 63
^
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
Method names
Method names are the fourth and final kind of identifier. We’ve already seen one of these at play:
>> 7.to_f
=> 7.0
Method names adhere to the same naming constraints as local variables, with a few exceptions. They can end in “?”, “!”, or “=”, and it’s possible to define methods such as “[]” or “<=>”. This might sound like a recipe for confusion, but it’s very much by design. Methods are just part of the furniture; Ruby without methods would be like ice cream…without ice. Or cream.
More About Methods
We discussed the idea of objects and methods at the very start of this section. However, it bears repeating, as the object is the most fundamentally important concept in Ruby. When we send a message to an object, using the dot operator, we’re calling some code that the object has access to. Strings have some nice methods to illustrate this:
>> "STOP SHOUTING".downcase
=> "stop shouting"
>> "speak louder".upcase
=> "SPEAK LOUDER"
The pattern is: OBJECT dot METHOD. To the left of the dot we have the receiver and to the right, the method we’re calling, or the message we’re sending.
Methods can take arguments:
>> "Tennis,Elbow,Foot".split
=> ["Tennis,Elbow,Foot"]
>> "Tennis,Elbow,Foot".split(',')
=> ["Tennis", "Elbow", "Foot"]
The first attempted to split the string on white space but didn’t find any. The second split the string on the comma. The result of each method is an Array—more on arrays shortly.
I mentioned that methods may end in signs such as “?” or “!”. Here’s an example:
>> [1,2,3,4].include? 3
=> true
Here we’re asking Ruby if the array [1,2,3,4] includes the number 3. The answer—the result of evaluating the expression—was true. A method with “!” on the end means “Do this, and make the change permanent!” We looked at downcase. Here it is again:
>> curse = "BOTHERATION!"
=> "BOTHERATION!"
>> curse.downcase
=> "botheration!"
>> curse
=> "BOTHERATION!"
>> curse.downcase!
=> "botheration!"
>> curse
=> "botheration!"
One final important idea connected with methods is the idea of method_missing. It is possible for an object to have the special method method_missing. In this case, if the object receives a message for which there is no corresponding method, rather than just throwing away the message and raising an error, Ruby can take the message and redirect it or use it in many powerful ways. Chef uses this functionality extensively to implement the language used to build infrastructure. This is an advanced topic, and I refer you to some of the classic texts—particularlyMetaprogramming Ruby (The Pragmatic Programmers) if you wish to learn more.
We create methods using the def keyword:
>> def shout(something)
>> puts something.upcase
>> end
=> nil
>> shout('i really like ruby')
I REALLY LIKE RUBY
=> nil
We created a method and specified that it take an argument called “something”. We then called the upcase method on the something. This worked fine, because the argument we passed was a string. Look what happens if we give bogus input:
>> shout(42)
NoMethodError: undefined method `upcase' for 42:Fixnum
from (irb):7:in `shout'
from (irb):10
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
>> shout('more', 'than', 'one', 'thing')
ArgumentError: wrong number of arguments (4 for 1)
from (irb):6:in `shout'
from (irb):11
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
You might be wondering on what object the shout method is being called! The answer is that it’s being called on the self object. The self object provides access to the current object—the object that is receiving the current message. If the receiver is explicitly stated, it’s obvious which object is the receiver. If the receiver is not specified, it is implicitly the self object. The self object, when we run in irb, is:
>> self
=> main
>> self.class
=> Object
What’s all this about? We’re basically looking at the top level of Ruby. If we type methods, we see there are some methods available at the top level:
>> methods
=> [:to_s, :public, :private, :include, :context, :conf, :irb_quit, :exit, :quit, :irb_print_working_workspace, :irb_cwws, :irb_pwws, :cwws, :pwws, :irb_current_working_binding, :irb_print_working_binding, :irb_cwb, :irb_pwb, :irb_chws, :irb_cws, :chws, :cws, :irb_change_binding, :irb_cb, :cb, :workspaces, :irb_bindings, :bindings, :irb_pushws, :pushws, :irb_push_binding, :irb_pushb, :pushb, :irb_popws, :popws, :irb_pop_binding, :irb_popb, :popb, :source, :jobs, :fg, :kill, :help, :irb_exit, :irb_context, :install_alias_method, :irb_current_working_workspace, :irb_change_workspace, :irb_workspaces, :irb_push_workspace, :irb_pop_workspace, :irb_load, :irb_require, :irb_source, :irb, :irb_jobs, :irb_fg, :irb_kill, :irb_help, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
These methods must be being called on something. We know that something is self. What is self? When we inspect it, we see:
>> self.inspect
=> "main"
>> self.class
=> Object
So self is an instance of Object that evaluates to the string main. By default it has no instance variables:
>> instance_variables
=> []
But we can add them:
>> @loathing = true
=> true
>> instance_variables
=> [:@loathing]
So self is just an instance of Object. But what’s going on when we define a method?
>> def say_hello
>> puts "hello"
>> end
=> nil
We can see that the method is now available to self.
>> self.methods.grep /hello/
=> [:say_hello]
Here we called the methods method, which we know returns an array, and then called the grep method on the array. The grep method takes a pattern to match—we specified /hello/, which matched only one method, so Ruby returned it. What we’ve effectively done is defined a top-level method on Object. We can see this by inspecting the method:
>> method(:say_hello).owner
=> Object
Normally we would define methods in the context of a class, so let’s look at classes.
Classes
Classes describe the characteristics and behavior of objects—simply put, a class is just a collection of properties with methods attached. We’re familiar with the idea of biological classification—a mechanism of grouping and categorizing organisms into genus or species. For example the walnut tree and the pecan tree are both instances of the family Juglandaceae. In Ruby every object is an instance of precisely one class. We tend not to deal with classes as much as instances of classes. One particularly powerful feature of Ruby is its ability to give instances of a class some attributes or methods belonging to a different type of object. This idea—the Mixin—is seen fairly frequently, and we’ll cover it later.
Let’s see an example of creating a class:
>> class Pet
>> end
=> nil
The class doesn’t do anything interesting yet, but we can create an instance of it:
>> rupert = Pet.new
=> #<Pet:0x00000001d97b68>
>> rupert.class
=> Pet
Let’s extend our class a little. It might be nice to be able to know the name of the pet. This allows us to introduce the idea of the constructor. The constructor is the method that is called when a new instance of a class is initialized. We can use it to set up state for the object. Let’s switch to using a text editor for this example:
$ emacs pet.rb
class Pet
def initialize(name)
@name = name
end
def name
@name
end
end
corins_pet = Pet.new("Rupert")
puts "The pet is called " + corins_pet.name
$ ruby pet.rb
The pet is called Rupert
So the constructor has the method initialize. We’ve said that it takes an argument, and we’re setting an instance variable to hold the state of the pet’s name. Later we have a method, name, which returns the value of the instance variable. Simple.
The trouble is, children are fickle. What they thought was a great name turns out to be a dreadful name a few days later. Unless we were going to be draconian, and insist that pet names be immutable, it might be nice to allow the child to rename the pet. Let’s add an instance method that will change the name:
$ emacs pet.rb
class Pet
def initialize(name)
@name = name
end
def name=(name)
@name=name
end
def name
@name
end
end
pet = Pet.new("Rupert")
puts "The pet is called " + pet.name
puts "ALL CHANGE!"
pet.name = "Harry"
puts "The pet is now called " + pet.name
$ ruby pet.rb
The pet is called Rupert
ALL CHANGE!
The pet is now called Harry
Here’s another example of a method name with some odd-looking punctuation at the end. But this is how we implement a method that allows assignment. This class is looking a bit lengthy (and frankly, ugly) for such a featureless class. Thankfully Ruby provides some syntactic sugar, which provides the ability to get and set instance variables. Here’s how it works:
class Pet
attr_accessor :name
def initialize(name)
@name = name
end
end
pet = Pet.new("Rupert")
puts "The pet is called " + pet.name
puts "ALL CHANGE!"
pet.name = "Harry"
puts "The pet is now called " + pet.name
What’s actually going on here is that when the Class block is evaluated, the attr_accessor method is run, which generates the methods we need. Ruby is particularly good at this—metaprogramming—code that writes code. In more advanced programming, it’s possible to overwrite the default attr_accessor method and make it do what we want—great is the power of Ruby. But why all the fuss? Why can’t we just peek into the class and see the instance variable? Remember, Ruby operates by sending and receiving messages, and methods are the way classes deal with the messages. The same is so for instance variables. We can’t access them without calling a method—it’s a design feature of Ruby.
Right, that’s enough of messages and classes for the time being. Let’s move on to look at some data structures.
Arrays
Arrays are indexed collections of objects, which keep this in a specific order:
>> children = ["Melisande", "Atalanta", "Wilfrid", "Corin"]
=> ["Melisande", "Atalanta", "Wilfrid", "Corin"]
>> children[0]
=> "Melisande"
>> children[2]
=> "Wilfrid"
The index starts at zero, and we can request the nth item by calling the “[]” method. This is very important to grasp. We’re sending messages again! We’re sending the [] message to the children array, with the argument “2”. The array knows how to handle the message and replies with the child at position 2 in the array. Arrays have convenient aliases:
>> children.first
=> "Melisande"
>> children.last
=> "Corin"
We can append to an array using the “<<” method. Suppose we adopted orphan Annie:
>> children << "Annie"
=> ["Melisande", "Atalanta", "Wilfrid", "Corin", "Annie"]
>> children.count
=> 5
Collections of objects can be iterated over. For example:
>> children.each { |child| puts "This child is #{child}" }
This child is Melisande
This child is Atalanta
This child is Wilfrid
This child is Corin
This child is Annie
=> ["Melisande", "Atalanta", "Wilfrid", "Corin", "Annie"]
This introduces two new pieces of Ruby syntax—the block and string interpolation. String interpolation is an alternative to the rather clumsy looking use of the “+” operator. Ruby evaluates the expression between #{} and prints the result.
>> dinner = "curry"
=> "curry"
>> puts "Stephen is going to eat #{dinner} for dinner"
Stephen is going to eat curry for dinner
=> nil
Of course the expression could be much more complex:
>> foods = ["chips", "curry", "soup", "cat sick"]
=> ["chips", "curry", "soup", "cat sick"]
>> 10.times { puts "Stephen will eat #{foods.sample} for dinner this evening." }
Stephen will eat chips for dinner this evening.
Stephen will eat soup for dinner this evening.
Stephen will eat soup for dinner this evening.
Stephen will eat cat sick for dinner this evening.
Stephen will eat chips for dinner this evening.
Stephen will eat curry for dinner this evening.
Stephen will eat chips for dinner this evening.
Stephen will eat soup for dinner this evening.
Stephen will eat soup for dinner this evening.
Stephen will eat curry for dinner this evening.
=> 10
Here we see another example of a block! The integer “10” has a method times, which takes a block as an argument.
Blocks allow a set of instructions to be grouped together and associated with a method. In essence, they’re a block of code that can be passed as an argument to a method. They’re a particular speciality of Ruby and are incredibly powerful. However, they’re also a bit tricky to understand at first.
For programmers new to Ruby, code blocks are generally the first sign that they have definitely departed Kansas. Part syntax, part method, and part object, the code block is one of the key features that gives the Ruby programming language its unique feel.
— Russ Olser Eloquent Ruby
Blocks are created by appending them to the end of a method. Ruby takes the content of the block and passes it to the method. Depending on the length of the block, Ruby convention is either:
§ If one line, then place in curly braces {} (unless the code has a side effect, such as writing to a file, in which case the do … end form applies)
§ If more than one line, then replace curly braces with do … end
The method definition itself has code to handle the contents of the block. For now it’s sufficient to understand that blocks are a kind of anonymous function—that is a function that we defined and call, without ever binding it to an identifier. Ruby uses them a great deal to implement iterators.
Although present in Smalltalk, I think that it’s when looking at blocks that we see most evidence of Lisp in Ruby. Lisp provides the lambda expression as a mechanism for creating a nameless or anonymous function, and passing it to another function. Lisp also has the concept of a closure—that is an anonymous function that can refer to variables visible at the time it was defined. Referring again to a Matz interview, the creator of Ruby says:
…we can create a closure out of a block. A closure is a nameless function the way it is done in Lisp. You can pass around a nameless function object, the closure, to another method to customize the behavior of the method. As another example, if you have a sort method to sort an array or list, you can pass a block to define how to compare the elements. This is not iteration. This is not a loop. But it is using blocks … the first reason [for this implementation] is to respect the history of Lisp. Lisp provided real closures, and I wanted to follow that.
— Bill Venners http://www.artima.com/intv/closuresP.html
Ruby features a wide range of iterators for various purposes. One commonly used one is map. The map method takes a block, and produces a new array with the results of the block being applied, without changing the initial array:
>> children.map do |child|
?> if child == "Annie"
>> child + " the Orphan"
>> else
?> child + " Nelson-Smith"
>> end
>> end
=> ["Melisande Nelson-Smith", "Atalanta Nelson-Smith", "Wilfrid Nelson-Smith", "Corin Nelson-Smith", "Annie the Orphan"]
>> children
=> ["Melisande", "Atalanta", "Wilfrid", "Corin", "Annie"]
The block arguments lie between the two pipe symbols. I find Why The Lucky Stiff’s description particularly apt:
The curly braces give the appearance of crab pincers that have snatched the code and are holding it together. When you see these two pincers, remember that the code inside has been pressed into a single unit…. I like to think of the pipe characters representing a tunnel. They give the appearance of a chute that the variables are sliding down. Variables are passed through this chute (or tunnel) into the block.
— WTLSPGTR
Conditional logic
Ruby supports various control structures to manage the flow of data through a program. The most commonly used are those that fork based on decisions:
>> 10.times do
?> grub = foods.sample
>> if grub == "cat sick"
>> puts "Stephen is not very hungry, for some reason."
>> else
?> puts "Stephen will eat #{grub} for dinner this evening."
>> end
>> end
Stephen will eat chips for dinner this evening.
Stephen will eat curry for dinner this evening.
Stephen will eat soup for dinner this evening.
Stephen is not very hungry, for some reason.
Stephen is not very hungry, for some reason.
Stephen will eat chips for dinner this evening.
Stephen will eat chips for dinner this evening.
Stephen will eat soup for dinner this evening.
Stephen will eat curry for dinner this evening.
Stephen will eat soup for dinner this evening.
=> 10
In addition to if and else, we also have elsif:
>> def editor_troll(editor)
>> if editor == "emacs"
>> puts "Best editor in the world!"
>> elsif editor =~ /vi/
>> puts "Be gone with you, you bearded weirdo!"
>> else
?> puts "*yawn* - sorry - were you talking to me?"
>> end
>> end
=> nil
>> editor_troll("emacs")
Best editor in the world!
=> nil
>> editor_troll("elvis")
Be gone with you, you bearded weirdo!
=> nil
>> editor_troll("nano")
*yawn* - sorry - were you talking to me?
=> nil
>> editor_troll("vim")
Be gone with you, you bearded weirdo!
=> nil
>> editor_troll("textmate")
*yawn* - sorry - were you talking to me?
=> nil
A handy option is the unless keyword:
>> def mellow_opinion(editor)
>> unless editor.length == 0
>> puts "Cool, dude. I hear #{editor} is really nice."
>> end
>> end
=> nil
>> mellow_opinion("emacs")
Cool, dude. I hear emacs is really nice.
=> nil
>> mellow_opinion("notepad")
Cool, dude. I hear notepad is really nice.
=> nil
>> mellow_opinion("")
=> nil
The final control structure you’ll come across is the case statement:
>> def seasonal_garment(season)
>> case season
>> when "winter"
>> puts "Wooly jumper and hat!"
>> when "spring"
>> puts "Shirt and jacket!"
>> when "summer"
>> puts "Shorts and t-shirt!"
>> when "autumn"
>> puts "Hmm... English? Raincoat!"
>> when "fall"
>> puts "Bit like spring, really."
>> end
>> end
>> seasonal_garment("winter")
Wooly jumper and hat!
=> nil
>> seasonal_garment("fall")
Bit like spring, really.
=> nil
>> seasonal_garment("autumn")
Hmm... English? Raincoat!
=> nil
Typically, the case statement is used if there are more than three options, as multiple elsif statements look a bit ugly, but it’s really just a matter of style.
Hashes
A hash is another sort of collection in Ruby. Variously called a dictionary or associative array in other languages, its defining feature is that the index can be something other than a static value. Hashes are commonly used in Chef for key/value pairs:
>> wines = {}
=> {}
>> wines['red'] = ["Rioja", "Barolo", "Zinfandel"]
=> ["Rioja", "Barolo", "Zinfandel"]
>> wines['white'] = ["Chablis", "Riesling", "Sauvignon Blanc"]
=> ["Chablis", "Riesling", "Sauvignon Blanc"]
>> wines
=> {"red"=>["Rioja", "Barolo", "Zinfandel"], "white"=>["Chablis", "Riesling", "Sauvignon Blanc"]}
The great thing about hashes is they can be deeply nested. We can add, for example:
>> wines['sparkling'] = {"Cheap" => ["Asti Spumante", "Cava"], "Moderate" => ["Veuve Cliquot", "Bollinger NV"], "Expensive" => ["Krug", "Cristal"]}
=> {"Cheap"=>["Asti Spumante", "Cava"], "Moderate"=>["Veuve Cliquot", "Bollinger NV"], "Expensive"=>["Krug", "Cristal"]}
>> wines['sparkling']["Cheap"]
=> ["Asti Spumante", "Cava"]
>> wines['sparkling']["Expensive"]
=> ["Krug", "Cristal"]
Again, of great significance for a Chef developer is to understand the message sending aspects. We’re calling the [] method on the wines hash, which gives us another hash, on which we’re calling the [] method, to get the expensive sparkling wines array. This is a common pattern in Chef and leads to perhaps the most common error message you’ll see:
>> wines['sparklin']['Cheap']
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):88
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
What happened? Well, I’ve drilled you so hard, I am sure you can say right away. Everything in Ruby is an object. Everything is an instance of a class. Even nothing at all:
>> nil.class
=> NilClass
>> nil.class.methods
=> [:allocate, :superclass, :freeze, :===, :==, :<=>, :<, :<=, :>, :>=,:to_s, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :editor_troll, :mellow_opinion, :seasonal_garment, :nil?, :=~, :!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
So an instance of NilClass has some methods, but it doesn’t have a [] method! Where did we get nil from? Well, because of our typo, we requested the value of a non-existent key. Let’s be more explicit:
>> wines['bogus']
=> nil
>> wines['bogus'].class
=> NilClass
>> wines['bogus']['reasonable']
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):93
from /opt/rubies/1.9.3-p429/bin/irb:12:in `<main>'
Hashes have a number of convenient methods that you’ll see used:
>> wines.keys
=> ["red", "white", "sparkling"]
>> wines.values
=> [["Rioja", "Barolo", "Zinfandel"], ["Chablis", "Riesling", "Sauvignon Blanc"], {"Cheap"=>["Asti Spumante", "Cava"], "Moderate"=>["Veuve Cliquot", "Bollinger NV"], "Expensive"=>["Krug", "Cristal"]}]
And, by definition, hashes are enumerable—they take a block and can be iterated over. When iterating over a block, we typically pass two arguments into the block, representing the key and value:
>> wines.each do |color, types|
?> if types.respond_to?(:keys)
>> puts "For #{color} wine, you have more options: #{types.keys}"
>> end
>> end
For sparkling wine, you have more options: ["Cheap", "Moderate", "Expensive"]
Truthiness
Let’s quickly talk about the abstract notion of truth. Truth, for Hegel, and many who followed in his footsteps is not merely semantic. It is a much richer metaphysical concept. Oh, sorry, wrong book. In Ruby, everything except nil and false is considered true. This includes 0. Let me say that again. In Ruby, absolutely everything is considered true. If in doubt, assume true. Obviously, false is false.
>> if 0
>> puts "0 is true"
>> else
?> puts "0 is false"
>> end
0 is true
=> nil
Note that nil is also not explicitly evaluated to false. A value can be coerced to true or false with “!!”. !! is just ! (the boolean negation operator) written twice. It will negate the argument, then negate the negation:
>> !nil
=> true
>> !!nil
=> false
>> !0
=> false
>> !!0
=> true
The first ! will convert the argument to a boolean (e.g., true if it’s nil or false, and false otherwise). The second will negate that again so that you get the boolean value of the argument, false for nil or false, true for just about everything else. This is mostly useful in the REPL, but occasionally you will see it in idiomatic usage.
Here’s a little table of truthiness for reference:
Operators
This leads nicely into a quick tour of Ruby operators. An operator is a token that represents an operation—for example, a comparison or a subtraction. It takes action on an operand and combines to form an expression. Ruby has lots of operators, covering boolean logic, assignment, and arithmetic. The best condensed summary is to be found in David Flanagan and Yukihiro Matsumoto’s The Ruby Programming Language (O’Reilly), specifically section 4.6. We’ll cover only those most pertinent to Chef.
First, arithmetic. Ruby provides the \+, -, *, /, and % operators to perform addition, subtraction, and integer division. The module operator can be used to calculate the remainder. This can also be used on String and Array. In the case of a string, \+ will concatenate and * will repeat. In the case of an array, + will concatenate and - will subtract:
>> 6*6
=> 36
>> 24/12
=> 2
>> 37-8
=> 29
>> 17+17
=> 34
>> 20/3
=> 6
>> 20 % 3
=> 2
>> "Classical" + "Dutch"
=> "ClassicalDutch"
>> "Ruby" * 4
=> "RubyRubyRubyRuby"
>> ["Gatting", "Gower"] + ["Embury", "Edmunds"]
=> ["Gatting", "Gower", "Embury", "Edmunds"]
>> ["Lineker", "Platt", "Waddle"] - ["Waddle"]
=> ["Lineker", "Platt"]
Secondly, comparison. The <, <=, >,and >= operators make comparisons between objects that have a natural order—such as numbers or strings:
>> "llama" > "frog"
=> true
>> "apple" > "elephant"
=> false
>> "zelda" < "ganon"
=> false
>> "art" < "life"
=> true
>> 7 > 4
=> true
>> 6 >= 6
=> true
Particularly useful is the so-called “spaceship” operator <=>—another import from Perl. This makes a relative comparison between two operands. If the one on the left is less than the one on the right, it returns +1. If they are equal, it returns nil or 0, and if the one on the right is greater than the one on the left, it returns -1. This is hugely useful in sorting!
>> "Individuals and interactions" <=> "Processes and tools"
=> -1
>> "Working software" <=> "Comprehensive documentation"
=> 1
>> "Customer collaboration" <=> "Contract negotiation"
=> 1
>> "Responding to change" <=> "Following a plan"
=> 1
>> "SunBlade" <=> "SunBlade"
=> 0
Thirdly, equality. == is the equality operator—it tests whether the left hand operand is equal to the right hand operand. Its opposite is !=. Take care not to use the assignment operator = in place of the equality operator!
>> melisande = 4
=> 4
>> adam = 4
=> 4
>> melisande == adam
=> true
>> helena = 42
=> 42
>> stephen = 37
=> 37
>> helena = stephen
=> 37
>> helena
=> 37
>> stephen
=> 37
Also very handy is the pattern-matching operator, =~. I think the characters in this resemble someone saying “kinda like” while rocking their hand in a circumspect manner, which gives a useful aide memoire. This operator is most commonly used when comparing strings:
>> "The Nimzo-Larsen attack" =~ /ck$/
=> 21
>> "The Nimzo-Larsen attack" =~ /xy$/
=> nil
The operator takes a regular expression as its rightmost operand. If the match is found, the index position where the match began is returned. Otherwise, nil is returned. Even a basic discussion of regular expressions is likely to be beyond the scope of this chapter, but with a little practice they’re very easy to pick up. One of the best recent introductions to Ruby regular expressions can be found at Bluebox. This is the first of a three-part article and is highly recommended. The standard textbooks referenced in the bibliography also give ample coverage. For a more general discussion on the subject, and its fascination and beauty, see Mastering Regular Expressions by Jeffrey Friedl (O’Reilly).
Bundler
The final subject is Bundler, which should be covered before moving beyond the fundamentals of the Ruby language. Bundler is so fundamental to how Ruby development is carried out, and how tools are created and shared, that a thorough understanding of it is required.
Bundler exists to solve two problems:
§ How to ensure that the appropriate dependencies are installed for a given problem without encountering unpleasant ordering issues or cyclical dependencies.
§ How to share a software project between other developers, or other machines or environments, and be confident the application and its dependencies will behave in the same way.
The first problem can be illustrated by three imaginary Rubygems:
1. Oxygen
2. Whale
3. Human
Let’s imagine that we have Oxygen 3.1.1 and Oxygen 1.8.5 installed on our system. Now, imagine that we want to install Whale 1.0.0. Whale depends on Oxygen, and the dependency is specified as “>= 1.5.0”. When we run gem install whale, Rubygems will solve the dependency for Whale, and identify that Oxygen 3.1.1 will satisfy the dependency, and so will start using this version of Oxygen. Suppose we then decide to install Human 2.1.0. Human is a bit more picky about the Oxygen upon which it depends than Whale, and the dependency has been specified as “= 1.8.5”. If we type gem install whale, Rubygems will identify that to solve the dependency, it will need to install version 1.8.5. The trouble is, 3.1.1 is already in use. This will result in an error. The problem is that Whale had a broader acceptance range for Oxygen than Human. Rubygems lacks a holistic view, and simply tries to solve dependencies on a case-by-case basis, resulting in nasty traps like this. The problem is magnified greatly the more Gems there are on the system. When you consider that these Gems themselves also have dependencies, it becomes apparent that trying to solve the dependencies one at a time isn’t going to work.
The second problem may be illustrated by imagining two scenarios where a Ruby developer wishes to move software that was being developed on her workstation and share it with another team member, or perhaps run the code on a staging machine somewhere. If our developer is running the latest patch level of Ruby 1.9.3 on a Macbook, and her colleague is running Ruby 2.0.0 on Windows, ensuring she has the same versions of the Rubygems that are needed to run the application is going to be a bit of a challenge. This is a classic recipe for “it works on my machine.” How can we somehow freeze known-good versions of Rubygems and ensure that when we move to the staging server, or share with another developer, that the versions are the same?
Bundler solves this problem in two ways. First, it provides an algorithm that solves all the dependencies for a given application at once. This is a more holistic and reliable approach. Second, it provides a kind of manifest file called a Gemfile, in which the top level dependencies for an application are specified, together with constraints, and optionally sources for the code. Between these two mechanisms, both problems are resolved.
The Gemfile has a straightforward syntax:
source 'https://rubygems.org'
gem 'some_dependency'
gem 'some_other_dependency'
Bundler will use the information in this file recursively to solve dependencies for the dependencies specified in the Gemfile, and to build a graph that satisfies the dependencies of each dependency in the Gemfile. The Gemfile allows for a high degree of sophistication in specifying source and version constraints. We can specify from where to retrieve the Gems. We can specify a particular version. We can specify that for a certain Gem, we should obtain the software directly from a Git repository, down to the commit, branch, or tag. We can also specify to use files on the local machine.
Once these dependencies have been resolved, developers can guarantee that only specific versions of the dependencies are used when the application is running on another user’s computer or when deployed to a server.
Bundler is itself a Rubygem. We actually ensured it was installed in our developer role, but if we were doing things manually, we’d simply run:
$ gem install bundler
Once bundler has been installed, we create a Gemfile for the project and start to specify dependencies. Bundler provides a convenient method for generating a Gemfile:
$ bundler init
$ bundle init
Writing new Gemfile to /home/tdi/example/Gemfile
Once we’ve specified the dependencies in the Gemfile, we run:
$ bundle install
Bundler solves the dependencies and installs the Gems required. If those Gems are already on the system, it uses them, otherwise it fetches them. Once the dependencies have been solved and the Gems installed, Bundler creates a file called Gemfile.lock, which represents a snapshot of the dependency graph it built, and the versions it installed. By checking this and the Gemfile into version control, we can create a sandboxed environment for other users and be confident that they have exactly the same versions as we have on our system.
By way of exploring the functionality of Bundler, we’re going to write a silly task to say “hello” three times in color. There’s a very handy gem that is ideal for helping craft tasks, and ultimately provide them as command-line applications—it’s called Thor. To print output in color, there is another handy Gem called Colorize. The process of setting up this project with Bundler looks like this:
$ mkdir /tmp/colorsay
$ cd /tmp/colorsay
$ bundle init
Writing new Gemfile to /private/tmp/colorsay/Gemfile
$ emacs Gemfile
$ cat Gemfile
source "https://rubygems.org"
gem "thor"
gem "colorize"
$ bundle install
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Using colorize (0.5.8)
Using thor (0.18.1)
Using bundler (1.3.5)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
$ cat Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
colorize (0.5.8)
thor (0.18.1)
PLATFORMS
ruby
DEPENDENCIES
colorize
thor
Now we write our simple task:
$ cat colorsay.thor
require 'colorize'
class ColorSay < Thor
desc "hello", "Say hello in color"
def hello
puts "Hello".colorize( :red )
puts "Hello".colorize( :green )
puts "Hello".colorize( :yellow )
end
end
This is our first case of requiring an external library or extension. In this case, we’re bringing in colorize to provide strings with a .colorize method, which takes a symbol as its argument.
Symbols get a bit of bad press in the Ruby world. Well, at least to newcomers. What’s that colon thing for? Why do we need it? Why bother? You’ll see symbols cropping up in Chef recipes from time to time, so it’s worth understanding what they are and what they’re for.
Symbols are really just strings, dressed up. That’s not quite fair, but they really don’t deserve their exotic and sinister reputation. It helps if we think a little about the purpose of strings. The common use of strings is to carry some meaning or data. This might change over time, and is generally designed to carry or convey information. For example, we might set the value of the variable soup to be the string value "Leek and Potato". However, we also use strings as references or tags. We do this especially in Chef, when we’re locating attributes on the Node. In this case, the string isn’t going to change, and the extent to which it carries meaning is restricted to its role as a placeholder, or indicator—a pointer to somewhere where we’ll find information that probably will change. Within the constructs of a programming language, these two roles exhibit significantly different profiles in terms of the resources, algorithms, and functions necessary to handle them. Strings as highly mutable, information carrying devices may need much manipulation and examination. Strings as placeholders just hold a place. That’s it. In Ruby, the String class is optimized for the former use case, and the Symbol class is optimized for holding a place—for symbolizing something. Bringing this directly into the context of Chef, within a recipe, we may access the attributes of a Node in three ways:
§ node.attribute
§ node['attribute\']
§ node[:attribute]
These can be used interchangeably (a side effect of the Node object actually being an instance of Mash). There’s been strong debate in the community around which should be used as standard. As a Rubyist, I would advise you to use symbols. As a pragmatist, strings are perhaps a little less intimidating. For more information on the difference, and why it matters, see this post by Robert Sosinski.
The other characteristic of our little Thor task is that we see the class ColorSay is a subclass of class Thor. This is the meaning of the < symbol. This makes Thor the parent, the superclass of ColorSay. This is the idea of inheritance at work. Inheritance, like the word would suggest, is all about passing traits down from parent to child. Instances of the subclass pick up the methods of the superclass.
Now let’s run our task:
$ thor list
color_say
---------
thor color_say:hello # Say hello in color
$ thor color_say:hello
Hello
Hello
Hello
There’s not really much more to it than that. For more comprehensive documentation and discussion, see the Bundler website.
The only consideration to bear in mind is that when using command-line tools provided by Rubygems, such as Thor, Rake, Cucumber, RSpec, Chef—in fact, pretty much all the tools we discuss in this chapter—there’s the potential for confusion and bugs if you don’t explicitly use the version installed in your bundle. Let me give you a trivial example. Suppose you had a need to use an earlier version of Thor because some functionality in your tasks relied upon some features that had been removed in the latest release. That’s easy to achieve—we simply specify the version we need in the Gemfile:
$ cat Gemfile
source "https://rubygems.org"
gem "thor", "= 0.15.4"
gem "colorize"
$ bundle install
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Using colorize (0.5.8)
Installing thor (0.15.4)
Using bundler (1.3.5)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
If we run Thor now, we’ll get version 0.15.4, right?
$ thor version
Thor 0.18.1
What? What’s going on? The answer is, our shell gave us the path to the newest Thor. This could well have undesired effects. Bundler has a couple of ways around this. The first is bundle exec. This will run the version installed in your bundle:
$ bundle exec thor version
Thor 0.15.4
There are three problems with this approach. First, it’s easy to forget and accidentally just run Thor. This can lead to annoying wastes of time as you try to figure out why your beeping code isn’t working anymore. Second, it’s a pain to have to type bundle exec every time. Third, there’s a significant performance penalty:
$ time thor version
Thor 0.18.1
real 0m0.263s
user 0m0.185s
sys 0m0.054s
$ time bundle exec thor version
Thor 0.15.4
real 0m0.565s
user 0m0.453s
sys 0m0.082s
Running under bundle exec was nearly twice as slow. I can offer you three solutions to this problem. Pick one and work with it, for the sake of your sanity and your productivity. The first, and easiest, is just to create a shell alias:
$ alias b='bundle exec'
$ b thor version
Thor 0.15.4
Put this in your shell config and deal with the hassle of having to type two extra characters. Try not to forget, and deal with the performance.
The second is to use Bundler’s own solution—binstubs. Bundle install supports the --binstubs option, which will create a local bin directory in your application root and a little wrapper script that calls the bundled command. Now you can just type ./bin/thor. It’s fewer keystrokes thanbundle exec, and it’s as fast as thor because bundler doesn’t have to search for the binary. You can add the local bin directory to your shell path, and now you don’t even need to remember ./bin:
$ export PATH="./bin:$PATH"
$ thor --version
Thor 0.15.4
The disadvantage of this is that it’s widely accepted as a security risk to have a local bin directory on your path, especially on, for example, a shared host. For more background, see the Unix FAQ. My preferred solution is one created by a friend and former colleague, Graham Ashton—bundler-exec. Using the power of shell aliases, it replaces a given list of commands with a shell function that checks for a Gemfile in your current directory, or one of its parents, and then prefixes the command with a bundle exec if needed. I think this is the best of all worlds. You’ll never forget, it’s no more typing, there’s no security risk, and while there is a performance penalty, we’re talking in the region of milliseconds. If you’re a fan of zsh, take a look at Robby Russell’s oh my zsh GitHub page, which provides this functionality as a plug-in. To install it on a bash shell, simply run the following:
$ curl -L https://github.com/Atalanta/bundler-exec/raw/master/bundler-exec.sh > ~/.bundler-exec.sh
$ echo "[ -f ~/.bundler-exec.sh ] && source ~/.bundler-exec.sh" >> ~/.bashrc
Now everything works beautifully:
$ cd /tmp/colorsay/
$ thor version
Thor 0.15.4
$ cd ..
$ thor version
$ thor version
Thor 0.18.1
Once you’ve implemented a mitigating strategy for the bundle exec annoyance, there aren’t really any obvious disadvantages to using Bundler. It’s the standard tool in the Ruby community for managing and solving inter-gem dependencies, and for maintaining shareable sets of known-good Gems.
Bundler is an underpinning tool to pretty much everything we do in this book. Spend some time getting familiar with it, read the documentation, and take the time to set up your shell to take the pain out of the need to bundle exec.