Ruby Introduction

Ruby is a dynamic, object-oriented language designed for programmer happiness. Created by Yukihiro Matsumoto ("Matz") in 1995, it's known for its elegant syntax and Rails framework.

Why Ruby?

  • Elegant — Designed to be enjoyable to write and read
  • Object-oriented — Everything is an object (even numbers and booleans)
  • Expressive — Closures, blocks, and metaprogramming
  • Rails — The most productive web framework for startups
  • Convention over configuration — sensible defaults
  • Community — Friendly, welcoming, and passionate

Hello World

Ruby
# Your first Ruby program
puts "Hello, World!"

# Everything is an object!
puts 42.class        # Integer
puts 3.14.class      # Float
puts "hello".class   # String
puts true.class      # TrueClass

# String methods (everything is an object)
puts "hello".upcase     # "HELLO"
puts "hello".reverse    # "olleh"
puts "hello".length     # 5
puts "hello".include?("ell")  # true
puts "hello world".split  # ["hello", "world"]

Variables and Types

Ruby
# Variables (snake_case by convention)
name = "Alice"        # String
age = 28              # Integer
pi = 3.14159          # Float
active = true         # Boolean
nothing = nil         # Ruby's null

# Constants (start with uppercase — Ruby warns if you reassign)
MAX_RETRIES = 3
PI = 3.14159

# Type hints (optional, Ruby 2.7+)
def greet(name : String) : String
  "Hello, #{name}!"
end

# String interpolation (double quotes only)
puts "Hello, #{name}!"
puts "2 + 2 = #{2 + 2}"
puts 'No interpolation here: #{name}'  # Single quotes are literal

Control Flow

Ruby
# if/elsif/else/end
score = 85
if score >= 90
  puts "A"
elsif score >= 80
  puts "B"
elsif score >= 70
  puts "C"
else
  puts "F"
end

# Ternary operator
status = age >= 18 ? "adult" : "minor"

# One-liner if (modifier form)
puts "Pass" if score >= 60
puts "Fail" unless score >= 60  # Unless = if not

# case/when (powerful pattern matching)
grade = case score
        when 90..100 then "A"
        when 80..89  then "B"
        when 70..79  then "C"
        when 60..69  then "D"
        else "F"
        end

# Loops
5.times { |i| puts i }              # 0, 1, 2, 3, 4
1.upto(5) { |i| puts i }           # 1, 2, 3, 4, 5
10.downto(1) { |i| puts i }        # 10, 9, ..., 1
(1..5).each { |i| puts i }         # 1, 2, 3, 4, 5
[1, 2, 3].each { |x| puts x * 2 }  # 2, 4, 6

# While/until
i = 0
while i < 5
  puts i
  i += 1
end

until i == 0
  puts i
  i -= 1
end

Blocks, Procs, and Lambdas

Ruby
# Blocks (most common)
5.times do |i|
  puts i
end

[1, 2, 3].each do |num|
  puts num * 2
end

# Shorthand block (one-liner)
[1, 2, 3].map { |n| n * 2 }  # [2, 4, 6]

# Procs (stored blocks)
square = Proc.new { |x| x ** 2 }
puts square.call(5)  # 25

# Lambdas (strict procs)
double = lambda { |x| x * 2 }
# Or shorthand
double = -> (x) { x * 2 }
puts double.call(5)  # 10

# Yield (call block from method)
def measure
  start = Time.now
  yield                    # Execute the block
  elapsed = Time.now - start
  puts "Completed in #{elapsed}s"
end

measure do
  sleep(1)
end
# "Completed in 1.001s"

Collections

Ruby
# Arrays
fruits = ["apple", "banana", "cherry"]
fruits.push("date")         # Add to end
fruits.pop                  # Remove from end
fruits[0]                   # "apple"
fruits[-1]                  # "cherry" (last element)
fruits.first                # "apple"
fruits.last                 # "cherry"
fruits.length               # 4
fruits.include?("apple")    # true

# Array methods
[1, 2, 3, 4, 5].map { |n| n * 2 }     # [2, 4, 6, 8, 10]
[1, 2, 3, 4, 5].select { |n| n > 3 }  # [4, 5]
[1, 2, 3, 4, 5].reject { |n| n > 3 }  # [1, 2, 3]
[1, 2, 3, 4, 5].reduce(0, :+)          # 15
[3, 1, 4, 1, 5].sort                   # [1, 1, 3, 4, 5]
[3, 1, 4, 1, 5].uniq                   # [3, 1, 4, 5]

# Hashes
person = { name: "Alice", age: 28, email: "alice@test.com" }
# Or string keys
person = { "name" => "Alice", "age" => 28 }

puts person[:name]          # "Alice"
puts person.fetch(:email)   # "alice@test.com"
person[:phone] = "555-1234" # Add
person.delete(:email)       # Remove

# Iterating hashes
person.each do |key, value|
  puts "#{key}: #{value}"
end

# Ranges
(1..5).to_a     # [1, 2, 3, 4, 5]
(1...5).to_a    # [1, 2, 3, 4] (exclusive end)
("a".."e").to_a # ["a", "b", "c", "d", "e"]

Classes

Ruby
class User
  attr_accessor :name, :email    # Create getters and setters
  attr_reader :created_at        # Read-only
  
  def initialize(name, email)
    @name = name       # Instance variables start with @
    @email = email
    @created_at = Time.now
  end
  
  def greet
    "Hello, I'm #{@name}!"
  end
  
  def to_s
    "#{name} (#{email})"
  end
end

user = User.new("Alice", "alice@example.com")
puts user.greet       # "Hello, I'm Alice!"
puts user.name        # "Alice"
user.email = "new@test.com"

# Inheritance
class Admin < User
  attr_accessor :permissions
  
  def initialize(name, email, permissions = [])
    super(name, email)    # Call parent constructor
    @permissions = permissions
  end
  
  def can?(permission)
    permissions.include?(permission)
  end
end

admin = Admin.new("Bob", "bob@test.com", ["edit", "delete"])
puts admin.can?("edit")    # true
puts admin.greet           # "Hello, I'm Bob!" (inherited)

Knowledge Check

4 questions — test your understanding

1

What does `puts` do in Ruby?

2

What is the difference between a Proc and a Lambda in Ruby?

3

What does `[1,2,3].map { |n| n * 2 }` return?

4

What does `attr_accessor` create in a Ruby class?