
The Ruby languages consists of only 40 keywords which provides a lot of power with minimal syntax. This article will dive into these core language keywords, provide a reference for quick lookup, and examples for use within your own code.
ENCODING
The ENCODING
keyword answers the encoding of the current file. By default, this is generally UTF-8
. Example:
__ENCODING__ # UTF-8
This can be changed with pragmas — also known as magic comments — as follows:
# encoding: ISO-8859-1
__ENCODING__ # ISO-8859-1
💡 You can use the Pragmater gem to learn about and manage pragmas.
LINE
The LINE
keyword answers the current line of the file in which it is defined. For example, if defined on the third line of a file, you’d get a value of 3
:
# One
# Two
__LINE__ # 3
There are multiple use cases for LINE
and is commonly used in scripting and metaprogramming. Here’s an example where LINE
information is used when dynamically defining a module method so that this information can be used for debugging purposes:
Demo = Module.new do
module_eval <<~DEFINITION, __FILE__, __LINE__ + 1
def self.say(text = "A demo.") = puts text
DEFINITION
end
puts Demo.method(:say).source_location # "demo.rb", 3
Knowing the .say
method was defined on Line 3 is valuable when wanting to find the source code of the dynamic method. Otherwise, without this information, you’d get back a line number of 1 which isn’t accurate:
Demo = Module.new do
module_eval <<~DEFINITION, __FILE__
def self.say(text = "A demo.") = puts text
DEFINITION
end
puts Demo.method(:say).source_location # "demo.rb", 1
FILE
The FILE
keyword answers the current file in which it is defined. Building upon the LINE
example from above, this is how we know the Demo.say
method was defined in the demo.rb
file:
Demo = Module.new do
module_eval <<~DEFINITION, __FILE__, __LINE__ + 1
def self.say(text = "A demo.") = puts text
DEFINITION
end
puts Demo.method(:say).source_location # "demo.rb", 3
Otherwise, removing this information entirely means we don’t know where this method is defined:
Demo = Module.new do
module_eval <<~DEFINITION
def self.say(text = "A demo.") = puts text
DEFINITION
end
puts Demo.method(:say).source_location # "(eval)", 1
BEGIN
The BEGIN
keyword defines a block — which can only be used with brackets ({}
) — for code that is meant to be run before any other code within the file. This is most useful for one-line scripts:
ruby -e 'BEGIN { text = "Demo." }; puts text'
# "Demo."
Here’s an example when used in a script which isn’t as useful as the one-liner shown above:
BEGIN { puts "Start" }
# "Start"
END
The END
keyword defines a block — which can only be used with brackets ({}
) — for code that is meant to be run after any other code within the file. Building upon the BEGIN
examples from above, this is meant for one-line scripts:
ruby -e 'text = "A demo."; END { puts text }'
# "A demo."
Here’s an example when used in a script which isn’t as useful as the one-liner shown above:
END { puts "Finish" }
# "Finish"
alias
The alias
keyword allows you to define a new method which is an alias to an existing method:
class Demo
def say = puts "An example."
alias speak say
end
demo = Demo.new
demo.say # "An example."
demo.speak # "An example."
This keyword is great for renaming methods or wanting to provide a reference to the original method so you can change behavior. You can also use Module#alias_method
but alias
is preferred since the syntax is more concise.
and
The and
keyword is a short-circuit boolean with lower precedence than &&
and is best used for control flow instead of yielding a boolean. In most cases, you want to use &&
but and
improves readability in situations where you want combine an operation with another operation and don’t care if the combination of both operations answers a boolean or not. Example:
class Collector
def initialize
@collection = Set.new
end
def add(item) = collection.add(item) and self
def to_a = collection.to_a
private
attr_reader :collection
end
Collector.new.add("one").add("two").to_a
# ["one", "two"]
With the above Collector#add
method, the and
keyword allows you to chain multiple #add
messages together.
begin
The begin
keyword defines the start of an exception block. Example:
begin
require "some/file"
rescue LoadError => error
puts error.message
end
# "cannot load such file -- some/file"
The above is great for inline scripting purposes but can be avoided when used in a method:
module Requirer
def self.call path
require path
rescue LoadError => error
puts error.message
end
end
Requirer.call "some/file"
# "cannot load such file -- some/file"
Both examples are equivalent but when used in a method and/or block, use of the begin
keyword is unnecessary.
break
The break
keyword is useful when needing to exit from a block early. Example:
function = lambda do
break
"Unreachable"
end
puts function.call # nil
With the above, "Unreachable"
is never reached due to hitting break
. Lesser known is that break
can accept an argument. Example:
function = lambda do
break "Exited early."
"Unreachable"
end
puts function.call # "Exited early."
While "Unreachable"
is still never reached, you can see that the "Exited early."
string was answered instead. This is powerful when needing to exit early while providing a custom value at the same time.
Keep in mind that break
will return from an entire block (even when nested). Example:
("a".."e").each do |letter|
5.times do |number|
puts "#{letter}-#{number}"
break
end
end
# a-0
# b-0
# c-0
# d-0
# e-0
Notice only the first iteration of both blocks is executed because, once break
is hit, the entire inner and outer blocks are exited.
case
The case
keyword is used for control flow and — more recently — pattern matching which makes it a highly versatile keyword. The following is an example of control flow:
def demo object
case object
when 1..10 then puts "Is within range."
when /blue/ then puts "Matches color."
when String then puts "Is a string."
when :demo then puts "Matches symbol."
else puts "Unknown: #{object}."
end
end
demo 5 # "Is within range."
demo "The sky is blue." # "Matches color."
demo "Hello." # "Is a string."
demo :demo # "Matches symbol."
demo Object.new # "Unknown: #<Object:0x0000000112f68908>."
The following demonstrates pattern matching:
def demo(**attributes)
case attributes
in name:, **nil then puts "Name: #{name}."
in name:, label: String => label then puts "Name: #{name}, Label: #{label}."
in {} then puts "No attributes found."
else puts "Unable to match: #{attributes}."
end
end
demo name: "demo" # "Name: demo."
demo name: "demo", label: "Demo" # "Name: demo, Label: Demo."
demo # "No attributes found."
demo x: 1, y: 2 # "Unable to match: {:x=>1, :y=>2}."
In each of the above examples, there is a lot more to unpack and explore further so recommend you check out these articles for a deeper dive: function composition and pattern matching.
class
The class
keyword is one of those most common keywords which allows you to create a class with optional support for subclassing:
class Demo
end
# Inherits from Object.
Demo.ancestors # [Demo, Object, ...]
class Sub < BasicObject
end
# Inherits from BasicObject.
Sub.ancestors # Sub, BasicObject]
def
The def
keyword allows you to define a method and comes in two forms:
# Endless.
def say = "Hello"
# Standard
def say
"Hello"
end
Endless methods are great for short one-liners while standard methods can use a few more lines of code to implement behavior. Be aware of these antipatterns when using both.
defined?
The defined?
keyword is a useful — but odd keyword — in that it breaks convention by ending in a question mark and doesn’t return either true
or false
. Although, it can distinguish the use of nil
, a variable that has never been set, expressions, methods, and constants. Example:
demo = "A demo."
defined? demo # "local-variable"
defined? bogus # nil
defined? @demo # nil
defined? :puts # "expression"
defined? puts # "method"
defined? Object # "constant"
Despite what is shown above, there are more robust alternatives:
-
Use
local_variables.include? :demo
for local variable checks or — even better — anil
check in the form ofdemo && demo == "<some_value>"
. -
Use
instance_variable_defined("@demo")
for instance variable checks or — even better — anil
check in the form of@demo && @demo == "<some_value>"
. -
Use
object.respond_to? :example
for method checks. -
Use
Object.const_defined? :Example
for constant checks.
do
The do
keyword indicates the start of a block — one of the most powerful features in the language — and is best used for multi-line expressions. Example:
demo = lambda do |text|
puts "Got: #{text}."
puts "End of block."
end
demo.call "A demo."
# "Got: A demo.."
# "End of block."
In situations where blocks only require a single line, you are better off using brackets (i.e. {}
):
demo = -> text { puts "Got: #{text}." }
demo.call "A demo."
# "Got: A demo."
else
The else
keyword provides an alternate branch of control flow logic when used in a if
or case
expression (can be used with unless
but is not recommended because it forces the reader to invert the logic). Example:
if true == false
puts "True."
else
puts "False."
end
# "False."
elsif
The elsif
keyword provides multiple alternate branches of control flow logic when used in an if
expression (can be used with unless
but is not recommended). Example:
if "blue" == "red"
puts "True."
elsif "blue".match?(/blue/)
puts "A match."
else
puts "Unknown"
end
# "A match."
Use of elsif
also allows you to add multiple branches of control flow logic much like a case…when
or case…in
expression and is best used when each logic branch requires non-uniform logic versus the more uniform logic you get with case
expressions.
end
The end
keyword denotes the end of a block, class, module, method, exception handling, or control expression. Examples:
proc do
"A demo."
end
class Demo
end
def demo
end
ensure
The ensure
keyword is used in exception handling to ensure behavior always executes after an exception has been raised. Example:
begin
demo.to_s
rescue NameError => error
puts "ERROR: #{error.message}"
ensure
puts "End of line."
end
# ERROR: undefined local variable or method `demo' for main:Object
# End of line.
Of course, when adding ensure
behavior, the logic should be safe in order to not trigger another exception.
false
The false
keyword is the companion to the true
keyword and both represent boolean logic even though a boolean type doesn’t exist in Ruby. In fact, the false
keyword is an object:
false.class # FalseClass
for
The for
keyword is meant for control expressions but use is discouraged since the Enumerator#each
method is infinitely more powerful and expressive. Example:
for number in 1..10 do
puts "#{number}"
end
The better way to write the above is:
(1..10).each { |number| puts "#{number}" }
if
The if
keyword is a control flow keyword that is the opposite of the unless
keyword and is best used as a multi-line expression or as a guard clause. Example:
# Multi-line.
if "The sky is blue".match?(/blue/)
puts "Detected blue color."
else
puts "A blue color wasn't detected."
end
# "Detected blue color."
# Guard clause.
puts "Detected blue color." if "The sky is blue".match?(/blue/)
# "Detected blue color."
in
The in
keyword has, historically, been used in conjunction with for
loops but, recently, gained use in pattern matching. Here’s an example of the in
keyword when used with for
and case
:
# Loops: Not recommended.
for number in 1..10 do
puts "#{number}"
end
# Pattern Matching: Recommended.
case attributes
in name:, label: String => label then puts "Name: #{name}, Label: #{label}."
in {} then puts "No attributes found."
else puts "Unable to match: #{attributes}."
end
module
The module
keyword is used for defining namespaces or mixins (i.e. multiple inheritance). Example:
module Demo
end
💡 Ensure you are aware of antipatterns when using modules, nested modules, and use of proper namespaces in general.
In terms of mixins, modules are quite flexible in that they can be used in various ways:
# Adds class methods.
extend Demo
# Adds instance methods.
include Demo
# Prepends instance methods.
prepend Demo
# Adds refinements.
using Demo
💡 Check out the Refinements article for a deeper dive on how to leverage refinements.
next
The next
keyword allows you to skip the rest of the block logic when used in an enumerable. Here’s an example where if the current number is equal to 2
, then it is skipped entirely:
(1..3).each { |number| number == 2 ? next : puts(number) }
# 1
# 3
Lesser known is next
can accept an argument. Here’s a modified example of the above where if 2
is detected, we answer Two!
instead:
(1..3).each do |number|
next puts("Two!") if number == 2
puts number
end
# 1
# Two!
# 3
nil
The nil
keyword — also known as the Billion Dollar Mistake — is what you get when something isn’t defined. Example:
@demo # nil
It is also an object:
nil.class # NilClass
Because nil
exists in the language, you can end up in hard to debug situations and is commonly the cause of NoMethodError
exceptions. A better way to avoid nil
— or at least significantly reduce its use — is to use monads as provided via the Dry Monads gem.
not
The not
keyword is used to invert a boolean expression and is not commonly used since it has a lower precedence than !
but can be useful in situations where readability is a concern. Example:
# Lower precedence.
puts "demo" if not false # "demo"
# Higher precedence.
puts "demo" if !false # "demo"
or
The or
keyword is a short-circuit boolean with lower precedence than ||
and is best used for control flow instead of yielding a boolean. In most cases, you want to use ||
but or
improves readability in situations where you want to do one operation or another and don’t care if the combination of both operations answers a boolean or not. Example:
questionable_authorizer = lambda do |login, password|
(login == "admin" or password == "open_sesame") ? "Allowed" : "Denied"
end
puts questionable_authorizer.call("admin", "hacked") # Allowed
puts questionable_authorizer.call("hacked", "open_sesame") # Allowed
puts questionable_authorizer.call("invalid", "password") # Denied
redo
The redo
keyword is a less commonly used keyword that is best used to restart execution within a block. Here’s a powerful, but abstract, example where you redo asynchronous processes (i.e. jobs) if you detect a failure when calling a job that answers Dry Monads:
require "dry/monads"
include Dry::Monads[:result]
jobs.each do |job| redo if job.call in Failure }
Alternatively, here’s a more concrete example where random numbers are generated, checked if equal to a random number within the block, and the block is restarted only if the current number and the random number match:
numbers = Array.new(3) { rand(1..10) }
numbers.each do |number|
puts "Number: #{number}."
redo if number == rand(1..3)
end
⚠️ As powerful as redo
is, you must ensure the number of attempts is capped or you can easily end up in an infinite loop.
rescue
The rescue
keyword defines how to handle an exception within an exception block. Example:
begin
require "some/file"
rescue LoadError => error
puts error.message
end
# "cannot load such file -- some/file"
With the above, we print the error message when the LoadError
is encountered. The rescue
keyword can also accept a list of exceptions:
require "json"
def load path
JSON.load_file path
rescue TypeError, Errno::ENOENT, JSON::ParserError => error
puts error.message
end
The above is handy when needing to capture a series of exceptions and deal with the error in the same manner. This might not always be applicable but can tighten up the logic.
retry
The retry
keyword is similar in nature to the redo
keyword and is used to restart execution when an exception is encountered. Here’s an example where a URL is pinged, up to three times, before giving up:
require "http"
module Pinger
def self.call url, count: 1, max: 3
count += 1
puts "Pinging #{url}..."
HTTP.timeout(1).get url
rescue HTTP::ConnectTimeoutError
count <= max ? retry : puts("Unable to reach: #{url}.")
end
end
Pinger.call "https://abc123.com"
# Pinging https://abc123.com...
# Pinging https://abc123.com...
# Pinging https://abc123.com...
# Unable to reach: https://abc123.com.
⚠️ As powerful as retry
is, you must ensure the number of attempts is capped or you can easily end up in an infinite loop.
return
The return
keyword allows you to explicitly exit a method or exit a method early when used with a guard clause. Examples:
# Implicit.
def demo
1 + 2
end
# 3
# Explicit.
def demo
1 + 2
return "End"
end
# "End"
# Guard clause.
def demo text
return if text == "hello"
text
end
# nil
self
The self
keyword answers the object that the current method is attached to and dynamically changes relative to the current scope. Example:
puts self.inspect
# main
demo = Module.new { def self.spy = puts self.inspect }
demo.spy
# #<Module:0x00000001158717c8>
class Demo
def self.spy = puts self.inspect
end
Demo.spy
# Demo
super
The super
keyword allows you to message the current method in a superclass and is primarily used in conjunction with inheritance. Example:
Parent = Class.new { def demo = puts "I'm the parent." }
class Child < Parent
def demo
super
puts "I'm the child."
end
end
Child.new.demo
# "I'm the parent."
# "I'm the child."
💡 For more sophisticated examples of the super
keyword, see the Infusible and/or Transactable gems.
then
The then
keyword is primarily used with case
expressions although can be used with other control structures such as with the if
keyword but is not recommended since it’s extraneous. The best use of then
is with one-line conditional logic in a case
expression:
case object
when Symbol then puts "Is a symbol."
when String then puts "Is a string."
else puts "Unknown: #{object}."
end
With the above, you can see then
allows you to define branch logic on a single line for concise case
expressions. Otherwise, you can avoid then
by using multiple lines to define your case
expression (at the cost of being more verbose):
case object
when Symbol
puts "Is a symbol."
when String
puts "Is a string."
else
puts "Unknown: #{object}."
end
true
The true
keyword is the companion to the false
keyword as both represent boolean logic even though a boolean type doesn’t exist in Ruby. In fact, the true
keyword is an object:
true.class # TrueClass
undef
The undef
keyword is the opposite of the def
keyword in that it allows you to undefine a previously defined method:
def demo = "This is a demo."
demo # "This is a demo."
undef :demo
demo
# undefined local variable or method `demo' for main:Object (NameError)
You can also undefine multiple methods at once using either symbols or the actual method name. Example:
undef one, :two, three
unless
The unless
keyword is a control flow keyword that is the opposite of the if
keyword and is best used as a multi-line expression or as a guard clause. It can be used with else
but is strongly discouraged since it leads to hard to read code that should be rewritten to use the if
keyword instead. Example:
# Multi-line.
unless "blue" == "green"
puts "Blue is not green."
end
# "Blue is not green."
# Guard clause.
puts "Blue is not green." unless "blue" == "green"
# "Blue is not green."
until
The until
keyword is meant for loops which iterate until a certain condition is met. Example:
count = 1
until count > 3
puts count
count += 1
end
# 1
# 2
# 3
You can optionally add the do
keyword after the until
condition but is not recommended since it’s extraneous. You can also use the break
keyword to exit the loop early.
when
The when
keyword is used in conjunction with case
expressions for control flow by allowing you to define multiple branches of conditional logic:
case object
when Symbol then puts "Is a symbol."
when String then puts "Is a string."
else puts "Unknown: #{object}."
end
while
The while
keyword is meant for loops which iterate until a certain condition is met. Example:
count = 1
while count <= 3
puts count
count += 1
end
# 1
# 2
# 3
You can optionally add the do
keyword after the while
condition but is not recommended since it’s extraneous. You can also use the break
keyword to exit the loop early.
yield
The yield
keyword allows you to hand over control to the current method’s block. Example:
def demo
puts yield if block_given?
end
demo # nil
demo { "Hi" } # "Hi"
The yield
keyword can also accept an argument:
def demo text = "This is a demo."
puts yield(text) if block_given?
end
demo # nil
demo { |text| text.tr ".", "!" } # "This is a demo!"
demo("Hi.") { |text| text.tr ".", "!" } # "Hi!"