Ruby Tutorial: Learn Dynamic Language from Scratch (2026)
Ruby was designed for programmer happiness, and after years of building web applications with it, I can confirm it delivers. Ruby on Rails made it famous, but the language itself — with its elegant syntax, pure OOP nature, and metaprogramming capabilities — is a joy to work with. Everything is an object, including numbers and classes.
Ruby taught me that expressiveness and readability are not at odds. Blocks, mixins, and duck typing let you write code that reads like natural language.
Blocks, Procs, Lambdas
Blocks are anonymous code blocks attached to method calls, delimited by do..end or {}. yield invokes the block. Procs are blocks converted to objects. Lambdas are similar to procs but check argument count and return from the lambda, not the enclosing method. Blocks are the foundation of Ruby's iteration and callback patterns.
# Block
def measure
start = Time.now
yield
Time.now - start
end
elapsed = measure { sleep(1) }
# Proc
square = Proc.new { |x| x ** 2 }
# Lambda
double = ->(x) { x * 2 }
# Block to proc
[1, 2, 3].map(&:to_s) # => ["1", "2", "3"]
Metaprogramming
Ruby lets you define methods, classes, and behavior at runtime. define_method creates methods dynamically. method_missing handles calls to undefined methods. send invokes methods by name. These tools power Rails' dynamic finders and DSLs. Use metaprogramming judiciously — it is powerful but makes code harder to trace.
class DynamicAttributes
def initialize
@attrs = {}
end
def method_missing(name, *args)
attr = name.to_s
if attr.end_with?('=')
@attrs[attr.chop.to_sym] = args.first
else
@attrs[name]
end
end
def respond_to_missing?(name, inc_priv = false)
true
end
end
obj = DynamicAttributes.new
obj.name = "Alice"
puts obj.name # "Alice"
Mixins
Ruby uses modules for multiple inheritance-like behavior. Modules provide methods that classes include via include (instance methods) or extend (class methods). The method lookup chain follows: class, included modules (last included first), superclass. prepend inserts the module before the class in the lookup chain.
module Loggable
def log(msg)
puts "[#{Time.now}] #{msg}"
end
end
module Timestamp
def log(msg)
super("#{Time.now}: #{msg}")
end
end
class Service
prepend Timestamp
include Loggable
end
svc = Service.new
svc.log("started")
Symbols and Hashes
Symbols are immutable, interned strings used primarily as identifiers. They are more memory-efficient and faster for comparison than strings. Hashes use symbols as keys in the key: value syntax (JSON-like). Keyword arguments in methods are syntactic sugar for hash parameters.
# Symbols as identifiers
:user_id
:"compound-key"
# Hash with symbol keys
config = {
host: "localhost",
port: 3000,
debug: true
}
# Access
puts config[:host]
# Merge
defaults = { port: 80, ssl: false }
merged = defaults.merge(config)
# Keyword arguments
def connect(host:, port: 80, ssl: false)
puts "Connecting to #{host}:#{port}"
end
connect(host: "example.com", ssl: true)
Enumerable
The Enumerable module provides collection methods to any class that implements each. This includes Array, Hash, and Range. map, select, reduce, any?, and all? are the workhorses. Chaining enumerable methods creates expressive data pipelines that replace explicit loops.
numbers = (1..10).to_a
evens = numbers.select(&:even?)
squares = numbers.map { |n| n ** 2 }
sum = numbers.reduce(:+)
nested = [[1, 2], [3, 4], [5, 6]]
flat = nested.flat_map { |arr| arr.map { |i| i * 2 } }
# Lazy enumeration for large data
large = (1..Float::INFINITY).lazy
.select(&:odd?)
.first(10)
puts large.inspect
Gems and Bundler
RubyGems is Ruby's package manager. gem install installs libraries. Bundler manages dependencies through a Gemfile. bundle install resolves and installs all dependencies with specific versions. The Gemfile.lock ensures reproducible environments across machines. Gemspec files define gem metadata for distribution.
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 7.1'
gem 'pg'
gem 'puma'
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
end
# After running bundle install:
# require 'bundler/setup'
require 'bundler/setup'
require 'rails'
Frequently Asked Questions
Blocks vs Procs vs Lambdas?
Blocks are syntax, not objects. Procs are objects from blocks. Lambdas check arity and return to the caller; procs return from the enclosing method.
What is duck typing?
If it walks like a duck and quacks like a duck, treat it as a duck. Ruby checks object capabilities at runtime, not type hierarchy.
attr_reader vs attr_accessor?
attr_reader creates a read-only getter. attr_accessor creates both getter and setter. attr_writer creates setter only.
When use class vs module?
Class for objects with state and behavior that you instantiate. Module for sharing behavior (mixins) or namespacing.
Originally published on Ayodhyyya. Last updated June 1, 2026.