Skip to content

Ruby Basics

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

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.

Some folks say that if you have multiple subsequent lines that are comments, then this comprises a comment block.

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.

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.

The list of magic comments are:

Magic CommentPurpose
# frozen_string_literal: trueMakes all strings frozen by default
# encoding: UTF-8Defines the encoding for the file. Obviously, if you want
encoding other than UTF-8, supply the appropriate value.
# warn_indent: trueEnables warnings for indentation issues.
# shareable_constant_value: literalControls 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 }.

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.

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.

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.

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.

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.

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.

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:

  1. They must start with an upper case character.
  2. 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.

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

Or, you could define a constant, such as

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.

You can create instances of this new Struct class just like any other class:

You can access the attributes using dot notation and you can modify the attributes directly:

Starting from Ruby 2.5, you can provide default values for struct members:

You can add methods to a Struct by passing a block to Struct.new:

You can convert a struct to a hash using the to_h method:

Struct instances can be compared using the == method, which checks if all attributes are equal:

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 NamePurpose
all?
any? empty?, none?Does the array have any elements?
append, pushAdd elements to the end of an array
assocSearches through an array whose elements are also arrays comparing supplied element with the first element of each contained array.
at, values_atReturns element at index (similar to [index]), counting from the end if the supplied argument is negative.
bsearch, bsearch_indexFinds the first value (or index) from array which meets the given condition
clearRemoves all elements from array
collect, collect!, mapReturns a new array based upon actions supplied in provided block
combination, permutationReturns permutations of an array.
compact, compact!Removes nil objects from array.
concatConcatenates arrays. Warning – it will change the original array even though it does not end with a !
countCounts number of objects in array that meet a given condition
cycleRepeats an operation a given number of times on an array
deleteDelete all matching elements.
delete_at, delete_ifDeletes elements matching conditions.
digExtracts nested value from array.
drop, drop_whileRemove elements from array.
each, each_index, each_with_indexIterate through array.
fetchReturns element found at supplied index, with an optional default value.
fillCreates an array based upon given criteria.
find_index, indexReturns 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, prependAdd elements to an array
length, sizeReturns number of elements in array.
join, packCombine array elements into a string
keep_if, select, rejectReturns elements that meet / don’t meet supplied criteria.
min, maxReturns min and max values in array.
pop, shiftRemoves elements from an array.
reverse, reverse!Reverse array order.
rotateRotate array elements based upon supplied criteria.
sampleReturn random array objects.
shuffleRandom 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.
sumSums element values based upon supplied criteria.
take, take_whileTakes elements from array based upon supplied criteria.
transposeTransposes array.
union, |combines arrays.
uniq, uniq!Removes duplicate values from array.
Array Methods

Hash Methods

MethodPurpose
assocReturns key and value if key is found in hash.
clearClear the hash.
compact, compact!Remove nil values.
defaultSet the default value to return if a key is not found in hash.
delete, delete_ifRemove key / value pairs from hash.
digReturns values for nested hashes.
each, each_pair, each_key, each_valueIterates through hash.
empty?Is hash empty?
exceptRemoves key / value pairs from hash based upon supplied criteria.
fetch, fetch_values, select, select!, reject, reject!, replace, replace!, keep_ifReturn array or hash based upon selected criteria.
flatten, flat_mapRemove duplicates.
has_key? has_value? include?, key?True if key / value in hash.
invertTrade places with keys and values.
keyReturn key of first pair with supplied value.
keysReturns an array of keys in hash.
length, sizeNumber of pairs in hash.
merge, merge!Merge hashes.
shiftRemove and return first pair in hash.
sliceReturn hash with pairs for supplied keys.
storeAdd pair to hash. Alias for []=.
transform_keys, transform_keys! transform_values, transform_values!Transform hash based upon supplied criteria.
valuesReturns array of all values in hash.
values_atReturns values at given keys.
Hash Methods

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:

Without a dig command you can write:

With a dig command you could write:

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:

or

or

or

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 nil for this purpose. 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:

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

Example of a protected method