Ruby

#16
TIOBE#24
PYPL#17
GitHub#18
RedMonk#9
IEEESpectrum#18
JetBrains#18
Programming LanguageObject-orientedScriptingWeb DevelopmentRuby on RailsDynamic Typing

Programming Language

Ruby

Overview

Ruby is an object-oriented scripting language developed in Japan.

Details

Ruby is an object-oriented scripting language developed in Japan by Yukihiro Matsumoto (Matz) in 1995. Based on the design philosophy that emphasizes "programmer happiness," it features highly readable, natural syntax and powerful expressiveness. With the consistent design principle that "everything is an object," it enables flexible and intuitive programming. The explosive popularity in web development came with David Heinemeier Hansson's release of the Ruby on Rails framework in 2004. Following the principle of "Convention over Configuration," it enables rapid development and has been adopted by famous services like GitHub, Shopify, Airbnb, and Twitter (early version). It continues to evolve through an active community.

Code Examples

Hello World

# Basic output
puts "Hello, World!"

# Multiple output methods
print "Hello, "
print "Ruby!\n"

# Output using variables
message = "Hello, Ruby!"
puts message

# String interpolation
name = "John"
age = 25
puts "My name is #{name} and I am #{age} years old."

# Multi-line strings
multiline = <<~TEXT
  This is a multi-line
  string.
  Very Ruby-like way to write.
TEXT
puts multiline

# p method (for debugging)
p "This is p method output"  # Shows with quotes

# printf style
printf "My name is %s and I am %d years old.\n", name, age

Variables and Data Types

# Numbers
number = 42
float_number = 3.14159
big_number = 123_456_789  # Underscore for digit separation
binary = 0b1010           # Binary
octal = 0o755            # Octal
hex = 0xFF               # Hexadecimal

# Strings
single_quoted = 'Single quoted string'
double_quoted = "Double quoted string"
name = "Smith"
interpolated = "Hello, #{name}!"  # String interpolation

# Symbols
symbol = :hello
status = :active
http_method = :get

# Arrays
fruits = ["Apple", "Banana", "Orange"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "string", :symbol, true]

# Easy array creation
words = %w[red green blue yellow]  # ["red", "green", "blue", "yellow"]
symbols = %i[start pause stop]     # [:start, :pause, :stop]

# Hashes (associative arrays)
person = {
  "name" => "John Smith",
  "age" => 30,
  "city" => "New York"
}

# Hash with symbol keys (modern syntax)
person_modern = {
  name: "Jane Doe",
  age: 25,
  city: "San Francisco"
}

# Ranges
range1 = 1..10      # 1 to 10 (inclusive)
range2 = 1...10     # 1 to 10 (exclusive)
alphabet = 'a'..'z' # a to z

# Boolean values
is_active = true
is_complete = false
nothing = nil       # Ruby's null value

# Regular expressions
pattern = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/
email = "[email protected]"

# Check variable types
puts number.class      # Integer
puts float_number.class # Float
puts name.class        # String
puts symbol.class      # Symbol
puts fruits.class      # Array
puts person.class      # Hash
puts pattern.class     # Regexp

# Type conversion
str_number = "123"
puts str_number.to_i   # String to integer
puts number.to_s       # Integer to string
puts number.to_f       # Integer to float
puts fruits.join(", ") # Array to string

# Constants
TAX_RATE = 0.1
COMPANY_NAME = "ABC Corporation"

puts "Number: #{number}"
puts "String: #{double_quoted}"
puts "Interpolated string: #{interpolated}"
puts "Array: #{fruits}"
puts "Hash: #{person}"
puts "Range includes 5: #{range1.include?(5)}"
puts "Email matches: #{email.match?(pattern)}"

Methods and Blocks

# Basic method definition
def add(a, b)
  a + b  # Ruby automatically returns the last expression
end

# Explicit return
def subtract(a, b)
  return a - b
end

# Default arguments
def greet(name, greeting = "Hello")
  "#{greeting}, #{name}!"
end

# Keyword arguments
def create_user(name:, age:, email: nil)
  user = {
    name: name,
    age: age,
    email: email
  }
  puts "User created: #{user}"
  user
end

# Variable arguments
def sum(*numbers)
  numbers.sum
end

# Keyword argument splat
def log_info(message, **options)
  puts "Message: #{message}"
  puts "Options: #{options}" unless options.empty?
end

# Method with block
def repeat_task(times)
  times.times do |i|
    yield(i + 1) if block_given?
  end
end

# Method accepting block
def process_data(data, &block)
  data.map(&block)
end

# Method call examples
puts add(5, 3)
puts subtract(10, 4)
puts greet("Smith")
puts greet("Johnson", "Good morning")

create_user(name: "Brown", age: 30)
create_user(name: "Wilson", age: 25, email: "[email protected]")

puts sum(1, 2, 3, 4, 5)
log_info("Process started", level: :info, timestamp: Time.now)

# Block usage examples
repeat_task(3) do |count|
  puts "Executing task #{count}..."
end

# Array methods
numbers = [1, 2, 3, 4, 5]

# each (iterate over each element)
numbers.each { |n| puts "Number: #{n}" }

# map (transform)
doubled = numbers.map { |n| n * 2 }
puts "Doubled: #{doubled}"

# select (filter)
evens = numbers.select { |n| n.even? }
puts "Even numbers: #{evens}"

# reject (exclude)
odds = numbers.reject { |n| n.even? }
puts "Odd numbers: #{odds}"

# reduce (aggregate)
total = numbers.reduce(0) { |sum, n| sum + n }
puts "Total: #{total}"

# Shorthand
sum_short = numbers.sum
puts "Total (shorthand): #{sum_short}"

# Hash methods
person = { name: "Smith", age: 30, city: "New York" }

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

# Method chaining
result = numbers
  .select { |n| n.odd? }
  .map { |n| n ** 2 }
  .sum

puts "Sum of odd squares: #{result}"

# proc and lambda
square_proc = proc { |x| x ** 2 }
square_lambda = lambda { |x| x ** 2 }

puts "proc result: #{square_proc.call(5)}"
puts "lambda result: #{square_lambda.call(5)}"

# Convert block to proc
def use_block(&block)
  block.call("Hello from proc!")
end

use_block { |msg| puts msg }

Classes and Objects

# Basic class
class Person
  # Automatic accessor methods
  attr_reader :name, :age      # Read-only
  attr_writer :email          # Write-only
  attr_accessor :phone        # Read and write

  # Class variable
  @@population = 0

  # Constant
  SPECIES = "Homo sapiens"

  # Initialize method (constructor)
  def initialize(name, age)
    @name = name    # Instance variable
    @age = age
    @id = generate_id
    @@population += 1
  end

  # Instance method
  def introduce
    "My name is #{@name} and I am #{@age} years old."
  end

  def birthday
    @age += 1
    puts "Happy #{@age}th birthday, #{@name}!"
  end

  # Class method
  def self.population
    @@population
  end

  def self.create_anonymous
    new("Anonymous", 0)
  end

  # Private method
  private

  def generate_id
    "ID_#{Time.now.to_i}_#{rand(1000)}"
  end
end

# Inheritance
class Student < Person
  attr_accessor :school, :grade

  def initialize(name, age, school)
    super(name, age)  # Call parent's initialize
    @school = school
    @grade = "Not set"
  end

  def introduce
    super + " I'm a student at #{@school}."
  end

  def study(subject)
    puts "#{@name} is studying #{subject}."
  end
end

# Module (mixin)
module Greetable
  def say_hello
    "Hello! I'm #{name}."
  end

  def say_goodbye
    "Goodbye from #{name}!"
  end
end

# Using module
class Employee < Person
  include Greetable  # Mixin

  attr_accessor :company, :position

  def initialize(name, age, company, position)
    super(name, age)
    @company = company
    @position = position
  end

  def work
    puts "#{@name} works at #{@company} as a #{@position}."
  end
end

# Usage examples
puts "=== Class Usage Examples ==="

# Create instances
person1 = Person.new("John Smith", 25)
person2 = Person.new("Jane Doe", 30)

puts person1.introduce
puts person2.introduce

# Using accessors
person1.phone = "090-1234-5678"
puts "Phone: #{person1.phone}"

# Birthday
person1.birthday

# Class method call
puts "Total population: #{Person.population}"

anonymous = Person.create_anonymous
puts anonymous.introduce

# Inheritance example
student = Student.new("Bob Wilson", 20, "MIT")
puts student.introduce
student.study("Computer Science")

# Mixin example
employee = Employee.new("Alice Brown", 35, "Tech Corp", "Engineer")
puts employee.introduce
employee.work
puts employee.say_hello
puts employee.say_goodbye

puts "Total population: #{Person.population}"

# Object inspection
puts "\n=== Object Inspection ==="
puts "person1's class: #{person1.class}"
puts "student's class: #{student.class}"
puts "student is a Person: #{student.is_a?(Person)}"
puts "student is a Student: #{student.is_a?(Student)}"
puts "employee includes Greetable: #{employee.class.include?(Greetable)}"

# Metaprogramming example
puts "\n=== Metaprogramming ==="

# Dynamic method definition
class DynamicClass
  ["create", "update", "delete"].each do |action|
    define_method("#{action}_user") do |user_id|
      puts "#{action.capitalize}ing user #{user_id}"
    end
  end
end

dynamic = DynamicClass.new
dynamic.create_user(123)
dynamic.update_user(456)
dynamic.delete_user(789)

# Using method_missing
class FlexibleClass
  def initialize
    @data = {}
  end

  def method_missing(method_name, *args)
    if method_name.to_s.end_with?('=')
      # Setter
      attribute = method_name.to_s.chop
      @data[attribute] = args.first
    else
      # Getter
      @data[method_name.to_s]
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    true
  end
end

flexible = FlexibleClass.new
flexible.name = "Dynamic Object"
flexible.value = 42
puts "Name: #{flexible.name}"
puts "Value: #{flexible.value}"

Exception Handling and Error Management

# Custom exception classes
class ValidationError < StandardError
  attr_reader :field

  def initialize(message, field = nil)
    super(message)
    @field = field
  end
end

class AgeError < ValidationError; end
class EmailError < ValidationError; end

# Methods that raise exceptions
def validate_age(age)
  raise AgeError.new("Age must be 0 or greater", :age) if age < 0
  raise AgeError.new("Age must be 150 or less", :age) if age > 150
  true
end

def validate_email(email)
  pattern = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/
  unless email.match?(pattern)
    raise EmailError.new("Invalid email address: #{email}", :email)
  end
  true
end

def divide(a, b)
  raise ZeroDivisionError, "Cannot divide by zero" if b == 0
  a.to_f / b
end

# Exception handling examples
puts "=== Exception Handling ==="

# begin-rescue-end
begin
  result = divide(10, 2)
  puts "10 ÷ 2 = #{result}"
rescue ZeroDivisionError => e
  puts "Math error: #{e.message}"
end

# Catching multiple exceptions
begin
  validate_age(-5)
rescue AgeError => e
  puts "Age error: #{e.message} (field: #{e.field})"
rescue ValidationError => e
  puts "Validation error: #{e.message}"
rescue StandardError => e
  puts "Unexpected error: #{e.message}"
end

# ensure clause (always executed)
begin
  puts "Starting process"
  validate_email("invalid-email")
rescue EmailError => e
  puts "Email error: #{e.message}"
ensure
  puts "Cleanup process (always executed)"
end

# else clause (executed when no exception occurs)
begin
  validate_age(25)
rescue AgeError => e
  puts "Age error: #{e.message}"
else
  puts "Age validation successful"
ensure
  puts "Validation process completed"
end

# retry (retry execution)
attempt = 0
begin
  attempt += 1
  puts "Attempt #{attempt}"
  
  if attempt < 3
    raise "Temporary error"
  end
  
  puts "Process successful!"
rescue => e
  puts "Error: #{e.message}"
  if attempt < 3
    puts "Retrying..."
    retry
  else
    puts "Maximum attempts reached"
  end
end

# raise (re-raise exception)
def process_data(data)
  validate_age(data[:age])
rescue AgeError => e
  puts "Error during data processing: #{e.message}"
  raise  # Re-raise exception
end

begin
  process_data(age: -10)
rescue AgeError
  puts "Error caught in caller"
end

# Using custom exceptions
class UserService
  def create_user(name, age, email)
    validate_age(age)
    validate_email(email)
    
    # User creation process
    user = { name: name, age: age, email: email, id: rand(1000) }
    puts "User creation successful: #{user}"
    user
  rescue ValidationError => e
    puts "User creation failed: #{e.message}"
    nil
  end
end

service = UserService.new
service.create_user("Smith", 25, "[email protected]")
service.create_user("Johnson", -5, "[email protected]")
service.create_user("Brown", 30, "invalid-email")

# throw/catch (non-local exit, not exception)
def find_in_nested_array(array, target)
  catch(:found) do
    array.each do |subarray|
      subarray.each do |item|
        throw(:found, item) if item == target
      end
    end
    nil  # Not found
  end
end

nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = find_in_nested_array(nested, 5)
puts "Search result: #{result}"

File Operations and I/O

# File read/write operations
puts "=== File Operations ==="

# Write to file
File.open("sample.txt", "w") do |file|
  file.puts "This is a sample file."
  file.puts "We're doing file operations in Ruby."
  file.puts "Using blocks automatically closes the file."
end

# Read from file
content = File.read("sample.txt")
puts "File content:"
puts content

# Read line by line
puts "\nReading line by line:"
File.foreach("sample.txt") do |line|
  puts "Line: #{line.strip}"
end

# Read as array
lines = File.readlines("sample.txt")
puts "\nRead as array: #{lines.map(&:strip)}"

# Append to file
File.open("sample.txt", "a") do |file|
  file.puts "This line was appended."
end

# CSV file operations
require 'csv'

# Create CSV data
csv_data = [
  ["Name", "Age", "Occupation"],
  ["John Smith", 30, "Engineer"],
  ["Jane Doe", 25, "Designer"],
  ["Bob Wilson", 35, "Manager"]
]

# Write to CSV file
CSV.open("people.csv", "w") do |csv|
  csv_data.each { |row| csv << row }
end

# Read from CSV file
puts "\nCSV file content:"
CSV.foreach("people.csv", headers: true) do |row|
  puts "#{row['Name']} (#{row['Age']} years old) - #{row['Occupation']}"
end

# JSON file operations
require 'json'

# Convert hash to JSON
user_data = {
  name: "Alice Johnson",
  age: 28,
  hobbies: ["Reading", "Movies", "Programming"],
  address: {
    city: "San Francisco",
    state: "California"
  }
}

# Write to JSON file
File.open("user.json", "w") do |file|
  file.write(JSON.pretty_generate(user_data))
end

# Read from JSON file
loaded_user = JSON.parse(File.read("user.json"), symbolize_names: true)
puts "\nData loaded from JSON:"
puts "Name: #{loaded_user[:name]}"
puts "Hobbies: #{loaded_user[:hobbies].join(', ')}"

# Directory operations
puts "\n=== Directory Operations ==="

# Current directory
puts "Current directory: #{Dir.pwd}"

# Create directory
Dir.mkdir("test_dir") unless Dir.exist?("test_dir")

# Get file list
files = Dir.glob("*.txt")
puts "txt files: #{files}"

# Process files in directory
Dir.foreach(".") do |file|
  next if file.start_with?(".")
  puts "File: #{file} (size: #{File.size(file)} bytes)" if File.file?(file)
end

# File information
if File.exist?("sample.txt")
  stat = File.stat("sample.txt")
  puts "\nsample.txt information:"
  puts "Size: #{stat.size} bytes"
  puts "Last modified: #{stat.mtime}"
  puts "Permissions: #{sprintf('%o', stat.mode)}"
end

# Path operations
require 'pathname'

path = Pathname.new("test_dir/subdir/file.txt")
puts "\nPath operations:"
puts "Directory name: #{path.dirname}"
puts "File name: #{path.basename}"
puts "Extension: #{path.extname}"
puts "File name without extension: #{path.basename(path.extname)}"

# Temporary files
require 'tempfile'

Tempfile.create("ruby_temp") do |temp|
  temp.write("Temporary data")
  temp.rewind
  puts "\nTemporary file content: #{temp.read}"
  puts "Temporary file path: #{temp.path}"
end

# Cleanup
File.delete("sample.txt") if File.exist?("sample.txt")
File.delete("people.csv") if File.exist?("people.csv")
File.delete("user.json") if File.exist?("user.json")
Dir.rmdir("test_dir") if Dir.exist?("test_dir")

puts "\nFile operations examples completed."

Versions

Version Release Date Major Features
Ruby 3.3 2023-12 Prism parser, YJIT improvements
Ruby 3.2 2022-12 WASI support, Anonymous rest arguments
Ruby 3.1 2021-12 YJIT JIT compiler, IRB improvements
Ruby 3.0 2020-12 Pattern matching, Keyword arguments separation
Ruby 2.7 2019-12 Pattern matching (experimental), Compaction GC
Ruby 2.6 2018-12 JIT compiler, Endless range
Ruby 2.5 2017-12 rescue/else/ensure in do/end blocks

Reference Links

Official Documentation

Learning Resources

Frameworks and Tools