programming4 min read

Ruby Tutorial: Learn Dynamic Language from Scratch (2026)

Ruby Tutorial: Learn Dynamic Language from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
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.