이 글은 Ruby를 한 번도 사용해 본 적이 없는 초보자를 위해 기본 문법 전체를 예제 중심으로 아주 자세하게 설명한 실전 가이드입니다. 한 줄씩 따라 치면서 읽으면 글을 다 읽을 때쯤 Ruby로 간단한 프로그램을 직접 만들 수 있는 수준이 됩니다.
Ruby란 어떤 언어인가?
Ruby는 1995년 일본의 마츠모토 유키히로(Matz)가 만든 프로그래밍 언어입니다. 핵심 철학은 딱 한 가지, "개발자의 행복(Developer Happiness)"입니다. 다른 언어에서 복잡하게 써야 하는 것들을 자연스러운 문장처럼 쓸 수 있도록 설계되어 있습니다.
- 동적 타입: 변수 타입을 미리 선언하지 않아도 됩니다.
- 모든 것이 객체: 숫자, 문자열, 심지어 nil도 모두 객체입니다.
- 표현력이 뛰어난 문법: 코드가 영어 문장처럼 읽힙니다.
- Rails 생태계: 웹 프레임워크 Ruby on Rails로 유명합니다.
1. Ruby 설치와 첫 실행
터미널(맥/리눅스) 또는 명령 프롬프트(윈도우)에서 아래 명령으로 버전을 확인합니다.
ruby -v
# ruby 3.3.0 (2023-12-25 revision ...) ...
설치가 안 되어 있다면 rbenv 또는 공식 설치 패키지(ruby-lang.org)를 사용하세요. 설치 후 첫 번째 파일을 만들어 실행해봅니다.
# hello.rb
puts "Hello, Ruby!" # Hello, Ruby! 출력
ruby hello.rb
출력 메서드를 비교해보면 각각 다른 용도로 쓰입니다.
puts "사과" # "사과" 출력 후 줄바꿈
print "바나나" # "바나나" 출력, 줄바꿈 없음
p 42 # 42 (Integer 타입 그대로 — 디버깅용)
p "hello" # "hello" (따옴표 포함)
p [1, 2, 3] # [1, 2, 3]
p nil # nil
puts는 배열을 받으면 요소마다 줄을 바꿔 출력합니다. p는 개발 중 값을 확인할 때 씁니다. print는 줄바꿈 없이 이어붙일 때 씁니다.
2. 주석
주석은 코드 실행에 영향을 주지 않는 메모입니다. Ruby에서는 #으로 시작합니다.
# 한 줄 주석입니다
puts "실행됩니다" # 여기서부터 주석
=begin
여러 줄 주석은
=begin ... =end 로 작성합니다
보통 문서화에 사용합니다
=end
3. 변수와 상수
Ruby에서 변수는 값을 저장하는 상자입니다. 이름 앞에 붙는 기호로 종류가 구분됩니다.
3-1. 지역 변수 (Local Variable)
소문자나 밑줄(_)로 시작하며, 현재 스코프(메서드, 블록 등)에서만 유효합니다.
name = "Alice"
age = 28
_temp = 0
user_id = 1001
puts name # Alice
puts age # 28
3-2. 인스턴스 변수 (Instance Variable)
@로 시작하며, 클래스의 각 인스턴스(객체)마다 독립적으로 존재합니다. 초기화 전까지는 nil입니다.
class Dog
def set_name(n)
@name = n # 인스턴스 변수
end
def bark
puts "#{@name}이(가) 짖습니다!"
end
end
d = Dog.new
d.set_name("초코")
d.bark # 초코이(가) 짖습니다!
3-3. 클래스 변수 (Class Variable)
@@로 시작하며, 클래스 자신과 모든 인스턴스가 공유합니다.
class Counter
@@total = 0 # 모든 인스턴스가 공유
def initialize
@@total += 1
end
def self.total
@@total
end
end
Counter.new
Counter.new
Counter.new
puts Counter.total # 3
3-4. 전역 변수 (Global Variable)
$로 시작하며, 프로그램 어디서든 접근 가능합니다. 남용하면 코드를 추적하기 어려우므로 꼭 필요할 때만 사용합니다.
$app_name = "MyApp"
def greet
puts "#{$app_name}에 오신 것을 환영합니다!"
end
greet # MyApp에 오신 것을 환영합니다!
3-5. 상수 (Constant)
대문자로 시작합니다. 관례상 전부 대문자로 씁니다. 재할당하면 경고가 나고, 권장하지 않습니다.
PI = 3.14159265
MAX_SIZE = 100
APP_NAME = "FitSystem"
puts PI # 3.14159265
puts MAX_SIZE # 100
4. 기본 자료형
Ruby의 모든 값은 객체이며, 각 자료형마다 유용한 메서드가 내장되어 있습니다.
4-1. 정수 (Integer)
n = 42
big = 1_000_000 # 밑줄로 가독성 향상 (실제 값은 1000000)
puts n.class # Integer
puts n.even? # false (짝수 여부)
puts n.odd? # true (홀수 여부)
puts n.to_f # 42.0 (Float로 변환)
puts n.to_s # "42" (String으로 변환)
puts n.abs # 42 (절댓값)
puts(-5.abs) # 5
puts 10.gcd(4) # 2 (최대공약수)
puts 2 ** 10 # 1024 (거듭제곱)
4-2. 실수 (Float)
f = 3.14
puts f.class # Float
puts f.round # 3 (반올림, 기본 소수점 0자리)
puts f.round(1) # 3.1
puts f.ceil # 4 (올림)
puts f.floor # 3 (내림)
puts f.to_i # 3 (Integer로 변환, 소수점 버림)
puts f.nan? # false (NaN 여부)
4-3. 문자열 (String)
큰따옴표("") 안에서는 문자열 보간(#{})과 이스케이프 시퀀스가 동작합니다. 작은따옴표('') 안에서는 있는 그대로 출력됩니다.
name = "Ruby"
ver = 3.3
# 큰따옴표 — 보간(interpolation) 가능
puts "언어: #{name}, 버전: #{ver}" # 언어: Ruby, 버전: 3.3
puts "줄바꿈: \n두 번째 줄" # \n이 실제 줄바꿈으로 처리됨
# 작은따옴표 — 있는 그대로
puts '언어: #{name}' # 언어: #{name} (보간 없음)
puts '줄바꿈: \n두 번째 줄' # \n이 그대로 출력됨
문자열에서 자주 쓰는 메서드들입니다.
s = "Hello, Ruby World!"
puts s.length # 18 (문자 수)
puts s.upcase # HELLO, RUBY WORLD!
puts s.downcase # hello, ruby world!
puts s.reverse # !dlroW ybuR ,olleH
puts s.include?("Ruby") # true
puts s.start_with?("He") # true
puts s.end_with?("!") # true
puts s.gsub("Ruby", "Python") # Hello, Python World! (전체 치환)
puts s.sub("l", "L") # HeLlo, Ruby World! (첫 번째만 치환)
puts s.split(", ").inspect # ["Hello", "Ruby World!"]
puts " hi ".strip # "hi" (앞뒤 공백 제거)
puts s[0, 5] # Hello (0번 인덱스부터 5개)
4-4. 심볼 (Symbol)
심볼은 :이름 형태의 불변 식별자입니다. 같은 심볼은 프로그램 내에서 항상 동일한 객체를 가리키므로, 해시 키로 매우 자주 씁니다. 심볼은 문자열보다 메모리 효율이 좋습니다.
s = :hello
puts s.class # Symbol
puts s.to_s # "hello" (문자열 변환)
puts :hello.upcase # HELLO
# 심볼과 문자열 키 차이
h1 = { name: "Alice" } # 심볼 키 (권장)
h2 = { "name" => "Alice" } # 문자열 키
puts h1[:name] # Alice
puts h1["name"] # nil (심볼과 문자열은 다름!)
puts h2["name"] # Alice
4-5. 불리언 (true / false)과 nil
Ruby에서 false와 nil만 거짓으로 처리됩니다. 숫자 0, 빈 문자열 "", 빈 배열 []은 모두 참입니다. 이 점이 C/JavaScript와 다르니 주의하세요.
puts false ? "참" : "거짓" # 거짓
puts nil ? "참" : "거짓" # 거짓
puts 0 ? "참" : "거짓" # 참 ← 주의! (0은 참)
puts "" ? "참" : "거짓" # 참 ← 주의! (빈 문자열도 참)
puts [] ? "참" : "거짓" # 참 ← 주의! (빈 배열도 참)
puts nil.nil? # true
puts nil.to_s # "" (빈 문자열)
puts nil.to_a # [] (빈 배열)
puts nil.to_i # 0
4-6. 배열 (Array)
배열은 순서가 있는 값의 목록입니다. 여러 자료형을 섞어서 담을 수 있습니다.
fruits = ["사과", "바나나", "딸기"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", :ok, nil, true]
# 인덱스 접근 (0부터 시작)
puts fruits[0] # 사과
puts fruits[-1] # 딸기 (뒤에서 첫 번째)
puts fruits[1, 2].inspect # ["바나나", "딸기"] (인덱스 1부터 2개)
# 추가/삭제
fruits.push("포도") # 끝에 추가
fruits << "수박" # << 도 push와 같음
fruits.unshift("레몬") # 앞에 추가
puts fruits.pop # 수박 (끝 요소 꺼내서 반환)
puts fruits.shift # 레몬 (앞 요소 꺼내서 반환)
# 자주 쓰는 메서드
puts numbers.length # 5
puts numbers.sum # 15
puts numbers.min # 1
puts numbers.max # 5
puts numbers.sort.inspect # [1, 2, 3, 4, 5]
puts numbers.reverse.inspect# [5, 4, 3, 2, 1]
puts numbers.include?(3) # true
puts numbers.count { |n| n.even? } # 2 (짝수 개수)
# 집합 연산
a = [1, 2, 3, 4]
b = [3, 4, 5, 6]
puts (a + b).inspect # [1,2,3,4,3,4,5,6] (합치기)
puts (a - b).inspect # [1,2] (차집합)
puts (a & b).inspect # [3,4] (교집합)
puts (a | b).inspect # [1,2,3,4,5,6] (합집합, 중복 제거)
4-7. 해시 (Hash)
해시는 키-값 쌍의 집합입니다. Python의 dict, JavaScript의 Object와 비슷합니다.
user = { name: "Alice", age: 30, city: "Seoul" }
# 읽기
puts user[:name] # Alice
puts user.fetch(:city) # Seoul
puts user.fetch(:email, "없음") # 없음 (없을 때 기본값)
# 쓰기 및 삭제
user[:email] = "alice@example.com"
user[:age] = 31
user.delete(:city)
# 순회
user.each do |key, value|
puts "#{key}: #{value}"
end
# 자주 쓰는 메서드
puts user.keys.inspect # [:name, :age, :email]
puts user.values.inspect # ["Alice", 31, "alice@example.com"]
puts user.has_key?(:name) # true
puts user.length # 3
4-8. 범위 (Range)
연속된 값의 범위를 나타냅니다. ..는 끝값 포함, ...는 끝값 미포함입니다.
r1 = 1..5 # 1, 2, 3, 4, 5
r2 = 1...5 # 1, 2, 3, 4 (5 미포함)
r3 = 'a'..'e' # a, b, c, d, e
puts r1.include?(5) # true
puts r2.include?(5) # false
puts r1.to_a.inspect # [1, 2, 3, 4, 5]
puts r1.sum # 15
puts r3.to_a.inspect # ["a", "b", "c", "d", "e"]
5. 연산자
5-1. 산술 연산자
puts 10 + 3 # 13
puts 10 - 3 # 7
puts 10 * 3 # 30
puts 10 / 3 # 3 (정수끼리 나누면 소수점 버림)
puts 10.0 / 3 # 3.3333... (Float가 섞이면 Float 결과)
puts 10 % 3 # 1 (나머지)
puts 10 ** 3 # 1000 (거듭제곱)
# 복합 대입
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 **= 2 # x = x ** 2 → 36
x %= 10 # x = x % 10 → 6
5-2. 비교 연산자
puts 5 == 5 # true (값 동일)
puts 5 != 3 # true (값 다름)
puts 5 > 3 # true
puts 5 < 3 # false
puts 5 >= 5 # true
# 우주선 연산자: 크면 1, 같으면 0, 작으면 -1 반환
puts (5 <=> 3) # 1
puts (3 <=> 3) # 0
puts (1 <=> 3) # -1
5-3. 논리 연산자
puts true && false # false (AND)
puts true || false # true (OR)
puts !true # false (NOT)
# 단락 평가 (short-circuit)
# || : 왼쪽이 true면 오른쪽 평가 안 함 → nil 기본값 패턴에 자주 사용
name = nil
puts name || "익명" # 익명 (nil이 falsy라서 오른쪽 반환)
6. 조건문
6-1. if / elsif / else / end
if 문은 조건이 참일 때 블록을 실행합니다. end로 블록을 닫습니다.
score = 85
if score >= 90
puts "A 등급"
elsif score >= 80
puts "B 등급"
elsif score >= 70
puts "C 등급"
else
puts "재시험 대상"
end
# B 등급
6-2. 후위 if와 unless
한 줄짜리 조건은 뒤에 붙여 쓸 수 있습니다. unless는 if not과 같습니다.
age = 20
puts "성인입니다" if age >= 18
puts "미성년자입니다" unless age >= 18
6-3. 삼항 연산자
age = 20
status = age >= 18 ? "성인" : "미성년자"
puts status # 성인
6-4. case / when
여러 값을 비교할 때 if-elsif 대신 case를 씁니다. when 절에는 범위, 다중 값도 쓸 수 있습니다.
grade = "B"
case grade
when "A"
puts "최우수"
when "B", "C" # 여러 값 한번에
puts "우수"
when "D"
puts "보통"
else
puts "재시험"
end
# 우수
# 범위도 사용 가능
point = 75
case point
when 90..100 then puts "A"
when 80..89 then puts "B"
when 70..79 then puts "C"
else puts "F"
end
# C
7. 반복문
7-1. each
배열, 범위 등의 컬렉션을 순회할 때 가장 많이 씁니다.
fruits = ["사과", "바나나", "딸기"]
fruits.each do |fruit|
puts fruit
end
# 사과
# 바나나
# 딸기
# 인덱스가 필요하면 each_with_index
fruits.each_with_index do |fruit, i|
puts "#{i}: #{fruit}"
end
# 0: 사과
# 1: 바나나
# 2: 딸기
# 해시 순회
user = { name: "Alice", age: 30 }
user.each { |key, value| puts "#{key} = #{value}" }
7-2. times, upto, downto, step
# N회 반복
3.times do |i|
puts "반복 #{i}" # 0, 1, 2
end
1.upto(5) { |n| print "#{n} " } # 1 2 3 4 5
5.downto(1){ |n| print "#{n} " } # 5 4 3 2 1
(0..10).step(2) { |n| print "#{n} " } # 0 2 4 6 8 10
7-3. while과 until
i = 0
while i < 5
print "#{i} "
i += 1
end
# 0 1 2 3 4
j = 5
until j == 0
print "#{j} "
j -= 1
end
# 5 4 3 2 1
7-4. loop와 break, next
# loop는 무한 루프 — break로 탈출
count = 0
loop do
count += 1
next if count == 3 # 3은 출력 건너뜀
break if count > 5 # 5 초과 시 종료
puts count
end
# 1
# 2
# 4
# 5
8. 메서드 정의
메서드는 반복적으로 사용할 코드를 묶는 단위입니다. def 이름 ... end로 정의하며, 마지막 식의 값이 자동으로 반환됩니다.
8-1. 기본 메서드
def add(a, b)
a + b # 마지막 식의 값이 자동 반환
end
puts add(3, 4) # 7
# 명시적 return — 중간에 조기 반환할 때 유용
def divide(a, b)
return "0으로 나눌 수 없습니다" if b == 0
a.to_f / b
end
puts divide(10, 3) # 3.3333...
puts divide(10, 0) # 0으로 나눌 수 없습니다
8-2. 기본값 인자
def greet(name, prefix = "안녕하세요")
"#{prefix}, #{name}님!"
end
puts greet("Alice") # 안녕하세요, Alice님!
puts greet("Bob", "반갑습니다") # 반갑습니다, Bob님!
8-3. 가변 인자 (*args)
def sum(*numbers)
numbers.sum # numbers는 배열이 됩니다
end
puts sum(1, 2, 3) # 6
puts sum(10, 20, 30, 40) # 100
puts sum # 0
8-4. 키워드 인자
키워드 인자는 메서드를 호출할 때 이름을 명시해서 전달합니다. 인자 순서를 신경 쓰지 않아도 되고, 코드 가독성이 높아집니다.
def create_user(name:, age: 20, role: "user")
puts "이름: #{name}, 나이: #{age}, 권한: #{role}"
end
create_user(name: "Alice")
# 이름: Alice, 나이: 20, 권한: user
create_user(name: "Bob", age: 30, role: "admin")
# 이름: Bob, 나이: 30, 권한: admin
8-5. 메서드 이름 관례
# ?로 끝나는 메서드 — true/false 반환
puts [].empty? # true
puts "hello".include?("he") # true
# !로 끝나는 메서드 — 파괴적 메서드(원본을 직접 변경)
s = "hello"
puts s.upcase # HELLO (원본 유지, 새 문자열 반환)
puts s # hello
s.upcase! # 원본 변경
puts s # HELLO
9. 블록(Block)
블록은 메서드에 전달하는 코드 조각입니다. do...end 또는 중괄호 { } 두 가지 방식으로 씁니다. 여러 줄이면 do...end, 한 줄이면 { }를 쓰는 것이 관례입니다.
# 여러 줄 블록
[1, 2, 3].each do |n|
doubled = n * 2
puts "#{n} * 2 = #{doubled}"
end
# 한 줄 블록
[1, 2, 3].each { |n| puts n * 2 }
# yield로 블록 호출 — 메서드가 블록을 실행하는 방법
def execute
puts "메서드 시작"
yield # 전달된 블록을 여기서 실행
puts "메서드 끝"
end
execute { puts "블록 실행!" }
# 메서드 시작
# 블록 실행!
# 메서드 끝
# yield에 값 전달
def double_yield(n)
yield n
yield n * 2
end
double_yield(5) { |x| puts x }
# 5
# 10
# 블록이 전달됐는지 확인
def optional_block
if block_given?
yield 42
else
puts "블록 없음"
end
end
optional_block { |n| puts "값: #{n}" } # 값: 42
optional_block # 블록 없음
10. Proc과 Lambda
블록을 객체로 저장하면 변수에 담아 여러 번 재사용할 수 있습니다. Proc과 lambda 두 가지 방법이 있습니다.
10-1. Proc
double = Proc.new { |n| n * 2 }
puts double.call(5) # 10
puts double.call(10) # 20
puts double.(7) # 14 (짧은 호출 방법)
puts double[3] # 6 (또 다른 호출 방법)
# 배열에 Proc 적용
numbers = [1, 2, 3, 4, 5]
puts numbers.map(&double).inspect # [2, 4, 6, 8, 10]
10-2. Lambda
square = lambda { |n| n ** 2 }
cube = ->(n) { n ** 3 } # 화살표 문법 (동일)
puts square.call(4) # 16
puts cube.call(3) # 27
# 여러 인자
add = ->(a, b) { a + b }
puts add.call(3, 4) # 7
10-3. Proc vs Lambda 핵심 차이
# 1. 인자 수 검사
p1 = Proc.new { |x, y| "#{x}, #{y}" }
l1 = lambda { |x, y| "#{x}, #{y}" }
puts p1.call(1) # "1, " (인자 부족해도 nil로 채워서 실행)
# l1.call(1) # ArgumentError: wrong number of arguments (1 for 2)
# 2. return의 범위 차이
def test_proc
p_obj = Proc.new { return "proc 탈출" }
p_obj.call
"이 줄은 실행 안 됨" # Proc의 return은 메서드 전체에서 탈출
end
def test_lambda
l_obj = -> { return "lambda 종료" }
l_obj.call
"이 줄은 실행됩니다" # lambda의 return은 lambda 내부에서만 종료
end
puts test_proc # proc 탈출
puts test_lambda # 이 줄은 실행됩니다
11. 컬렉션 핵심 메서드
배열/해시를 다룰 때 매일 쓰는 메서드들을 정리합니다.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# map — 각 요소를 변환해 새 배열 반환
p numbers.map { |n| n * 10 } # [10, 20, 30, ...]
# select — 조건 참인 요소만 모아 새 배열 반환
p numbers.select { |n| n.even? } # [2, 4, 6, 8, 10]
# reject — 조건 거짓인 요소만 모아 새 배열 반환
p numbers.reject { |n| n > 5 } # [1, 2, 3, 4, 5]
# inject/reduce — 누적 계산
sum = numbers.inject(0) { |acc, n| acc + n }
p sum # 55
p numbers.inject(:+) # 55 (심볼로 연산자 전달)
# any? / all? / none?
p numbers.any? { |n| n > 9 } # true (하나라도 9 초과)
p numbers.all? { |n| n > 0 } # true (모두 0 초과)
p numbers.none? { |n| n > 10 } # true (10 초과 없음)
p numbers.count { |n| n.odd? } # 5 (홀수 개수)
# find/detect — 첫 번째 매칭 요소
p numbers.find { |n| n > 6 } # 7
# flat_map — map 후 1단계 flatten
p [[1,2],[3,4]].flat_map { |a| a } # [1, 2, 3, 4]
# group_by — 조건별로 그룹화
p numbers.group_by { |n| n.even? ? :even : :odd }
# {:odd=>[1,3,5,7,9], :even=>[2,4,6,8,10]}
12. 클래스와 객체지향
클래스는 객체의 설계도입니다. new로 인스턴스(객체)를 만들고, 메서드로 동작을 정의합니다.
class Person
attr_reader :name # getter만 생성
attr_writer :email # setter만 생성
attr_accessor :age # getter + setter 모두 생성
def initialize(name, age, email)
@name = name
@age = age
@email = email
end
def introduce
"안녕하세요! 저는 #{@name}이고, #{@age}살입니다."
end
def to_s
"Person(#{@name}, #{@age})"
end
end
p1 = Person.new("Alice", 30, "alice@example.com")
puts p1.name # Alice (attr_reader)
puts p1.age # 30 (attr_accessor의 getter)
p1.age = 31 # attr_accessor의 setter
puts p1.age # 31
p1.email = "new@example.com" # attr_writer
puts p1.introduce
puts p1 # Person(Alice, 31) ← to_s 사용됨
12-1. 클래스 메서드와 클래스 변수
class BankAccount
@@total_accounts = 0 # 클래스 변수
INTEREST_RATE = 0.05 # 상수
attr_reader :balance, :owner
def initialize(owner, balance = 0)
@owner = owner
@balance = balance
@@total_accounts += 1
end
def deposit(amount)
@balance += amount
puts "#{amount}원 입금 → 잔액: #{@balance}원"
end
def withdraw(amount)
if amount > @balance
puts "잔액 부족"
else
@balance -= amount
puts "#{amount}원 출금 → 잔액: #{@balance}원"
end
end
def self.total_accounts # 클래스 메서드 (self. 접두사)
@@total_accounts
end
end
acc1 = BankAccount.new("Alice", 10000)
acc2 = BankAccount.new("Bob")
acc1.deposit(5000) # 5000원 입금 → 잔액: 15000원
acc1.withdraw(3000) # 3000원 출금 → 잔액: 12000원
puts BankAccount.total_accounts # 2
13. 상속 (Inheritance)
class Child < Parent 문법으로 부모 클래스의 속성과 메서드를 물려받습니다. 공통 코드는 부모에 두고, 자식에서 특화된 동작만 추가하거나 오버라이드합니다.
class Animal
attr_reader :name
def initialize(name)
@name = name
end
def eat
puts "#{@name}이(가) 먹이를 먹습니다."
end
def speak
puts "..."
end
end
class Dog < Animal
def speak
puts "#{@name}: 멍멍!" # 부모 메서드 오버라이드
end
def fetch
puts "#{@name}이(가) 공을 물어옵니다!"
end
end
class Cat < Animal
def initialize(name, indoor)
super(name) # 부모 initialize 호출
@indoor = indoor
end
def speak
puts "#{@name}: 야옹~"
end
def info
"#{@name} (#{@indoor ? '실내' : '실외'})"
end
end
dog = Dog.new("초코")
cat = Cat.new("나비", true)
dog.eat # 초코이(가) 먹이를 먹습니다.
dog.speak # 초코: 멍멍!
dog.fetch # 초코이(가) 공을 물어옵니다!
cat.speak # 나비: 야옹~
puts cat.info # 나비 (실내)
puts dog.is_a?(Dog) # true
puts dog.is_a?(Animal) # true (상속 관계 확인)
14. 모듈 (Module)
모듈은 메서드를 묶는 네임스페이스이자, 클래스에 기능을 추가하는 믹스인(Mixin)입니다. Ruby는 단일 상속만 지원하지만, 모듈을 include하면 여러 기능을 자유롭게 조합할 수 있습니다.
module Greetable
def greet
puts "안녕하세요, 저는 #{name}입니다!"
end
end
module Farewell
def bye
puts "#{name}이(가) 작별 인사를 합니다."
end
end
class User
include Greetable
include Farewell
attr_reader :name
def initialize(name)
@name = name
end
end
u = User.new("Alice")
u.greet # 안녕하세요, 저는 Alice입니다!
u.bye # Alice이(가) 작별 인사를 합니다.
14-1. extend — 클래스 메서드로 추가
module ClassUtils
def description
"나는 #{self.name} 클래스입니다"
end
end
class Product
extend ClassUtils
end
puts Product.description # 나는 Product 클래스입니다
15. 예외 처리
예외는 프로그램 실행 중 발생하는 오류입니다. 예외를 처리하지 않으면 프로그램이 종료됩니다. begin/rescue/ensure/end로 안전하게 처리합니다.
begin
num = Integer("abc") # 숫자가 아닌 문자열 변환 시도 → 예외 발생
puts num
rescue ArgumentError => e
puts "입력 오류: #{e.message}"
rescue ZeroDivisionError => e
puts "0 나눗셈: #{e.message}"
rescue => e
puts "기타 오류: #{e.message}"
ensure
puts "항상 실행됩니다 (파일 닫기, 연결 해제 등)"
end
# 입력 오류: invalid value for Integer(): "abc"
# 항상 실행됩니다
15-1. raise로 예외 직접 발생
def withdraw(amount, balance)
raise ArgumentError, "금액은 0보다 커야 합니다" if amount <= 0
raise "잔액 부족" if amount > balance
balance - amount
end
begin
puts withdraw(0, 1000)
rescue ArgumentError => e
puts "입력 오류: #{e.message}"
end
# 입력 오류: 금액은 0보다 커야 합니다
15-2. 커스텀 예외 클래스
class InsufficientFundsError < StandardError
def initialize(amount)
super("잔액이 #{amount}원 부족합니다")
end
end
begin
raise InsufficientFundsError.new(3000)
rescue InsufficientFundsError => e
puts e.message # 잔액이 3000원 부족합니다
end
16. 파일 입출력
# 파일 쓰기 (기존 내용 덮어씀)
File.write("log.txt", "첫 번째 줄\n")
# 파일에 추가 (append 모드)
File.open("log.txt", "a") do |f|
f.puts "두 번째 줄"
f.puts "세 번째 줄"
end # 블록이 끝나면 자동으로 파일 닫힘
# 파일 전체 읽기
content = File.read("log.txt")
puts content
# 줄 단위로 읽기 (대용량 파일에 효율적)
File.foreach("log.txt") do |line|
puts line.chomp # chomp로 끝의 개행 제거
end
# 파일 존재 여부 확인
puts "파일이 있습니다" if File.exist?("log.txt")
17. 여러 줄 문자열 (Here Document)
# <<~TEXT 는 들여쓰기를 자동으로 제거해줘서 코드 정렬이 깔끔합니다
name = "Ruby"
welcome = <<~TEXT
#{name}에 오신 것을 환영합니다!
오늘도 즐거운 코딩 되세요.
TEXT
puts welcome
18. 자주 쓰는 편리한 문법
18-1. 안전 탐색 연산자 (&.)
# nil이면 NoMethodError 대신 nil을 반환 — 방어 코드를 줄여줍니다
user = nil
puts user&.name # nil (에러 없음)
str = "hello"
puts str&.upcase # HELLO
18-2. 다중 할당과 splat
a, b, c = 1, 2, 3
puts "#{a}, #{b}, #{c}" # 1, 2, 3
# 값 교환 (temp 변수 불필요)
a, b = b, a
puts "#{a}, #{b}" # 2, 1
# splat 연산자
first, *rest = [1, 2, 3, 4, 5]
puts first # 1
puts rest.inspect # [2, 3, 4, 5]
18-3. %w와 %i로 간편 배열 작성
# 문자열 배열 — 따옴표 없이 공백으로 구분
fruits = %w[사과 바나나 딸기 포도]
p fruits # ["사과", "바나나", "딸기", "포도"]
# 심볼 배열
keys = %i[name age email]
p keys # [:name, :age, :email]
18-4. tap — 체인 중 디버깅
result = [1, 2, 3]
.tap { |a| puts "원본: #{a.inspect}" }
.map { |n| n * 2 }
.tap { |a| puts "변환 후: #{a.inspect}" }
.select { |n| n > 3 }
puts result.inspect
# 원본: [1, 2, 3]
# 변환 후: [2, 4, 6]
# [4, 6]
19. 파일 분리와 require
# user.rb
class User
attr_reader :name
def initialize(name)
@name = name
end
end
# main.rb
require_relative "user" # 현재 파일 기준 상대 경로 로드
u = User.new("Alice")
puts u.name # Alice
20. 실전 미니 프로젝트: 간단한 할 일 관리 CLI
지금까지 배운 문법을 모두 활용한 작은 예제입니다. 직접 실행해보세요.
class TodoApp
def initialize
@todos = []
end
def add(title)
@todos << { id: @todos.length + 1, title: title, done: false }
puts "추가됨: #{title}"
end
def complete(id)
todo = @todos.find { |t| t[:id] == id }
if todo
todo[:done] = true
puts "완료됨: #{todo[:title]}"
else
puts "해당 ID의 할 일이 없습니다"
end
end
def list
if @todos.empty?
puts "할 일이 없습니다."
return
end
@todos.each do |t|
status = t[:done] ? "[완료]" : "[미완]"
puts "#{t[:id]}. #{status} #{t[:title]}"
end
end
end
app = TodoApp.new
app.add("Ruby 기본 문법 공부하기")
app.add("블로그 글 읽기")
app.add("예제 코드 직접 실행하기")
app.list
app.complete(1)
app.list
# 출력 예시
추가됨: Ruby 기본 문법 공부하기
추가됨: 블로그 글 읽기
추가됨: 예제 코드 직접 실행하기
1. [미완] Ruby 기본 문법 공부하기
2. [미완] 블로그 글 읽기
3. [미완] 예제 코드 직접 실행하기
완료됨: Ruby 기본 문법 공부하기
1. [완료] Ruby 기본 문법 공부하기
2. [미완] 블로그 글 읽기
3. [미완] 예제 코드 직접 실행하기
마무리: Ruby 학습 로드맵
이 글에서 다룬 내용을 기반으로, 다음 단계로 진행하면 Ruby 실력을 빠르게 키울 수 있습니다.
- 이 글의 예제 직접 실행 — 모든 코드를 손으로 타이핑하며 확인하세요.
- Ruby Silver 자격증 도전 — 문법 이해도를 공식으로 검증합니다.
- 간단한 CLI 프로그램 만들기 — To-do 앱, 계산기 등 작은 프로젝트를 완성해보세요.
- Ruby on Rails 입문 — 웹 앱 개발로 실전 활용 영역을 확장합니다.
- Gem 사용법 습득 —
Bundler와Gemfile로 외부 라이브러리를 활용합니다.
Ruby의 철학은 "코드를 읽는 것이 즐거워야 한다"입니다. 이 글의 예제들을 직접 실행하며 한 줄씩 이해하다 보면, Ruby가 다른 언어보다 훨씬 직관적이고 유쾌한 언어라는 것을 느끼게 됩니다.