Last updated on July 28th, 2024 at 05:56 pm
This section assumes that you have followed the basic installation instructions provided at https://www.ruby-lang.org/en/documentation/installation/ and are able to invoke the ruby command line interface irb.
- Comments
- Execution Blocks
- Everything is an Expression
- Object Assignment
- Variable Names
- Local Variables
- Instance Variables
- Class Variables
- Constants
- Ranges
- Symbols and Labels
- Structs
- Arrays, Hashes and Sets
- Digging
- Exploding things in Ruby
- Truthy and Falsey
- Type Casting
- Zero Indexed Arrays
- Ruby Operators
- nil, nil? and the nil Safety Operator
- any? and empty?
- Inspecting objects
- Access Control with Public and Private Methods
Comments
Just about every programming language in existence has the ability to add comments to the code, so why should Ruby not follow suite? There are two basic comment types – line comments and comment blocks. Basically, anything that follows a # is considered a comment. The only exception would be when the # is enclosed in a string. If the # is not at the beginning of the line, it can also be considered an In-Line comment.
# Maybe a comment to explain the method, if it is not self explanatory
def my_method
variable = 123 # this is also a comment
end
Some folks say that if you have multiple subsequent lines that are comments, then this comprises a comment block.
# Here are a few
# lines that are comments
# Is this really a block, or just multiple line comments?
My personal preference, if I am going to have something that I call a block comment would be to use =begin and =end, as in the example below.
=begin
Anything between the =begin and =end are comments and the lines don't have to start with a #
You might notice that I have an =end in the middle of the comment. Why isn't that considered the
end of the comment? Because both =begin and =end have to be at the start of the line in order for
a block comment to work.
=end
Magic Comments
Ruby also has an interesting thing known as “Magic Comments”, which are always placed at the start of the file. One example would be # frozen_string_literal: true
. This would cause any string declared after this to be considered frozen. The scope of the magic comments is only within the file they are used in.
# frozen_string_literal: true
var = 'hello'
var.frozen? # => true
The list of magic comments are:
Magic Comment | Purpose |
# frozen_string_literal: true | Makes all strings frozen by default |
# encoding: UTF-8 | Defines the encoding for the file. Obviously, if you want encoding other than UTF-8, supply the appropriate value. |
# warn_indent: true | Enables warnings for indentation issues. |
# shareable_constant_value: literal | Controls the behavior of shareable constants. Introduced in Ruby 3.1. |
Execution Blocks
Execution blocks in Ruby are a key foundation of the language. An execution block is a set of expressions which begin with an initiating statement and terminate with an end
statement. The most common initiating statements are def
(to define a method), begin
(an explicit block start), and do
(an iteration). A commonly used shorthand for do .. end
execution blocks is a pair of curly braces {
expressions }
.
def method_name
expressions
begin
expressions
end
collection.each do |record|
expressions
end
{ expressions }
end
Everything is an Expression
One of the interesting aspects of Ruby is that it doesn’t differentiate between statements and expressions. In Ruby, everything is an expression. This gives Ruby the relatively unique ability to do things like assign if
statements. In any block of expressions, a block automatically returns the value of the last executed expression in the block. Just for clarity – in the example below, if expression1
evaluates to Truthy, then expression2
is returned, otherwise expression3
is returned.
variable = if expression1
expression2
else
expression3
end
Object Assignment
At the root of just about any programming language is the ability to assign values to variables. In Ruby, this operates as one would expect. Using irb, type in the following statement:
Ruby will automatically recognize that “Hello World” is a string and assign it to the variable named message. After your statement, the interpreter will output the value assigned. You may notice that we did not have to declare the variable message anywhere and Ruby automatically created it for us.
You can interrogate a variable to determine its class name and in this example, it will say that it is a String.
Similar to Strings, if I assign an Integer to a variable, Ruby automatically determines that the value is an Integer and assigns the class appropriately.
The same occurs when assigning a floating point number.
Multiple objects can be assigned simultaneously
As well, when assigning to multiple objects, there is no problem mixing class types.
Assignments can also be made within if
statements or any expression for that matter, although this is generally frowned upon because it hinders readability and can lead to errors.
Variable Names
Variable names in Ruby must begin with either a character or an underscore (_) and may contain as many characters, digits or underscores as you would like. Theoretically, there is no limit to the length of a variable name.
Variable names in Ruby are case sensitive, so be aware that Age is not the same as age. Because Ruby is a dynamically typed language, it can be very easy to make a small typo and spend an inordinate amount of time debugging the typo. Fortunately, most development tools include automatic name completion, so it becomes less likely to make mistakes of this kind. Don’t start looking for a “strict” option to allow you to force variable declarations or type enforcement. One of Ruby’s key strengths is its Metaprogramming capability which makes strict typing and variable declarations difficult / impossible.
Naming Conventions
Ruby does not have specific naming requirements, however, like most other programming languages there are common conventions. For Ruby, that would be:
- Use snake_case for symbols, methods and variables.
- Use CamelCase for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.)
- Use SCREAMING_SNAKE_CASE for constants
I suggest that you refer to the RuboCop Style Guide for more details on what is considered best practice for Ruby.
Local Variables
If you just type in a variable name like age or message, it will become a local variable and its scope is limited to the method it is used in. As an example, let’s define two methods and in the first method we assign a value to var1 and in the second method, we attempt to use that variable.
def method1
var1 = 123
end
def method2
puts var1
end
Because the variable was assigned as a local variable, the attempt to use it in the second method will fail.
Local Variable Scope
In some languages, the scope of a variable is tightly tied to the execution block. In Java, for example, if a variable is declared within an if
statement, its scope is tied to that statement. In Ruby, this is not the case. A variable that is defined anywhere within a method is available anywhere else within that same method. Obviously, you want to ensure that the variable is created before it is referenced.
def method1
if true
var1 = 123
end
puts var1
end
Instance Variables
Remembering that everything in Ruby is an object, we can define a Class that will have variables and methods. If the variable is preceded with a ‘@’, it will be considered an Instance Variable and once it is assigned, it will be available to all methods in the instance.
class Aaa
def method1
@var1 = 123
end
def method2
puts @var1
end
end
bb = Aaa.new
bb.method1
bb.method2
u
It is important to note that, in the example above, calling bb.method2 before calling bb.method1 will fail because the instance variable @var1 would not be defined.
Class Variables
A class variable is a variable that is available to all instances of a class. In the following example, we create two instances of a class and are able to access the variable @@var1 in both instances. As with the Instance Variable, we need to ensure that the variable is defined before it is used, otherwise the method call will fail.
class Aaa
def method1
@@var1 = 123
end
def method2
puts @@var1
end
end
bb = Aaa.new
cc = Aaa.new
bb.method1
cc.method2
Please note that you can also use a $ instead of @@ to create class variables.
Although, usage of class variables is generally frowned up, they can be very handy when referencing data that is either generated or fetched, but won’t change for the duration of the program execution.
For example, I have a customer that has defined a SQL table which contains all of the Cantons in Switzerland. As changes to this data would be exceptionally rare, it would have probably been better to make this data constant, however that isn’t the code that I inherited and the customer isn’t interested in paying for refactoring. As the Canton is referenced quite frequently in code execution, it would be best to have this data in the heap instead of pulling it from the SQL cache every time it is needed.
This is a situation where a class variable can come in handy. A commonly used pattern would be to define a method, and then reference the class variable within that method, assigning the data the first time it is used.
def cantons
@@cantons ||= Canton.all.pluck(:id, :name).to_h
end
The assignment statement @@cantons ||= Canton.all.pluck(:id, :name).to_h
first evaluates @@cantons. If @@cantons is truthy, it returns @@cantons. The first time this method is executed, @@cantons will be falsey, so the statement Canton.all.pluck(:id, :name).to_h
is executed, which fetches a list of the Ids and Names of all of the cantons and converts it to a hash. Once the class variable is populated, subsequent instances will evaluate to truthy and simply return the hash.
Constants
In addition to variables, Ruby can define constants. Constants only have a few simple rules:
- They must start with an upper case character.
- They must be defined outside of a method.
It is common programming practice to have constants all upper case using an underline (_) to separate words, such as MAX_VALUE. This is sometimes referred to as SCREAMING_SNAKE_CASE.
Once a class constant is defined, it is automatically considered public and can be referenced by using ::
, such as ClassName::CONSTANT_NAME. As of Ruby 1.9.3, you can also define class private constants using the private_constant
method.
SECRET = "Something only I should know"
private_constant :SECRET
Ranges
One of the more unique aspects of Ruby is that it implements the concept of ranges. In practice this is two numbers separated by two dots. For example, if you want to refer to the first three elements of an array, you could write this as array_name[0..2]
. You can also use constants and/or variables as the end points, such as
my_values = my_array[first_element..last_element]
Or, you could define a constant, such as
MYRANGE = 0..3
my_values = my_array[MYRANGE]
If you supply an ending index that is greater than the actual length of the object it is applied to, then the return value will be everything from the start to the last element of the object.
If the start of the range is a negative number, the selection will start at the end minus the start of the range. If the end of the range is negative, the selection will stop at the end of the object minus the end value. The following example shows this behavior in action.
You can also use ...
to create a range that excludes the endpoint. For example the range 2...20
would evaluate to 2..19
.
An interesting aspect of ranges is that it introduces the cover?
method. Although it is similar to the include?
method, it will not always produce the same result as the include?
method and can in many cases be more efficient. The include? method will essentially iterate, if possible,over the collection in question, creating new objects for each iteration.
The cover?
method, will only check and compare against the range endpoints. A classic example would be the range “a”…”z”. The method include?
will return true for “m”, but false for “mm”. The cover?
method, however, will return true because both “m” and “mm” are greater than “a” and less than “z”.
Symbols and Labels
Symbols and Labels are used constantly throughout Ruby and sometimes the difference between a Symbol and a Label can be confusing. A symbol can be thought of as nothing more than a variable preceded by a colon (:) and a label can be thought of as a variable suffixed by a colon.
Ruby guarantees that no matter where it appears in your program, a given symbol or label will have the same value. Symbols and labels break Ruby’s rule of everything being an object, because symbols and labels are not pointers to values, they are values themselves
There is also a shorthand mechanism for creating symbol arrays in Ruby.
You can also create symbols programmatically by using the to_sym
method on any string object.
As with many native Ruby objects, a symbol can be converted to a string with the to_s
method.
Labels are most frequently used as indexes in to hashes, which will be explained below.
Structs
In Ruby, a Struct
is a convenient way to bundle a number of attributes together using accessor methods without having to write an explicit class. It provides a quick way to create simple data structures. You create a new Struct
by calling Struct.new
and passing a list of attribute names. This creates a new class with the specified attributes.
Person = Struct.new(:name, :age, :gender)
You can create instances of this new Struct
class just like any other class:
person1 = Person.new("Alice", 30, "Female")
You can access the attributes using dot notation and you can modify the attributes directly:
puts person1.name # => "Alice"
puts person1.age # => 30
puts person1.gender # => "Female"
person1.age = 31
puts person1.age # => 31
Starting from Ruby 2.5, you can provide default values for struct members:
Person = Struct.new(:name, :age, :gender) do
def initialize(name, age = 0, gender = "Unknown")
super(name, age, gender)
end
end
person2 = Person.new("Bob")
puts person2.age # => 0
puts person2.gender # => "Unknown"
You can add methods to a Struct
by passing a block to Struct.new
:
Person = Struct.new(:name, :age, :gender) do
def birthday
self.age += 1
end
end
person3 = Person.new("Charlie", 25, "Male")
person3.birthday
puts person3.age # => 26
You can convert a struct to a hash using the to_h
method:
person4 = Person.new("Daisy", 40, "Female")
puts person4.to_h # => {:name=>"Daisy", :age=>40, :gender=>"Female"}
Struct instances can be compared using the ==
method, which checks if all attributes are equal:
person5 = Person.new("Eve", 28, "Female")
person6 = Person.new("Eve", 28, "Female")
puts person5 == person6 # => true
Arrays, Hashes and Sets
As with almost any programming language, Ruby has the concept of arrays. An array can be thought of as nothing more complex than a list of objects that can be accessed by an index. Arrays in Ruby are zero indexed.
Because Ruby treats everything as an object, you can mix object types in an array.
Pushing elements into an array can be accomplished be either using the <<
operator or the .push
method.
Not surprisingly, arrays also have a pop
method which returns the last element of the array and removes it from the array. The pop
method can also accept a numeric argument which tells it how many elements to pop from the array.
You might notice that both the push
and pop
methods violate the convention of ending the method name with a ! whenever a method modifies the object using it.
You can also use the shift
and unshift
methods to remove or add array elements, respectively.
Because Ruby likes to have many ways of accomplishing the same task, there is also the insert
method, which will insert an element at the target position (remembering that arrays in Ruby are 0 indexed!). As a short intellectual exercise -> how could you rewrite push
and unshift
as insert
?
A hash can be considered an array that is indexed by symbols instead of integers. Also note that a hash is sometimes referred to as a dictionary. As with arrays, you are allowed to mix object types in the same hash.
What may be confusing for some is that when defining the hash, a label is used as the index and when accessing the hash a symbol is used as the index.
You may notice that when defined, the array is bounded by square brackets ([]) and the hash is bounded by curly brackets ({}). However, when accessed, both the array and hash use square brackets.
A typical way of initializing an array would be to simply assign values to it. There is also a shortcut that you can use – %w
.
There are also many helper methods to access array and hash elements, such as first, second, third, last, etc.
If you supply a numeric parameter, that is used to determine how many elements to retrieve.
Array methods
Method Name | Purpose |
all? | |
any? empty?, none? | Does the array have any elements? |
append, push | Add elements to the end of an array |
assoc | Searches through an array whose elements are also arrays comparing supplied element with the first element of each contained array. |
at, values_at | Returns element at index (similar to [index]), counting from the end if the supplied argument is negative. |
bsearch, bsearch_index | Finds the first value (or index) from array which meets the given condition |
clear | Removes all elements from array |
collect, collect!, map | Returns a new array based upon actions supplied in provided block |
combination, permutation | Returns permutations of an array. |
compact, compact! | Removes nil objects from array. |
concat | Concatenates arrays. Warning – it will change the original array even though it does not end with a ! |
count | Counts number of objects in array that meet a given condition |
cycle | Repeats an operation a given number of times on an array |
delete | Delete all matching elements. |
delete_at, delete_if | Deletes elements matching conditions. |
dig | Extracts nested value from array. |
drop, drop_while | Remove elements from array. |
each, each_index, each_with_index | Iterate through array. |
fetch | Returns element found at supplied index, with an optional default value. |
fill | Creates an array based upon given criteria. |
find_index, index | Returns index of object matching given criteria. |
flatten, flatten! | Flattens a multi-dimensional array into a one dimensional array. |
include? | Returns true if the array includes supplied element, false otherwise |
insert, push, unshift, prepend | Add elements to an array |
length, size | Returns number of elements in array. |
join, pack | Combine array elements into a string |
keep_if, select, reject | Returns elements that meet / don’t meet supplied criteria. |
min, max | Returns min and max values in array. |
pop, shift | Removes elements from an array. |
reverse, reverse! | Reverse array order. |
rotate | Rotate array elements based upon supplied criteria. |
sample | Return random array objects. |
shuffle | Random shuffle of array. |
slice, slice! | Returns elements of array based upon supplied criteria. |
sort, sort!, sort_by, sort_by! | Sort array based upon supplied criteria. |
sum | Sums element values based upon supplied criteria. |
take, take_while | Takes elements from array based upon supplied criteria. |
transpose | Transposes array. |
union, | | combines arrays. |
uniq, uniq! | Removes duplicate values from array. |
Hash Methods
Method | Purpose |
assoc | Returns key and value if key is found in hash. |
clear | Clear the hash. |
compact, compact! | Remove nil values. |
default | Set the default value to return if a key is not found in hash. |
delete, delete_if | Remove key / value pairs from hash. |
dig | Returns values for nested hashes. |
each, each_pair, each_key, each_value | Iterates through hash. |
empty? | Is hash empty? |
except | Removes key / value pairs from hash based upon supplied criteria. |
fetch, fetch_values, select, select!, reject, reject!, replace, replace!, keep_if | Return array or hash based upon selected criteria. |
flatten, flat_map | Remove duplicates. |
has_key? has_value? include?, key? | True if key / value in hash. |
invert | Trade places with keys and values. |
key | Return key of first pair with supplied value. |
keys | Returns an array of keys in hash. |
length, size | Number of pairs in hash. |
merge, merge! | Merge hashes. |
shift | Remove and return first pair in hash. |
slice | Return hash with pairs for supplied keys. |
store | Add pair to hash. Alias for []=. |
transform_keys, transform_keys! transform_values, transform_values! | Transform hash based upon supplied criteria. |
values | Returns array of all values in hash. |
values_at | Returns values at given keys. |
Sets
Sets
can be thought of as arrays that automatically ensure that there are no duplicate values. Creation of a set
is accomplished by explicitly creating the Set object. There is no [] or {} alias, as with arrays and hashes, although specifying set
values is the same as with arrays.
As with arrays and hashes, contents of a set can be any object and don’t have to be consistent within the set. Adding objects to a set is similar to pushing objects onto an array. However, notice that if an element is already in a set, it won’t be added a second time.
If all you’re doing is adding items to an array & searching to see if an item is included or not, a Set can significantly improve your performance.
Digging
A very useful tool with arrays, hashes, structs and CSV files is the dig
method. Consider the following data:
item = {
id: "0001",
type: "donut",
name: "Cake",
ppu: 0.55,
batters: {
batter: [
{id: "1001", type: "Regular"},
{id: "1002", type: "Chocolate"},
{id: "1003", type: "Blueberry"},
{id: "1004", type: "Devil's Food"}
]
},
topping: [
{id: "5001", type: "None"},
{id: "5002", type: "Glazed"},
{id: "5005", type: "Sugar"},
{id: "5007", type: "Powdered Sugar"},
{id: "5006", type: "Chocolate with Sprinkles"},
{id: "5003", type: "Chocolate"},
{id: "5004", type: "Maple"}
]
}
Without a dig command you can write:
item[:batters][:batter][1][:type] # => "Chocolate"
item[:batters][:BATTER][1][:type] # => NoMethodError (undefined method `[]' for nil:NilClass)):
With a dig command you could write:
item.dig(:batters, :batter, 1, :type) # => "Chocolate"
item.dig(:batters, :BATTER, 1, :type) # => nil
Why is dig better? It has fewer things that can go wrong, it is easier to read and, most importantly, it won’t throw an error if you attempt to access a non-existent element.
Ruby also has the ability to dig within CSV files. See the Working with Files section for more details.
Exploding things in Ruby
While the *
in Ruby is most often used as a multiplication operator, it is also an explosion operator. Rather than explain it, let’s see the operator in action in an excellent StackOverflow answer.
Truthy and Falsey
Just about every programming language in existence supports the concept of boolean values – true and false. Some, such as Ruby, take things a bit further and have the concept of “Truthy” and “Falsey” which extends the concept of strict boolean true and false. This means that there are non-boolean values that will evaluate to true or false. It is worth nothing that Ruby will correctly evaluate the literals true
and TRUE
, as well as false
and FALSE
. It is one of the few situations in Ruby where case doesn’t matter.
The statement if true
will obviously evaluate to true, just as the statement if false
will evaluate to false. Any variable or expression that evaluates to the boolean value of true or false will also operate just as expected.
However, let’s have a look at the value nil
. In the world of Truthy and Falsey, nil evaluates to a false. So, if nil
evaluates to false, as does any variable or expression that evaluates to nil
. Any other value, even 0 and “””, will evaluate to true.
This gives Ruby some interesting flexibility, once you get the hang of it. For example, instead of using an if statement or ternary statement, such as:
if variable
new_variable = variable
else
new_variable = something_else
end
or
new_variable = if variable
variable
else
something_else
end
or
new_variable = variable ? variable : something_else
or
new_variable = variable || something_else
Once you understand and get used to how things work, it makes the code much cleaner and more readable.
Type Casting
Ruby will not automatically type cast like some languages do. For example, in JavaScript you can have an assignment of:
where a number is automatically converted to a string for a concatenation. Trying to do the same in Ruby will result in the following error:
To Type Cast in Ruby, you need to use an explicit conversion. Many Ruby classes have helper methods such as to_s
(convert to string), to_i
(convert to integer), to_h
(convert to hash) or to_a
(convert to array).
When you are defining your own classes it is very common to define a to_s
method. If you do not do this and someone tries to convert your class to a string with the to_s
method, they will get a string with the class name followed by a hash of the object’s address.
Zero Indexed Arrays
All arrays in Ruby are zero indexed. What this means is that the first element of an array has the index of 0, not 1.
Ruby Operators
Rather than replicate work that is already done, I will provide a link to a well done explanation of Ruby operators at tutorialpoint.
nil, nil? and the nil Safety Operator
Any object oriented language has to have a concept of an unassigned pointer. Ruby uses
for this purpose. nil
nil
can be assigned to any variable and may be returned from any method.
To check and see if a variable has the value of nil
assigned to it, a simple comparison of varName == nil
will suffice. Ruby has a nice helper method nil?
which can be used to abbreviate the nil check.
Because it can be very cumbersome to manually check for nil values within a chain of linked references, Ruby has what is referred to as a nil safety operator, &
that can be applied. If the object is nil, the statement evaluates to nil
instead of throwing a nil pointer error. The nil safety operator is simply applied at the end of the object’s name.
Without the nil safety operator, you will need to either check the reference before using it or catch the resulting NoMethodError.
any? and empty?
Very often you will want to check to see if an array or hash has any elements. You can do this the hard way with array_name.count > 0
or use the helper method any?
, as in array_name.any?
The inverse of any?
is empty?
, which can be applied in exactly the same manner – array_name.empty?
This is another example of Ruby having multiple methods that perform inverse operations of each other.
Inspecting objects
While debugging Ruby code, one of the most commonly used tools is the inspect
method. All objects respond to the inspect
method and it will normally deliver a description of what the object contains.
Access Control with Public and Private Methods
By default, all methods in a model or controller are publicly accessible, except for the initialize
method in a class. There are situations, however, where it is good to make the method available only to other methods in the class without exposing it outside of the class. This is easily accomplished by using the protected and private specifiers, as shown in the following example:
class ExampleClass
def first_method # This method is public, by default
#...
end
protected # This statement indicates that all following methods will be protected until changed to public or private
def protected_method
#...
end
private # This statement indicates that all following methods will be private until changed to public or protected
def private_method
#...
end
end
The difference between protected and private is that a protected methods can be called by other methods within the class, where private methods can only be called from within an object.
Example of a private method
class Example
def compare_with(other)
other.private_method == private_method
end
private
def private_method
"A private value"
end
end
ex1 = Example.new
ex2 = Example.new
ex1.compare_with(ex2) # Raises NoMethodError
Example of a protected method
class Example
def compare_with(other)
other.protected_method == protected_method
end
protected
def protected_method
"A protected value"
end
end
ex1 = Example.new
ex2 = Example.new
ex1.compare_with(ex2) # Works
ex1.protected_method # Raises NoMethodError