Skip to content

Ruby Programming

107 companion flashcards · AI-assisted study content · Open the deck →

This deck walks you through some of the core building blocks of the Ruby programming language, from everyday data structures like arrays and hashes to the more distinctive features that set Ruby apart, such as blocks, procs, and lambdas. You will also explore the object-oriented side of the language, including how classes are defined, how instance and class variables differ, and how modules and mixins let you organize and share behavior across your code. Together, these cards cover the kinds of concepts that come up constantly when reading or writing idiomatic Ruby.

It is a good fit if you are relatively new to Ruby and want to firm up your understanding of its fundamentals, or if you have some experience with another language and are picking up Ruby for the first time. The questions about procs versus lambdas, and modules versus mixins, are especially useful if you are preparing for a technical interview or a coding assessment, since these distinctions are commonly tested.

Because many of these topics come in pairs that are easy to mix up, try to think about the relationships between concepts as you study, for example why you might reach for a lambda over a proc, or what a mixin really is under the hood. Spacing your review sessions over several days, rather than cramming everything in one sitting, tends to help these distinctions stick. If you can, follow up each card by writing a short snippet of Ruby on your own machine to test the idea in practice.

Core Data Structures

Ruby provides several built-in data structures for organizing information in programs. A Symbol is a lightweight, immutable identifier written with a leading colon, such as :status or :id. Because each symbol is stored in memory only once, symbols are more efficient than strings when used as keys or repeated identifiers, and they are commonly seen as hash keys or method names passed around a program.

An Array is an ordered, indexed collection of objects. Arrays can be created with square brackets, such as nums = [1, 2, 3], or with the percent-w shortcut for word arrays, like words = %w[hello world]. The Array.new constructor accepts a size and a default value, so Array.new(5, 0) produces an array of five zeros. Arrays preserve insertion order and can hold mixed object types.

A Hash stores data as key-value pairs and is created with curly braces or Hash.new. The shorthand h = { name: "Alice", age: 30 } uses symbols as keys; the colon before each key turns it into :name and :age behind the scenes. Hash.new(0) sets a default value of zero for missing keys, and values are accessed with square brackets, for instance h[:name]. A Range represents an interval of values using either two dots for an inclusive range like (1..5) or three dots for an exclusive range like (1...5). Ranges work with numbers, strings, and any object that implements the spaceship operator.

Blocks, Procs, and Lambdas

Ruby treats blocks of code as first-class values that can be passed to methods. A block is an anonymous chunk of code written either with curly braces or with the do and end keywords. By convention, curly braces are used for single-line blocks while do...end is preferred for multi-line blocks, partly because curly braces bind more tightly to the surrounding expression than do...end does.

When you want to store a block in a variable or pass it around like any other object, you wrap it in a Proc. A Proc can be created with Proc.new or with the arrow syntax, and it is invoked with .call or similar shorthand. A Lambda is a special kind of Proc that behaves more like a method: it strictly checks the number of arguments and returns control to the calling method rather than exiting it. As a result, a Lambda raises ArgumentError when given the wrong arity, while a Proc silently assigns nil to missing arguments. Returning from a Lambda returns to the caller, whereas returning from a Proc exits the enclosing method, which can cause surprising flow control.

Methods can accept blocks implicitly using the yield keyword, which transfers execution to whatever block was passed in. To avoid a LocalJumpError when no block is provided, methods often guard yield with block_given?. The ampersand operator provides a bridge between Procs and blocks: writing &block in a parameter list captures an incoming block as a Proc object, while passing &proc at a call site converts a Proc back into a block. This makes it possible to defer, store, or transform the executable chunks of code that flow through a Ruby program.

Classes, Inheritance, and Modules

Ruby is fundamentally object-oriented, and classes are defined with the class keyword followed by the class name and a body of method definitions. When an object is created with ClassName.new, the special initialize method is automatically called as the constructor, allowing the new instance to set up its own state. State is stored in instance variables prefixed with a single at sign, such as @name, which live for the lifetime of the object and are accessible from any instance method.

Class variables, prefixed with two at signs like @@count, are shared across all instances of a class and any subclasses that inherit from it. Because subclass changes affect the same variable, they should be used with caution. Ruby supports single inheritance through the less-than symbol: writing class Dog < Animal declares that Dog inherits the behavior of Animal, and any class implicitly inherits from Object unless it specifies another parent.

Modules are Ruby's answer to the lack of multiple inheritance. A module is a container for methods and constants that cannot be instantiated directly. It serves two roles: as a namespace that groups related classes together, and as a mixin that adds behavior to a class through include or extend. Including a module makes its methods available as instance methods of the class, while extending a module adds those methods as class methods. To eliminate boilerplate getter and setter definitions, Ruby offers the attr_reader, attr_writer, and attr_accessor class macros, which generate the corresponding methods for the named symbols. The Comparable module is another built-in mixin: by defining the spaceship operator and including Comparable, a class gains all the standard comparison operators for free.

Iterators and Functional Collections

Iteration is at the heart of idiomatic Ruby, and most collection methods are powered by the Enumerable module. The each method is the workhorse of iteration: it walks through a collection and yields each element to a block, returning the original collection unchanged. Methods like map, select, and reduce are layered on top of each, so any class that defines its own each method and includes Enumerable gains a powerful suite of operations.

The map method, also known by its alias collect, applies a block to every element and collects the results into a new array. For example, doubling a list produces [2, 4, 6] from the original [1, 2, 3]. The select method returns a new array containing only the elements for which the block returns a truthy value, making it ideal for filtering. The reduce method, aliased as inject, folds a collection into a single value by repeatedly applying a binary operation, starting from an optional initial accumulator. Combining these methods allows Ruby code to express transformations on data clearly and concisely without explicit loops.

Because Enumerable provides many additional methods such as sort, min, max, and find, defining an each method on a custom class and including Enumerable is the standard way to make that class behave like a first-class collection. This design reflects Ruby's philosophy: rather than building a large inheritance hierarchy of collection types, the language provides a small set of core iteration primitives and composes them into a rich set of higher-level operations.

Exception Handling and File I/O

Ruby programs use a structured exception handling mechanism built around the begin, rescue, and ensure keywords. Risky code is wrapped in a begin block, and any raised exception that matches a rescue clause can be caught and handled, often bound to a variable such as e for inspection. The most common pattern catches StandardError, the default superclass for application-level errors, and either logs the message, re-raises the exception, or attempts recovery.

The ensure clause, when present, runs whether or not an exception was raised, making it ideal for cleanup tasks like closing files, releasing locks, or restoring state. This guarantees that resources do not leak even when something goes wrong mid-operation. Programmers can also define custom exception classes simply by subclassing StandardError and then raise them with the raise keyword, passing an instance of the custom class along with a descriptive message. Such custom errors are then caught with rescue MyError => e in the same way built-in exceptions are.

File input and output in Ruby is straightforward through the File class. The simplest way to read a file is to call File.read with a path, which returns the entire contents as a string. For line-by-line processing, File.foreach iterates through the file without loading it all at once, which is more memory-efficient for large files. Using File.open with a block automatically closes the file when the block exits, so the idiom of passing a block to File.open is preferred. Writing to a file uses File.open with the "w" mode to overwrite or "a" mode to append, with methods like puts writing strings followed by a newline.

Strings, Patterns, and Operators

Strings in Ruby can be constructed with single or double quotes, but only double-quoted strings support interpolation, where the result of any Ruby expression can be embedded with the #{} syntax. This allows you to write puts "Hello, #{name}!" instead of concatenating pieces together. For printing output, the puts method writes a string followed by a newline, print omits the newline, and p outputs the inspected representation of an object, which is invaluable for debugging complex data structures.

Regular expressions are first-class objects in Ruby, written between forward slashes. The match method applies a pattern to a string and returns a MatchData object, while the =~ operator returns the index of the first match or nil if no match is found. The gsub method replaces every occurrence of a pattern with a given replacement string or with the result of a block, making it easy to transform text programmatically, such as masking vowels or capitalizing words.

Ruby offers several convenience operators for everyday programming. The ternary operator provides a compact one-line conditional of the form condition ? true_value : false_value. The splat operator gathers extra positional arguments into an array or, when used at a call site, expands an array into individual arguments; the double splat does the same for keyword arguments. The safe navigation operator invokes a method only when the receiver is not nil, returning nil otherwise, which prevents the dreaded NoMethodError on undefined nil receivers. For multi-branch conditions, case/when offers readable pattern matching that uses the case-equality operator, so it works naturally with ranges, classes, and regular expressions as well as literal values.

Metaprogramming and the Ruby Ecosystem

One of Ruby's most distinctive features is metaprogramming, the ability to write code that writes or modifies code at runtime. The define_method method dynamically defines an instance method on a class, allowing patterns like iterating over a list of command names and generating a method for each one. The method_missing hook intercepts calls to undefined methods, so an object can respond to arbitrary messages by name and arguments; pairing it with respond_to_missing? keeps reflection honest. The send method invokes any method by name, even private ones, which is powerful but should be used carefully; public_send offers a safer alternative that respects visibility.

When a flexible data structure is needed, OpenStruct from the standard ostruct library allows objects to be created with arbitrary attributes defined at runtime, accessed and assigned with normal dot notation. To prevent accidental mutation, Ruby provides the freeze method, which makes an object immutable and raises a FrozenError on any attempt to modify it. A common modern practice is to add a comment at the top of a source file declaring frozen_string_literal: true, which freezes all string literals in that file for performance and safety. Ruby also distinguishes three flavors of equality: == checks value equality and can be overridden, eql? additionally requires the same type and is used by Hash to compare keys, and equal? checks whether two references point to the very same object.

The wider Ruby ecosystem revolves around RubyGems and Bundler. A gem is a packaged Ruby library or application distributed through RubyGems, installable with a simple install command. For real projects, dependencies are listed in a Gemfile, and Bundler resolves and installs the correct versions, recording them in a Gemfile.lock file. The bundle install command installs dependencies and bundle exec runs commands within the bundle context so that the right gem versions are always used. Together, these tools form a mature ecosystem that supports everything from small scripts to large web applications, and they are part of what makes Ruby both expressive and practical for production use.

Frequently asked questions

What is a Symbol in Ruby?

A Symbol is a lightweight, immutable identifier prefixed with a colon, e.g. :name. Symbols are stored in memory only once, making them more efficient than strings for keys and identifiers.
Example: :status, :id

What are instance variables in Ruby?

Instance variables start with @ and belong to a specific object instance. They persist for the lifetime of the object.
Example: @name = "Ruby"

How do you handle exceptions in Ruby?

Use begin...rescue...end:
begin
  risky_operation
rescue StandardError => e
  puts e.message
ensure
  cleanup
end

The ensure block always executes.

What does method_missing do in Ruby?

method_missing is called when an object receives a message it cannot handle. You can override it to intercept undefined method calls.
Example:
def method_missing(name, *args)
  puts "Called: #{name}"
end

Always define respond_to_missing? alongside it.

What is the Comparable module in Ruby?

The Comparable module provides comparison operators (<, <=, ==, >=, >) when you define the <=> (spaceship) method.
Example:
class Box
  include Comparable
  def <=>(other)
    self.size <=> other.size
  end
end

What are local variables in Ruby?

A local variable starts with a lowercase letter or underscore and is scoped to the block, method, or class it is defined in. It is not accessible outside that scope: name = "Ruby".

What does the to_s method do?

to_s converts an object to its string representation, used by puts and interpolation. The default Object#to_s shows the class name and object id; you can override it in your classes.

What is the modulo operator?

% returns the remainder of division: 10 % 3 # => 1. The sign follows the divisor in Ruby. It is useful for checking even/odd and wrapping values.

What is the difference between sort and sort_by?

sort uses the <=> operator of the elements directly. sort_by computes a sort key for each element (e.g., a method or attribute), which is usually faster for complex keys.

What is the difference between a lambda return and a proc return?

Inside a lambda, return exits only the lambda. Inside a proc, return exits the enclosing method where the proc was defined, which can cause a LocalJumpError if that method already returned.

Drill this topic

107 flashcards on Ruby Programming — free, no signup needed to start.

Study Ruby Programming flashcards

LearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.