A Comprehensive Introduction for Absolute Beginners Table of Contents 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 1. Introduction to Ruby 1.1 A Brief History of Rub…
A Comprehensive Introduction for Absolute Beginners
Table of Contents
Introduction to Ruby
Syntax Basics
Operators
Control Flow
Data Structures
Methods
Object-Oriented Programming
Exception Handling
Files and I/O
Gems and Bundler
Best Practices and Idiomatic Ruby
Conclusion and Next Steps
1. Introduction to Ruby
1.1 A Brief History of Ruby
Ruby was created by Yukihiro "Matz" Matsumoto and released to the public in 1995. Matz, a Japanese computer scientist, wanted to design a language that emphasized human happiness and productivity. He blended elements from his favorite languages:
- Perl for its text-processing power
- Smalltalk for its pure object-oriented design
- Eiffel for its elegant syntax
- Ada for its readability
- Lisp for its functional programming features
The result was Ruby: a dynamic, reflective, general-purpose, interpreted language that treats everything as an object.
# In Ruby, even a simple number is an object with methods.
puts 5.class # => Integer
puts 5.even? # => false
puts "hello".upcase # => HELLO
puts nil.class # => NilClass
1.2 The Philosophy of Ruby: MINASWAN
Ruby's design is guided by a simple, powerful philosophy:
MINASWAN — "Matz Is Nice And So We Are Nice."
This isn't just a cute acronym. It reflects the Ruby community's commitment to:
- Kindness in code reviews and discussions
- Inclusivity for beginners and experts alike
- Collaboration over competition
Matz himself said:
"Ruby is designed to make programmers happy."
This philosophy explains why Ruby's syntax is so readable and why the community is famously welcoming.
1.3 Why Learn Ruby?
Ruby remains one of the most pleasant languages to write and read. Its key strengths:
| Strength |
Description |
| Readability |
Code reads almost like English. |
| Object-Oriented |
Everything is an object — no primitives. |
| Metaprogramming |
You can write code that writes code. |
| Rich Ecosystem |
Thousands of gems (libraries) available. |
| Rails |
The Ruby on Rails framework powers major sites like GitHub, Shopify, and Airbnb. |
| Community |
Friendly, diverse, and active. |
1.4 Installing Ruby
1.4.1 Windows Installation
- Download RubyInstaller from rubyinstaller.org.
- Run the installer. Choose the version with DevKit (for compiling native gems).
- Check "Add Ruby executables to your PATH."
- After installation, open Command Prompt and verify:
ruby -v
# => ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x64-mingw-ucrt]
1.4.2 macOS Installation
macOS ships with an old version of Ruby. To get a modern version:
# Install Homebrew first (if not installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install rbenv (Ruby version manager)
brew install rbenv
# Install a recent Ruby version
rbenv install 3.2.2
rbenv global 3.2.2
# Verify
ruby -v
1.4.3 Linux Installation
Using rbenv is recommended on Linux too:
# Ubuntu/Debian
sudo apt update
sudo apt install -y build-essential libssl-dev libreadline-dev zlib1g-dev
# Install rbenv
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-installer | bash
# Add to PATH (add to ~/.bashrc)
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
# Install Ruby
rbenv install 3.2.2
rbenv global 3.2.2
ruby -v
1.5 Your First Ruby Program
Create a file named hello.rb:
# hello.rb
# The classic first program in any language.
puts "Hello, World!" # puts adds a newline at the end
print "Hello, " # print does NOT add a newline
print "World!\n" # We add it manually
p "Hello, World!" # p is like puts but shows quotes (useful for debugging)
Run it from the terminal:
ruby hello.rb
Output:
Hello, World!
Hello, World!
"Hello, World!"
Why three methods?
puts — "put string" — prints and adds a newline.
print — prints exactly what you give it.
p — "print inspect" — shows the raw representation, great for debugging.
1.6 Interactive Ruby (irb)
irb is Ruby's REPL (Read-Eval-Print Loop). It lets you test code instantly.
irb
Example session:
irb(main):001:0> 2 + 3
=> 5
irb(main):002:0> "hello".capitalize
=> "Hello"
irb(main):003:0> [1, 2, 3].map { |n| n * 2 }
=> [2, 4, 6]
irb(main):004:0> exit
Tip: Try pry (a gem) for an even more powerful REPL with syntax highlighting and debugging.
2. Syntax Basics
2.1 Comments
# This is a single-line comment.
=begin
This is a multi-line comment.
It is rarely used in modern Ruby;
most developers prefer multiple # lines.
=end
2.2 Variables
Ruby is dynamically typed — you don't declare types. A variable's type is inferred from its value.
name = "Alice" # String
age = 30 # Integer
price = 19.99 # Float
is_student = true # Boolean
nothing = nil # NilClass
Variable naming conventions:
| Type |
Convention |
Example |
| Local |
snake_case |
first_name |
| Global |
$ prefix |
$app_name |
| Instance |
@ prefix |
@user |
| Class |
@@ prefix |
@@count |
| Constant |
SCREAMING_SNAKE_CASE |
MAX_SIZE |
2.3 Constants
Constants start with an uppercase letter. They can be reassigned (Ruby won't stop you) but will emit a warning.
PI = 3.14159
MAX_LOGIN_ATTEMPTS = 5
# PI = 3.14 # warning: already initialized constant PI
2.4 Data Types
Ruby has several core data types. Since everything is an object, each has methods.
Strings
greeting = "Hello"
name = 'Alice'
# Methods
puts greeting.length # => 5
puts greeting.upcase # => HELLO
puts greeting.downcase # => hello
puts greeting.reverse # => olleH
puts greeting.include?("ell") # => true
puts greeting * 3 # => HelloHelloHello
Integers and Floats
a = 10 # Integer
b = 3 # Integer
c = 10.0 # Float
puts a + b # => 13
puts a / b # => 3 (integer division!)
puts c / b # => 3.3333333333333335 (float division)
puts a % b # => 1 (modulus)
puts a ** 2 # => 100 (exponent)
Booleans
is_active = true
is_admin = false
puts is_active && is_admin # => false
puts is_active || is_admin # => true
puts !is_active # => false
Nil
nil represents "nothing" or "no value." It is an object of class NilClass.
x = nil
puts x.nil? # => true
puts x.to_s # => ""
puts x.inspect # => nil
2.5 Type Conversion
# String to Integer
"42".to_i # => 42
"3.14".to_i # => 3 (stops at decimal)
"hello".to_i # => 0
# String to Float
"3.14".to_f # => 3.14
"abc".to_f # => 0.0
# Integer/Float to String
42.to_s # => "42"
3.14.to_s # => "3.14"
# Integer to Float
42.to_f # => 42.0
# Float to Integer (truncates)
3.99.to_i # => 3
3.99.round # => 4
3.99.floor # => 3
3.99.ceil # => 4
2.6 String Interpolation
Only works with double quotes. #{} embeds any Ruby expression.
name = "Alice"
age = 30
puts "My name is #{name} and I am #{age} years old."
# => My name is Alice and I am 30 years old.
puts "Next year I'll be #{age + 1}."
# => Next year I'll be 31.
# Single quotes do NOT interpolate:
puts 'My name is #{name}.'
# => My name is #{name}.
3. Operators
3.1 Arithmetic Operators
a = 10
b = 3
puts a + b # => 13 Addition
puts a - b # => 7 Subtraction
puts a * b # => 30 Multiplication
puts a / b # => 3 Integer Division
puts a % b # => 1 Modulus
puts a ** b # => 1000 Exponentiation
3.2 Comparison Operators
puts 5 == 5 # => true Equal
puts 5 != 3 # => true Not equal
puts 5 > 3 # => true Greater than
puts 5 < 3 # => false Less than
puts 5 >= 5 # => true Greater or equal
puts 5 <= 4 # => false Less or equal
puts 5 <=> 3 # => 1 Spaceship (see below)
3.3 Logical Operators
t = true
f = false
puts t && f # => false AND
puts t || f # => true OR
puts !t # => false NOT
# Short-circuit evaluation
puts f && puts("never printed") # => false
puts t || puts("never printed") # => true
3.4 Assignment Operators
x = 10
x += 5 # x = x + 5 => 15
x -= 3 # x = x - 3 => 12
x *= 2 # x = x * 2 => 24
x /= 4 # x = x / 4 => 6
x %= 4 # x = x % 4 => 2
x **= 3 # x = x ** 3 => 8
# Parallel assignment
a, b = 1, 2
a, b = b, a # Swap without temp variable
puts a # => 2
puts b # => 1
3.5 The Spaceship Operator
<=> returns:
-1 if left is less than right
0 if equal
1 if left is greater than right
puts 1 <=> 2 # => -1
puts 2 <=> 2 # => 0
puts 3 <=> 2 # => 1
# Commonly used for sorting
arr = [3, 1, 4, 1, 5, 9, 2, 6]
puts arr.sort { |a, b| a <=> b }.inspect
# => [1, 1, 2, 3, 4, 5, 6, 9]
4. Control Flow
4.1 Conditional Statements
if / elsif / else
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
elsif score >= 70
puts "C"
else
puts "F"
end
# => B
unless
unless is the opposite of if. Use it when the condition is negative.
logged_in = false
unless logged_in
puts "Please log in."
end
# => Please log in.
Modifier Form
Ruby allows single-line conditionals:
puts "Adult" if age >= 18
puts "Minor" unless age >= 18
Ternary Operator
status = age >= 18 ? "Adult" : "Minor"
4.2 The case Statement
case is Ruby's switch statement — but far more flexible.
grade = "B"
case grade
when "A"
puts "Excellent!"
when "B"
puts "Good job!"
when "C"
puts "You passed."
else
puts "Try again."
end
# => Good job!
Ranges in case:
score = 85
case score
when 90..100 then puts "A"
when 80...90 then puts "B"
when 70...80 then puts "C"
else puts "F"
end
# => B
Multiple values:
case day
when "Saturday", "Sunday"
puts "Weekend!"
else
puts "Weekday"
end
4.3 Loops
while
i = 1
while i <= 5
puts i
i += 1
end
# Prints 1 through 5
until
i = 1
until i > 5
puts i
i += 1
end
# Prints 1 through 5
for
for i in 1..5
puts i
end
# Prints 1 through 5
Idiomatic Ruby: Most Rubyists avoid for and prefer iterators like each.
4.4 Iterators
Iterators are the preferred way to loop in Ruby.
each
[1, 2, 3].each do |n|
puts n
end
# Single-line version
[1, 2, 3].each { |n| puts n }
times
5.times do |i|
puts "Iteration #{i}"
end
# Iteration 0 ... Iteration 4
upto and downto
1.upto(5) { |n| puts n }
5.downto(1) { |n| puts n }
map, select, reject, reduce
nums = [1, 2, 3, 4, 5]
puts nums.map { |n| n * 2 }.inspect # => [2, 4, 6, 8, 10]
puts nums.select { |n| n.even? }.inspect # => [2, 4]
puts nums.reject { |n| n.even? }.inspect # => [1, 3, 5]
puts nums.reduce(0) { |sum, n| sum + n } # => 15
4.5 Loop Control Keywords
# break — exit the loop entirely
1.upto(10) do |n|
break if n > 5
puts n
end
# Prints 1..5
# next — skip to the next iteration
1.upto(5) do |n|
next if n.even?
puts n
end
# Prints 1, 3, 5
# redo — repeat the current iteration
# (rarely used)
5. Data Structures
5.1 Arrays
Arrays are ordered, integer-indexed collections.
# Creation
arr = [1, 2, 3]
arr2 = Array.new(3, 0) # => [0, 0, 0]
arr3 = %w[apple banana cherry] # => ["apple", "banana", "cherry"]
# Access
puts arr[0] # => 1
puts arr[-1] # => 3 (last element)
puts arr.first # => 1
puts arr.last # => 3
# Adding/Removing
arr.push(4) # => [1, 2, 3, 4]
arr << 5 # => [1, 2, 3, 4, 5] (shovel operator)
arr.pop # => 5
arr.shift # => 1 (removes first)
arr.unshift(0) # => [0, 2, 3, 4]
# Useful methods
puts arr.length # => 5
puts arr.include?(3) # => true
puts arr.reverse.inspect # => [4, 3, 2, 0]
puts arr.sort.inspect # => [0, 2, 3, 4]
puts arr.join(", ") # => "0, 2, 3, 4"
puts arr.uniq.inspect # => [0, 2, 3, 4]
puts arr.flatten.inspect # => [0, 2, 3, 4]
# Nested arrays
matrix = [[1, 2], [3, 4]]
puts matrix[0][1] # => 2
5.2 Hashes
Hashes are key-value collections (like dictionaries in other languages).
# Creation with symbols (preferred)
person = { name: "Alice", age: 30, city: "NYC" }
# Creation with strings
person2 = { "name" => "Bob", "age" => 25 }
# Access
puts person[:name] # => Alice
puts person[:age] # => 30
# Adding/Modifying
person[:email] = "alice@example.com"
person[:age] = 31
# Useful methods
puts person.keys.inspect # => [:name, :age, :city, :email]
puts person.values.inspect # => ["Alice", 31, "NYC", "alice@example.com"]
puts person.key?(:name) # => true
puts person.fetch(:name) # => Alice
puts person.fetch(:unknown, "default") # => "default"
# Iteration
person.each do |key, value|
puts "#{key}: #{value}"
end
# Merging
defaults = { theme: "light", lang: "en" }
user_prefs = { theme: "dark" }
puts defaults.merge(user_prefs).inspect
# => {:theme=>"dark", :lang=>"en"}
5.3 Ranges
Ranges represent a sequence of values.
# Inclusive range
(1..5).to_a # => [1, 2, 3, 4, 5]
# Exclusive range
(1...5).to_a # => [1, 2, 3, 4]
# Letter ranges
("a".."e").to_a # => ["a", "b", "c", "d", "e"]
# Check inclusion
puts (1..10).include?(5) # => true
# Use in case statements
case score
when 90..100 then puts "A"
end
5.4 Sets
Sets are unordered collections of unique elements. Require require 'set'.
require 'set'
set = Set.new([1, 2, 3, 3, 3])
puts set.inspect # => #<Set: {1, 2, 3}>
set.add(4)
set.delete(1)
puts set.include?(2) # => true
# Set operations
a = Set.new([1, 2, 3])
b = Set.new([3, 4, 5])
puts (a & b).inspect # => #<Set: {3}> (intersection)
puts (a | b).inspect # => #<Set: {1, 2, 3, 4, 5}> (union)
puts (a - b).inspect # => #<Set: {1, 2}> (difference)
5.5 Symbols
Symbols are immutable, reusable identifiers. They are often used as hash keys.
:name # Symbol literal
"name".to_sym # => :name
:name.to_s # => "name"
# Why symbols?
# 1. Immutable — cannot be changed
# 2. Memory-efficient — same symbol is the same object
puts :name.object_id == :name.object_id # => true
puts "name".object_id == "name".object_id # => false (different strings)
# Common use: hash keys
person = { name: "Alice", age: 30 }
6. Methods
6.1 Defining Methods
def greet
puts "Hello!"
end
greet # => Hello!
Methods return the value of the last expression automatically.
def add(a, b)
a + b # implicit return
end
puts add(2, 3) # => 5
6.2 Method Arguments
Default Arguments
def greet(name = "World")
"Hello, #{name}!"
end
puts greet # => Hello, World!
puts greet("Alice") # => Hello, Alice!
Keyword Arguments
def create_user(name:, age:, city: "Unknown")
"#{name} (#{age}) from #{city}"
end
puts create_user(name: "Alice", age: 30)
# => Alice (30) from Unknown
puts create_user(name: "Bob", age: 25, city: "NYC")
# => Bob (25) from NYC
Splat Arguments
def sum(*numbers)
numbers.reduce(0) { |total, n| total + n }
end
puts sum(1, 2, 3) # => 6
puts sum(1, 2, 3, 4, 5) # => 15
Double Splat (Keyword Splat)
def print_options(**options)
options.each { |k, v| puts "#{k}: #{v}" }
end
print_options(color: "red", size: "large")
6.3 Return Values
def check(x)
return "negative" if x < 0
return "zero" if x == 0
"positive"
end
puts check(-5) # => negative
puts check(0) # => zero
puts check(5) # => positive
6.4 Blocks
A block is a chunk of code passed to a method. It is not an object — it's a special syntax.
# do...end form (multi-line)
[1, 2, 3].each do |n|
puts n
end
# { } form (single-line)
[1, 2, 3].each { |n| puts n }
Yielding to a block:
def repeat(times)
times.times { yield }
end
repeat(3) { puts "Hello!" }
# Hello!
# Hello!
# Hello!
Block with arguments:
def each_item(arr)
arr.each { |item| yield(item) }
end
each_item([1, 2, 3]) { |n| puts n * 10 }
# 10
# 20
# 30
Check if a block was given:
def maybe_yield
if block_given?
yield
else
puts "No block given"
end
end
maybe_yield # => No block given
maybe_yield { puts "Hello!" } # => Hello!
6.5 Procs
A Proc is a block converted into an object.
square = Proc.new { |x| x * x }
puts square.call(5) # => 25
# Alternative syntax
cube = proc { |x| x ** 3 }
puts cube.call(3) # => 27
6.6 Lambdas
Lambdas are like Procs but with stricter rules.
square = lambda { |x| x * x }
puts square.call(4) # => 16
# Stabby lambda syntax (preferred)
square = ->(x) { x * x }
puts square.call(4) # => 16
# With multiple arguments
add = ->(a, b) { a + b }
puts add.call(2, 3) # => 5
6.7 Blocks vs Procs vs Lambdas
| Feature |
Block |
Proc |
Lambda |
| Object? |
No |
Yes |
Yes |
| Can be stored? |
No |
Yes |
Yes |
| Checks arity? |
No |
No |
Yes |
return behavior |
Returns from method |
Returns from method |
Returns from lambda |
| Syntax |
{ } or do...end |
Proc.new or proc |
lambda or -> |
# Arity difference
p1 = proc { |a, b| "#{a}, #{b}" }
puts p1.call(1) # => "1, " (no error)
l1 = ->(a, b) { "#{a}, #{b}" }
# l1.call(1) # ArgumentError: wrong number of arguments
7. Object-Oriented Programming
7.1 Classes and Objects
class Dog
def initialize(name, breed)
@name = name
@breed = breed
end
def bark
"Woof! I'm #{@name}."
end
def info
"#{@name} is a #{@breed}."
end
end
dog = Dog.new("Rex", "Labrador")
puts dog.bark # => Woof! I'm Rex.
puts dog.info # => Rex is a Labrador.
7.2 Instance Variables and Methods
@variable — instance variable (available in all instance methods)
@@variable — class variable (shared across all instances)
self — refers to the current object
class Counter
@@count = 0 # class variable
def initialize
@@count += 1
end
def self.count # class method
@@count
end
end
Counter.new
Counter.new
puts Counter.count # => 2
7.3 Encapsulation with attr_accessor
Instead of writing getter/setter methods manually:
class Person
attr_accessor :name, :age # getter + setter
attr_reader :id # getter only
attr_writer :password # setter only
def initialize(name, age, id)
@name = name
@age = age
@id = id
end
end
p = Person.new("Alice", 30, 1)
puts p.name # => Alice
p.name = "Alicia"
puts p.name # => Alicia
puts p.id # => 1
# p.id = 2 # NoMethodError
p.password = "secret"
7.4 Inheritance
class Animal
def initialize(name)
@name = name
end
def speak
"..."
end
def introduce
"I am #{@name}. I say: #{speak}"
end
end
class Cat < Animal
def speak
"Meow!"
end
end
class Dog < Animal
def speak
"Woof!"
end
end
cat = Cat.new("Whiskers")
dog = Dog.new("Rex")
puts cat.introduce # => I am Whiskers. I say: Meow!
puts dog.introduce # => I am Rex. I say: Woof!
Calling super:
class Puppy < Dog
def speak
"Woof! (small) " + super
end
end
7.5 Modules and Mixins
Modules are collections of methods that can be mixed into classes.
module Swimmable
def swim
"I'm swimming!"
end
end
module Flyable
def fly
"I'm flying!"
end
end
class Duck
include Swimmable
include Flyable
end
class Fish
include Swimmable
end
duck = Duck.new
puts duck.swim # => I'm swimming!
puts duck.fly # => I'm flying!
fish = Fish.new
puts fish.swim # => I'm swimming!
Module as a namespace:
module MathUtils
def self.square(x)
x * x
end
end
puts MathUtils.square(5) # => 25
7.6 Polymorphism
Different classes respond to the same method name in different ways.
class Shape
def area
raise NotImplementedError, "Subclass must implement area"
end
end
class Circle < Shape
def initialize(radius)
@radius = radius
end
def area
3.14159 * @radius ** 2
end
end
class Rectangle < Shape
def initialize(w, h)
@w, @h = w, h
end
def area
@w * @h
end
end
shapes = [Circle.new(5), Rectangle.new(3, 4)]
shapes.each { |s| puts s.area.round(2) }
# => 78.54
# => 12
7.7 Method Visibility
class BankAccount
def initialize(balance)
@balance = balance
end
def deposit(amount)
@balance += amount
log_transaction("Deposit: #{amount}")
end
private # only callable within the class
def log_transaction(msg)
puts "[LOG] #{msg}"
end
end
account = BankAccount.new
account = BankAccount.new(100)
account.deposit(50) # => [LOG] Deposit: 50
# account.log_transaction("test") # NoMethodError (private)
**Visibility levels:**
| Keyword | Accessible from |
|---------|----------------|
| `public` (default) | Anywhere |
| `private` | Inside the class only (no explicit receiver) |
| `protected` | Inside the class and subclasses (with receiver) |
```ruby
class Account
def initialize(balance)
@balance = balance
end
def >(other)
balance > other.balance # protected allows access to other's balance
end
protected
def balance
@balance
end
end
a = Account.new(100)
b = Account.new(50)
puts a > b # => true
8. Exception Handling
8.1 Begin, Rescue, Ensure
begin
# Code that might raise an error
result = 10 / 0
rescue ZeroDivisionError => e
puts "Error: #{e.message}"
ensure
puts "This always runs."
end
# Output:
# Error: divided by 0
# This always runs.
Method-level rescue (idiomatic):
def divide(a, b)
a / b
rescue ZeroDivisionError => e
"Cannot divide by zero: #{e.message}"
end
puts divide(10, 0) # => Cannot divide by zero: divided by 0
Multiple rescue clauses:
begin
# risky code
rescue ArgumentError => e
puts "Bad argument: #{e.message}"
rescue TypeError => e
puts "Type error: #{e.message}"
rescue StandardError => e
puts "Other error: #{e.message}"
end
8.2 Raising Exceptions
def check_age(age)
raise ArgumentError, "Age must be positive" if age < 0
raise "Too young" if age < 18
"Welcome!"
end
begin
check_age(-5)
rescue ArgumentError => e
puts e.message # => Age must be positive
end
Re-raising:
begin
risky_operation
rescue => e
puts "Logging: #{e.message}"
raise # re-raises the same exception
end
8.3 Custom Exceptions
class InsufficientFundsError < StandardError
def initialize(msg = "Insufficient funds in account")
super
end
end
class BankAccount
def initialize(balance)
@balance = balance
end
def withdraw(amount)
raise InsufficientFundsError if amount > @balance
@balance -= amount
end
end
account = BankAccount.new(100)
begin
account.withdraw(200)
rescue InsufficientFundsError => e
puts e.message # => Insufficient funds in account
end
8.4 Retry and Else
attempts = 0
begin
attempts += 1
puts "Attempt #{attempts}"
raise "Temporary failure" if attempts < 3
puts "Success!"
rescue => e
retry if attempts < 3
puts "Failed after #{attempts} attempts."
else
puts "No exceptions were raised."
ensure
puts "Done."
end
# Output:
# Attempt 1
# Attempt 2
# Attempt 3
# Success!
# No exceptions were raised.
# Done.
Exception hierarchy (most common):
Exception
├── NoMemoryError
├── ScriptError
├── SignalException
├── StandardError ← rescue catches this by default
│ ├── ArgumentError
│ ├── IOError
│ ├── NameError
│ │ └── NoMethodError
│ ├── RuntimeError
│ ├── TypeError
│ ├── ZeroDivisionError
│ └── ...
└── ...
Best practice: Always inherit custom exceptions from StandardError (or a subclass), not from Exception, so a bare rescue catches them.
9. Files and I/O
9.1 Reading from Files
# Read entire file
content = File.read("example.txt")
puts content
# Read line by line (memory-efficient for large files)
File.foreach("example.txt") do |line|
puts line
end
# Read all lines into an array
lines = File.readlines("example.txt")
puts lines.inspect
# Using File.open with a block (auto-closes)
File.open("example.txt", "r") do |file|
file.each_line do |line|
puts line
end
end
Check if file exists:
puts File.exist?("example.txt") # => true or false
puts File.size("example.txt") # size in bytes
puts File.basename("/path/to/file.txt") # => "file.txt"
puts File.extname("file.txt") # => ".txt"
9.2 Writing to Files
# Write (overwrites existing content)
File.write("output.txt", "Hello, file!\n")
# Append
File.open("output.txt", "a") do |file|
file.puts "Appended line."
end
# Write multiple lines
File.open("output.txt", "w") do |file|
file.puts "Line 1"
file.puts "Line 2"
file.write "No newline here"
end
File modes:
| Mode |
Description |
"r" |
Read only (default). File must exist. |
"w" |
Write only. Truncates existing file or creates new. |
"a" |
Append. Creates file if missing. |
"r+" |
Read and write. File must exist. |
"w+" |
Read and write. Truncates or creates. |
"a+" |
Read and append. Creates if missing. |
9.3 Working with JSON and CSV
JSON
require 'json'
# Hash to JSON
data = { name: "Alice", age: 30, hobbies: ["reading", "coding"] }
json_str = data.to_json
puts json_str
# => {"name":"Alice","age":30,"hobbies":["reading","coding"]}
# JSON to Hash
parsed = JSON.parse(json_str)
puts parsed["name"] # => Alice
# Pretty-print
puts JSON.pretty_generate(data)
CSV
require 'csv'
# Write
CSV.open("users.csv", "w") do |csv|
csv << ["Name", "Age", "City"]
csv << ["Alice", 30, "NYC"]
csv << ["Bob", 25, "LA"]
end
# Read
CSV.foreach("users.csv", headers: true) do |row|
puts "#{row['Name']} is #{row['Age']} from #{row['City']}"
end
# => Alice is 30 from NYC
# => Bob is 25 from LA
10. Gems and Bundler
10.1 What Are Gems?
Gems are Ruby libraries (packages) that add functionality. RubyGems is the package manager.
gem --version # Check RubyGems version
gem list # List installed gems
gem search rails # Search for a gem
gem info rails # Detailed info
10.2 Installing Gems
gem install rails # Install latest
gem install rails -v 7.0.0 # Install specific version
gem uninstall rails # Uninstall
10.3 Bundler and Gemfile
Bundler manages gem dependencies per project, ensuring consistent environments.
Install Bundler:
gem install bundler
Create a Gemfile:
# Gemfile
source "https://rubygems.org"
gem "rails", "~> 7.0"
gem "pg", "~> 1.5"
gem "rspec", group: :test
gem "rubocop", require: false
Install dependencies:
bundle install # or just: bundle
bundle update # Update gems within version constraints
bundle exec rspec # Run a command within the bundle context
Gemfile.lock: Auto-generated file that pins exact versions. Commit this to version control.
Version constraint operators:
| Operator |
Meaning |
Example |
= |
Exact version |
gem "rails", "= 7.0.0" |
>= |
At least |
gem "rails", ">= 7.0" |
~> |
Pessimistic (compatible) |
gem "rails", "~> 7.0" → >= 7.0, < 8.0 |
~> 7.0.1 |
|
>= 7.0.1, < 7.1.0 |
10.4 Popular Gems
| Gem |
Purpose |
| rails |
Full-stack web framework |
| sinatra |
Lightweight web framework |
| rspec |
Testing framework |
| rubocop |
Linter and formatter |
| pry |
Enhanced REPL/debugger |
| nokogiri |
HTML/XML parsing |
| httparty |
HTTP client |
| devise |
Authentication (Rails) |
| sidekiq |
Background jobs |
| faker |
Generate fake data |
11. Best Practices and Idiomatic Ruby
11.1 Ruby Style Guide
Follow the community style guide. Key rules:
# ✅ Good: snake_case for variables and methods
first_name = "Alice"
def calculate_total; end
# ✅ Good: SCREAMING_SNAKE_CASE for constants
MAX_RETRIES = 3
# ✅ Good: CamelCase for classes and modules
class UserAccount; end
module PaymentProcessor; end
# ✅ Good: 2-space indentation (NOT tabs)
def greet
puts "Hello"
end
# ✅ Good: parentheses in method definitions with args
def add(a, b)
a + b
end
# ✅ Good: no parentheses when calling methods without args
puts "hello"
user.save
Use rubocop to enforce style automatically:
gem install rubocop
rubocop # Check all files
rubocop -a # Auto-fix safe issues
rubocop -A # Auto-fix all issues (may be unsafe)
11.2 Idiomatic Ruby
Idiomatic Ruby is concise and expressive. Compare:
# ❌ Non-idiomatic
if !user.admin?
puts "Not admin"
end
array = [1, 2, 3]
doubled = []
array.each do |n|
doubled << n * 2
end
if x == 5
result = "five"
else
result = "other"
end
# ✅ Idiomatic
unless user.admin?
puts "Not admin"
end
doubled = array.map { |n| n * 2 }
result = x == 5 ? "five" : "other"
More idiomatic patterns:
# Use symbol-to-proc shorthand
["a", "b", "c"].map(&:upcase) # instead of .map { |s| s.upcase }
# Use `each_with_object` instead of building hashes manually
words = %w[apple banana apple cherry]
counts = words.each_with_object(Hash.new(0)) do |word, hash|
hash[word] += 1
end
# => {"apple"=>2, "banana"=>1, "cherry"=>1}
# Use safe navigation operator (&.)
user&.profile&.name # returns nil if user or profile is nil
# Use `fetch` for required hash keys
config.fetch(:api_key) # raises KeyError if missing
# Use `freeze` for immutable strings
NAME = "Alice".freeze # or use frozen_string_literal magic comment
# Use `tap` for side effects
user = User.new.tap { |u| u.name = "Alice" }
# Use `then`/`yield_self` for chaining
result = 5.then { |n| n * 2 }.then { |n| n + 1 } # => 11
Magic comment for frozen strings:
# frozen_string_literal: true
# All string literals in this file are now immutable.
# This improves performance and prevents bugs.
11.3 Common Pitfalls
1. Mutating vs Non-Mutating Methods
str = "hello"
str.upcase # returns "HELLO" but does NOT change str
puts str # => "hello"
str.upcase! # mutates str in place
puts str # => "HELLO"
Methods ending in ! (bang) usually mutate. But not always — save! raises instead of returning false.
2. String vs Symbol Hash Keys
h = { "name" => "Alice" }
puts h["name"] # => Alice
puts h[:name] # => nil (different key!)
# Modern syntax (symbols)
h = { name: "Alice" }
puts h[:name] # => Alice
3. == vs equal? vs eql?
a = "hello"
b = "hello"
puts a == b # => true (value equality)
puts a.equal?(b) # => false (object identity)
puts a.eql?(b) # => true (value + type equality)
puts 1 == 1.0 # => true (numeric value)
puts 1.eql?(1.0) # => false (different types)
4. nil vs false
Only nil and false are falsy. Everything else — including 0 and "" — is truthy.
if 0
puts "0 is truthy in Ruby!"
end
# => 0 is truthy in Ruby!
5. Mutable Default Arguments
# ❌ BAD — the array is shared across calls!
def add_item(item, list = [])
list << item
end
puts add_item("a").inspect # => ["a"]
puts add_item("b").inspect # => ["a", "b"] ← surprise!
# ✅ GOOD — create a new array each time
def add_item(item, list = nil)
list ||= []
list << item
end
12. Conclusion and Next Steps
Congratulations! You've completed a thorough introduction to Ruby. You now know:
- ✅ Ruby's history and philosophy (MINASWAN)
- ✅ Variables, data types, and operators
- ✅ Control flow with conditionals and loops
- ✅ Core data structures: arrays, hashes, ranges, sets, symbols
- ✅ Methods, blocks, procs, and lambdas
- ✅ Object-oriented programming with classes, modules, and mixins
- ✅ Exception handling
- ✅ File I/O and working with JSON/CSV
- ✅ Gems and Bundler for dependency management
- ✅ Idiomatic Ruby and best practices
Where to Go Next
A Final Word
Ruby was designed to make programmers happy. As you continue learning, remember:
- Write code for humans first, computers second.
- Prefer clarity over cleverness.
- Be nice — MINASWAN.
Happy coding! 🚀
Hashtags
[#ruby](/tags/ruby) [#rubylang](/tags/rubylang) [#programming](/tags/programming) [#learntocode](/tags/learntocode) [#learnruby](/tags/learnruby) [#oop](/tags/oop) [#objectorientedprogramming](/tags/objectorientedprogramming) [#backend](/tags/backend) [#webdevelopment](/tags/webdevelopment) [#rubyonrails](/tags/rubyonrails) [#coding](/tags/coding) [#developer](/tags/developer) [#softwareengineering](/tags/softwareengineering) [#minaswan](/tags/minaswan) [#codenewbie](/tags/codenewbie) #100DaysOfCode [#techeducation](/tags/techeducation) [#programmingtutorial](/tags/programmingtutorial) [#rubygems](/tags/rubygems) [#bundler](/tags/bundler) [#metaprogramming](/tags/metaprogramming) [#cleancode](/tags/cleancode) [#devcommunity](/tags/devcommunity)