Ruby

#16
TIOBE#24
PYPL#17
GitHub#18
RedMonk#9
IEEESpectrum#18
JetBrains#18
プログラミング言語オブジェクト指向スクリプト言語Web開発Ruby on Rails動的型付け

プログラミング言語

Ruby

概要

Rubyは日本で開発されたオブジェクト指向スクリプト言語です。

詳細

Rubyは1995年にまつもとゆきひろ(Matz)によって日本で開発されたオブジェクト指向スクリプト言語です。「プログラマーの幸福」を重視した設計思想のもと、可読性の高い自然な構文と強力な表現力を持っています。「全てがオブジェクト」という一貫した設計により、柔軟で直感的なプログラミングが可能です。2004年にDavid Heinemeier HanssonがRuby on Railsフレームワークを発表したことで、Web開発分野で爆発的な人気を獲得しました。Convention over Configuration(設定より規約)の原則により、迅速な開発が可能で、GitHub、Shopify、Airbnb、Twitter(初期)などの有名サービスで採用されています。現在も活発なコミュニティによって継続的に発展しています。

書き方の例

Hello World

# 基本的な出力
puts "Hello, World!"

# 複数の出力方法
print "Hello, "
print "Ruby!\n"

# 変数を使った出力
message = "こんにちは、Ruby!"
puts message

# 文字列の展開(補間)
name = "太郎"
age = 25
puts "私の名前は#{name}で、#{age}歳です。"

# 複数行の文字列
multiline = <<~TEXT
  これは複数行の
  文字列です。
  Ruby らしい書き方ですね。
TEXT
puts multiline

# pメソッド(デバッグ用)
p "これはpメソッドの出力です"  # 引用符も含めて表示

# printfスタイル
printf "私の名前は%sで、%d歳です。\n", name, age

変数とデータ型

# 数値
number = 42
float_number = 3.14159
big_number = 123_456_789  # アンダースコアで桁区切り
binary = 0b1010           # 二進数
octal = 0o755            # 八進数
hex = 0xFF               # 十六進数

# 文字列
single_quoted = 'シングルクォート文字列'
double_quoted = "ダブルクォート文字列"
name = "田中"
interpolated = "こんにちは、#{name}さん!"  # 文字列展開

# シンボル
symbol = :hello
status = :active
http_method = :get

# 配列
fruits = ["りんご", "バナナ", "オレンジ"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "文字列", :シンボル, true]

# 文字列の配列を簡単に作成
words = %w[red green blue yellow]  # ["red", "green", "blue", "yellow"]
symbols = %i[start pause stop]     # [:start, :pause, :stop]

# ハッシュ(連想配列)
person = {
  "name" => "山田太郎",
  "age" => 30,
  "city" => "東京"
}

# シンボルをキーとするハッシュ(モダンな書き方)
person_modern = {
  name: "佐藤花子",
  age: 25,
  city: "大阪"
}

# 範囲
range1 = 1..10      # 1から10まで(10を含む)
range2 = 1...10     # 1から10まで(10を含まない)
alphabet = 'a'..'z' # aからzまで

# 真偽値
is_active = true
is_complete = false
nothing = nil       # Rubyのnull値

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

# 変数の型を確認
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

# 型の変換
str_number = "123"
puts str_number.to_i   # 文字列を整数に
puts number.to_s       # 整数を文字列に
puts number.to_f       # 整数を浮動小数点数に
puts fruits.join(", ") # 配列を文字列に

# 定数
TAX_RATE = 0.1
COMPANY_NAME = "ABC株式会社"

puts "数値: #{number}"
puts "文字列: #{double_quoted}"
puts "展開された文字列: #{interpolated}"
puts "配列: #{fruits}"
puts "ハッシュ: #{person}"
puts "範囲に5は含まれる: #{range1.include?(5)}"
puts "メールのマッチ: #{email.match?(pattern)}"

メソッドとブロック

# 基本的なメソッド定義
def add(a, b)
  a + b  # Rubyは最後の式の結果を自動的に返す
end

# 明示的なreturn
def subtract(a, b)
  return a - b
end

# デフォルト引数
def greet(name, greeting = "こんにちは")
  "#{greeting}#{name}さん!"
end

# キーワード引数
def create_user(name:, age:, email: nil)
  user = {
    name: name,
    age: age,
    email: email
  }
  puts "ユーザー作成: #{user}"
  user
end

# 可変長引数
def sum(*numbers)
  numbers.sum
end

# キーワード引数の可変長
def log_info(message, **options)
  puts "メッセージ: #{message}"
  puts "オプション: #{options}" unless options.empty?
end

# ブロック付きメソッド
def repeat_task(times)
  times.times do |i|
    yield(i + 1) if block_given?
  end
end

# ブロックを受け取るメソッド
def process_data(data, &block)
  data.map(&block)
end

# メソッド呼び出し例
puts add(5, 3)
puts subtract(10, 4)
puts greet("田中")
puts greet("山田", "おはよう")

create_user(name: "佐藤", age: 30)
create_user(name: "高橋", age: 25, email: "[email protected]")

puts sum(1, 2, 3, 4, 5)
log_info("処理開始", level: :info, timestamp: Time.now)

# ブロックの使用例
repeat_task(3) do |count|
  puts "タスク #{count} を実行中..."
end

# 配列のメソッド
numbers = [1, 2, 3, 4, 5]

# each(各要素に対して処理)
numbers.each { |n| puts "数値: #{n}" }

# map(変換)
doubled = numbers.map { |n| n * 2 }
puts "倍数: #{doubled}"

# select(フィルタリング)
evens = numbers.select { |n| n.even? }
puts "偶数: #{evens}"

# reject(除外)
odds = numbers.reject { |n| n.even? }
puts "奇数: #{odds}"

# reduce(集約)
total = numbers.reduce(0) { |sum, n| sum + n }
puts "合計: #{total}"

# 短縮記法
sum_short = numbers.sum
puts "合計(短縮): #{sum_short}"

# ハッシュのメソッド
person = { name: "田中", age: 30, city: "東京" }

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

# メソッドチェーン
result = numbers
  .select { |n| n.odd? }
  .map { |n| n ** 2 }
  .sum

puts "奇数の二乗の合計: #{result}"

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

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

# ブロックをprocに変換
def use_block(&block)
  block.call("Hello from proc!")
end

use_block { |msg| puts msg }

クラスとオブジェクト

# 基本的なクラス
class Person
  # アクセサメソッドの自動生成
  attr_reader :name, :age      # 読み取り専用
  attr_writer :email          # 書き込み専用
  attr_accessor :phone        # 読み書き両方

  # クラス変数
  @@population = 0

  # 定数
  SPECIES = "Homo sapiens"

  # 初期化メソッド(コンストラクタ)
  def initialize(name, age)
    @name = name    # インスタンス変数
    @age = age
    @id = generate_id
    @@population += 1
  end

  # インスタンスメソッド
  def introduce
    "私の名前は#{@name}で、#{@age}歳です。"
  end

  def birthday
    @age += 1
    puts "#{@name}さん、#{@age}歳のお誕生日おめでとう!"
  end

  # クラスメソッド
  def self.population
    @@population
  end

  def self.create_anonymous
    new("名無し", 0)
  end

  # プライベートメソッド
  private

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

# 継承
class Student < Person
  attr_accessor :school, :grade

  def initialize(name, age, school)
    super(name, age)  # 親クラスのinitializeを呼び出し
    @school = school
    @grade = "未設定"
  end

  def introduce
    super + " #{@school}の学生です。"
  end

  def study(subject)
    puts "#{@name}#{subject}を勉強しています。"
  end
end

# モジュール(ミックスイン)
module Greetable
  def say_hello
    "Hello! I'm #{name}."
  end

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

# モジュールの利用
class Employee < Person
  include Greetable  # ミックスイン

  attr_accessor :company, :position

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

  def work
    puts "#{@name}#{@company}#{@position}として働いています。"
  end
end

# 使用例
puts "=== クラスの使用例 ==="

# インスタンスの作成
person1 = Person.new("田中太郎", 25)
person2 = Person.new("山田花子", 30)

puts person1.introduce
puts person2.introduce

# アクセサの使用
person1.phone = "090-1234-5678"
puts "電話番号: #{person1.phone}"

# 誕生日
person1.birthday

# クラスメソッドの呼び出し
puts "総人口: #{Person.population}"

anonymous = Person.create_anonymous
puts anonymous.introduce

# 継承の例
student = Student.new("佐藤学", 20, "東京大学")
puts student.introduce
student.study("数学")

# ミックスインの例
employee = Employee.new("高橋一郎", 35, "ABC株式会社", "エンジニア")
puts employee.introduce
employee.work
puts employee.say_hello
puts employee.say_goodbye

puts "総人口: #{Person.population}"

# オブジェクトの検査
puts "\n=== オブジェクトの検査 ==="
puts "person1のクラス: #{person1.class}"
puts "studentのクラス: #{student.class}"
puts "studentはPersonか: #{student.is_a?(Person)}"
puts "studentはStudentか: #{student.is_a?(Student)}"
puts "employeeはGreetableを含むか: #{employee.class.include?(Greetable)}"

# メタプログラミングの例
puts "\n=== メタプログラミング ==="

# メソッドの動的定義
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)

# method_missingの活用
class FlexibleClass
  def initialize
    @data = {}
  end

  def method_missing(method_name, *args)
    if method_name.to_s.end_with?('=')
      # セッター
      attribute = method_name.to_s.chop
      @data[attribute] = args.first
    else
      # ゲッター
      @data[method_name.to_s]
    end
  end

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

flexible = FlexibleClass.new
flexible.name = "動的オブジェクト"
flexible.value = 42
puts "名前: #{flexible.name}"
puts "値: #{flexible.value}"

例外処理とエラーハンドリング

# カスタム例外クラス
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

# 例外をスローするメソッド
def validate_age(age)
  raise AgeError.new("年齢は0以上である必要があります", :age) if age < 0
  raise AgeError.new("年齢は150以下である必要があります", :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("無効なメールアドレスです: #{email}", :email)
  end
  true
end

def divide(a, b)
  raise ZeroDivisionError, "ゼロで除算はできません" if b == 0
  a.to_f / b
end

# 例外処理の例
puts "=== 例外処理 ==="

# begin-rescue-end
begin
  result = divide(10, 2)
  puts "10 ÷ 2 = #{result}"
rescue ZeroDivisionError => e
  puts "数学エラー: #{e.message}"
end

# 複数の例外をキャッチ
begin
  validate_age(-5)
rescue AgeError => e
  puts "年齢エラー: #{e.message} (フィールド: #{e.field})"
rescue ValidationError => e
  puts "検証エラー: #{e.message}"
rescue StandardError => e
  puts "予期しないエラー: #{e.message}"
end

# ensure節(必ず実行される)
begin
  puts "処理を開始します"
  validate_email("invalid-email")
rescue EmailError => e
  puts "メールエラー: #{e.message}"
ensure
  puts "クリーンアップ処理(必ず実行される)"
end

# else節(例外が発生しなかった場合)
begin
  validate_age(25)
rescue AgeError => e
  puts "年齢エラー: #{e.message}"
else
  puts "年齢の検証に成功しました"
ensure
  puts "検証処理完了"
end

# retry(リトライ)
attempt = 0
begin
  attempt += 1
  puts "試行 #{attempt}"
  
  if attempt < 3
    raise "一時的なエラー"
  end
  
  puts "処理成功!"
rescue => e
  puts "エラー: #{e.message}"
  if attempt < 3
    puts "リトライします..."
    retry
  else
    puts "最大試行回数に達しました"
  end
end

# raise(例外の再発生)
def process_data(data)
  validate_age(data[:age])
rescue AgeError => e
  puts "データ処理中にエラー: #{e.message}"
  raise  # 例外を再発生
end

begin
  process_data(age: -10)
rescue AgeError
  puts "呼び出し元でエラーをキャッチしました"
end

# カスタム例外の活用
class UserService
  def create_user(name, age, email)
    validate_age(age)
    validate_email(email)
    
    # ユーザー作成処理
    user = { name: name, age: age, email: email, id: rand(1000) }
    puts "ユーザー作成成功: #{user}"
    user
  rescue ValidationError => e
    puts "ユーザー作成失敗: #{e.message}"
    nil
  end
end

service = UserService.new
service.create_user("田中", 25, "[email protected]")
service.create_user("山田", -5, "[email protected]")
service.create_user("佐藤", 30, "invalid-email")

# throw/catch(例外ではない非局所脱出)
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  # 見つからなかった場合
  end
end

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

ファイル操作とI/O

# ファイルの読み書き
puts "=== ファイル操作 ==="

# ファイルに書き込み
File.open("sample.txt", "w") do |file|
  file.puts "これはサンプルファイルです。"
  file.puts "Ruby でファイル操作をしています。"
  file.puts "ブロックを使うと自動的にファイルがクローズされます。"
end

# ファイルから読み込み
content = File.read("sample.txt")
puts "ファイル内容:"
puts content

# 一行ずつ読み込み
puts "\n一行ずつ読み込み:"
File.foreach("sample.txt") do |line|
  puts "行: #{line.strip}"
end

# 配列として読み込み
lines = File.readlines("sample.txt")
puts "\n配列として読み込み: #{lines.map(&:strip)}"

# ファイルの追記
File.open("sample.txt", "a") do |file|
  file.puts "追記された行です。"
end

# CSVファイルの操作
require 'csv'

# CSVデータの作成
csv_data = [
  ["名前", "年齢", "職業"],
  ["田中太郎", 30, "エンジニア"],
  ["山田花子", 25, "デザイナー"],
  ["佐藤次郎", 35, "マネージャー"]
]

# CSVファイルに書き込み
CSV.open("people.csv", "w") do |csv|
  csv_data.each { |row| csv << row }
end

# CSVファイルから読み込み
puts "\nCSVファイルの内容:"
CSV.foreach("people.csv", headers: true) do |row|
  puts "#{row['名前']} (#{row['年齢']}歳) - #{row['職業']}"
end

# JSONファイルの操作
require 'json'

# ハッシュをJSONに変換
user_data = {
  name: "鈴木一郎",
  age: 28,
  hobbies: ["読書", "映画鑑賞", "プログラミング"],
  address: {
    city: "東京",
    prefecture: "東京都"
  }
}

# JSONファイルに書き込み
File.open("user.json", "w") do |file|
  file.write(JSON.pretty_generate(user_data))
end

# JSONファイルから読み込み
loaded_user = JSON.parse(File.read("user.json"), symbolize_names: true)
puts "\nJSONから読み込んだデータ:"
puts "名前: #{loaded_user[:name]}"
puts "趣味: #{loaded_user[:hobbies].join(', ')}"

# ディレクトリ操作
puts "\n=== ディレクトリ操作 ==="

# 現在のディレクトリ
puts "現在のディレクトリ: #{Dir.pwd}"

# ディレクトリの作成
Dir.mkdir("test_dir") unless Dir.exist?("test_dir")

# ファイル一覧の取得
files = Dir.glob("*.txt")
puts "txtファイル: #{files}"

# ディレクトリ内のファイルを処理
Dir.foreach(".") do |file|
  next if file.start_with?(".")
  puts "ファイル: #{file} (サイズ: #{File.size(file)} bytes)" if File.file?(file)
end

# ファイル情報
if File.exist?("sample.txt")
  stat = File.stat("sample.txt")
  puts "\nsample.txt の情報:"
  puts "サイズ: #{stat.size} bytes"
  puts "最終更新: #{stat.mtime}"
  puts "権限: #{sprintf('%o', stat.mode)}"
end

# パスの操作
require 'pathname'

path = Pathname.new("test_dir/subdir/file.txt")
puts "\nパス操作:"
puts "ディレクトリ名: #{path.dirname}"
puts "ファイル名: #{path.basename}"
puts "拡張子: #{path.extname}"
puts "拡張子なしのファイル名: #{path.basename(path.extname)}"

# 一時ファイル
require 'tempfile'

Tempfile.create("ruby_temp") do |temp|
  temp.write("一時的なデータ")
  temp.rewind
  puts "\n一時ファイルの内容: #{temp.read}"
  puts "一時ファイルのパス: #{temp.path}"
end

# クリーンアップ
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 "\nファイル操作の例が完了しました。"

バージョン

バージョン リリース日 主な新機能
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

参考ページ

公式ドキュメント

学習リソース

フレームワーク・ツール

  • Ruby on Rails - Webアプリケーションフレームワーク
  • Sinatra - 軽量Webフレームワーク
  • RSpec - テスティングフレームワーク
  • Bundler - 依存関係管理ツール