💎

【Ruby 47日目】オブジェクト指向 - selfキーワード

に公開

はじめに

Rubyのselfキーワードについて、Ruby 3.4の仕様に基づいて詳しく解説します。

この記事では、基本的な概念から実践的な使い方まで、具体的なコード例を交えて説明します。

基本概念

selfは、現在のレシーバ(オブジェクト)を参照する特別なキーワードです:

  • 現在のオブジェクトへの参照 - メソッド内でレシーバとなっているオブジェクトを指す
  • コンテキストによって変わる - クラス定義内、インスタンスメソッド内で異なる
  • メソッド呼び出し - selfを省略できる場合とできない場合がある
  • クラスメソッド定義 - def self.method_name での self はクラス自身

selfを正しく理解することで、Rubyのオブジェクトモデルの理解が深まります。

基本的な使い方

インスタンスメソッド内のself

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    # selfは現在のインスタンスを指す
    puts "I am #{self.name}, #{self.age} years old"
    puts "My class is #{self.class}"
    puts "My object_id is #{self.object_id}"
  end

  def who_am_i
    self  # インスタンス自身を返す
  end
end

person = Person.new("Alice", 25)
person.introduce
#=> I am Alice, 25 years old
#   My class is Person
#   My object_id is ...

puts person.who_am_i == person  #=> true

selfを使ったメソッド呼び出し

class Calculator
  def initialize(value)
    @value = value
  end

  def add(n)
    @value += n
    self  # selfを返すことでメソッドチェーン可能に
  end

  def multiply(n)
    @value *= n
    self
  end

  def result
    @value
  end
end

calc = Calculator.new(10)
result = calc.add(5).multiply(2).result
puts result  #=> 30

クラス定義内のself

class Example
  # クラス定義のトップレベルでselfはクラス自身
  puts "In class body, self is: #{self}"  #=> Example

  def self.class_method
    # クラスメソッド内でもselfはクラス自身
    puts "In class method, self is: #{self}"  #=> Example
  end

  def instance_method
    # インスタンスメソッド内でselfはインスタンス
    puts "In instance method, self is: #{self.inspect}"
  end
end

Example.class_method  #=> In class method, self is: Example

obj = Example.new
obj.instance_method  #=> In instance method, self is: #<Example:0x...>

setterメソッド呼び出しでのself

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def update_info(name, age)
    # setterメソッドを呼ぶにはselfが必要
    self.name = name  # self.を付けないとローカル変数になる
    self.age = age
  end

  def bad_update(name)
    # BAD: ローカル変数への代入になってしまう
    name = name
    # @nameは変更されない
  end
end

person = Person.new("Alice", 25)
person.update_info("Bob", 30)

puts person.name  #=> Bob
puts person.age   #=> 30

class << self でのself

class MyClass
  # class << self ブロック内でselfは特異クラス
  class << self
    def who_am_i
      puts "self is: #{self}"  #=> MyClass
      puts "self.class is: #{self.class}"  #=> Class
    end

    def create_instance
      # ここでのselfはMyClassなので、newで新しいインスタンスを作成
      self.new
    end
  end
end

MyClass.who_am_i
#=> self is: MyClass
#   self.class is: Class

instance = MyClass.create_instance

よくあるユースケース

ケース1: メソッドチェーンの実装

selfを返すことでメソッドチェーンを可能にします。

class QueryBuilder
  def initialize
    @conditions = []
    @order = nil
    @limit = nil
  end

  def where(condition)
    @conditions << condition
    self  # selfを返す
  end

  def order_by(column)
    @order = column
    self  # selfを返す
  end

  def limit(count)
    @limit = count
    self  # selfを返す
  end

  def to_sql
    sql = "SELECT * FROM users"

    unless @conditions.empty?
      sql += " WHERE " + @conditions.join(" AND ")
    end

    sql += " ORDER BY #{@order}" if @order
    sql += " LIMIT #{@limit}" if @limit

    sql
  end
end

query = QueryBuilder.new
  .where("age > 18")
  .where("active = true")
  .order_by("created_at DESC")
  .limit(10)

puts query.to_sql
#=> SELECT * FROM users WHERE age > 18 AND active = true ORDER BY created_at DESC LIMIT 10

ケース2: ビルダーパターン

オブジェクトを段階的に構築します。

class User
  attr_reader :name, :email, :age, :role

  def initialize
    @name = nil
    @email = nil
    @age = nil
    @role = :member
  end

  def name=(value)
    @name = value
    self
  end

  def email=(value)
    @email = value
    self
  end

  def age=(value)
    @age = value
    self
  end

  def role=(value)
    @role = value
    self
  end

  def build
    raise "Name is required" if @name.nil?
    raise "Email is required" if @email.nil?
    self
  end

  def to_s
    "User: #{@name} (#{@email}), age #{@age}, role: #{@role}"
  end
end

user = User.new
  .name=("Alice")
  .email=("alice@example.com")
  .age=(25)
  .role=(:admin)
  .build

puts user
#=> User: Alice (alice@example.com), age 25, role: admin

ケース3: 委譲パターン

selfを使って他のオブジェクトに処理を委譲します。

class Logger
  def initialize
    @output = $stdout
  end

  def log(message)
    @output.puts("[#{Time.now}] #{message}")
  end
end

class Application
  def initialize
    @logger = Logger.new
  end

  def start
    log("Application starting...")
    # アプリケーションの起動処理
    log("Application started successfully")
  end

  private

  def log(message)
    # selfのメソッドとして呼べるように委譲
    @logger.log(message)
  end
end

app = Application.new
app.start
#=> [2025-11-23 10:00:00] Application starting...
#   [2025-11-23 10:00:00] Application started successfully

ケース4: シングルトンパターン

selfを使ってシングルトンを実装します。

class Configuration
  @instance = nil

  private_class_method :new

  def self.instance
    @instance ||= new
  end

  def initialize
    @settings = {}
  end

  def set(key, value)
    @settings[key] = value
    self
  end

  def get(key)
    @settings[key]
  end

  def configure
    yield self if block_given?
    self
  end
end

# シングルトンインスタンスの取得
config = Configuration.instance

config.configure do |c|
  c.set(:app_name, "MyApp")
  c.set(:version, "1.0.0")
  c.set(:debug, true)
end

# 同じインスタンスが返される
config2 = Configuration.instance
puts config == config2  #=> true

puts config.get(:app_name)  #=> MyApp

ケース5: DSLの実装

instance_evalを使ってDSLを作成します。

class TaskList
  def initialize
    @tasks = []
  end

  def task(name, &block)
    task_obj = Task.new(name)
    task_obj.instance_eval(&block) if block_given?
    @tasks << task_obj
  end

  def execute_all
    @tasks.each(&:execute)
  end
end

class Task
  attr_reader :name

  def initialize(name)
    @name = name
    @description = ""
    @priority = :normal
    @steps = []
  end

  def description(text)
    @description = text
    self
  end

  def priority(level)
    @priority = level
    self
  end

  def step(name, &block)
    @steps << { name: name, block: block }
    self
  end

  def execute
    puts "Executing task: #{@name}"
    puts "  Description: #{@description}"
    puts "  Priority: #{@priority}"

    @steps.each do |step|
      puts "  Step: #{step[:name]}"
      step[:block].call if step[:block]
    end
  end
end

# DSLを使用
tasks = TaskList.new

tasks.task("Deploy Application") do
  description "Deploy the application to production"
  priority :high

  step("Build") do
    puts "    Building application..."
  end

  step("Test") do
    puts "    Running tests..."
  end

  step("Deploy") do
    puts "    Deploying to production..."
  end
end

tasks.execute_all
#=> Executing task: Deploy Application
#     Description: Deploy the application to production
#     Priority: high
#     Step: Build
#       Building application...
#     Step: Test
#       Running tests...
#     Step: Deploy
#       Deploying to production...

注意点とベストプラクティス

注意点

  1. setterメソッド呼び出しではselfが必要
class Person
  attr_accessor :name

  def update_name(new_name)
    # BAD: ローカル変数への代入
    name = new_name
    # @nameは変更されない
  end

  def update_name_correctly(new_name)
    # GOOD: selfを使ってsetterを呼ぶ
    self.name = new_name
  end
end

person = Person.new
person.update_name("Bob")
puts person.name.inspect  #=> nil(変更されていない)

person.update_name_correctly("Alice")
puts person.name  #=> Alice
  1. privateメソッドの呼び出し
class Example
  def public_method
    # BAD: Ruby 2.7以前ではエラー
    # self.private_method

    # GOOD: selfなしで呼ぶ
    private_method
  end

  private

  def private_method
    "This is private"
  end
end

obj = Example.new
puts obj.public_method  #=> This is private
  1. selfの値の変化に注意
class Outer
  def self.who
    puts "Outer class: #{self}"  #=> Outer
  end

  class Inner
    def self.who
      puts "Inner class: #{self}"  #=> Outer::Inner
    end
  end

  def instance_method
    puts "Outer instance: #{self.class}"  #=> Outer

    # ブロック内のselfは変わらない
    [1, 2, 3].each do |n|
      puts "In block: #{self.class}"  #=> Outer(変わらない)
    end

    # instance_evalでselfが変わる
    [1, 2, 3].each do |n|
      n.instance_eval do
        puts "In instance_eval: #{self.class}"  #=> Integer
      end
    end
  end
end

Outer.who
Outer::Inner.who

obj = Outer.new
obj.instance_method

ベストプラクティス

  1. メソッドチェーンではselfを返す
# GOOD: selfを返してメソッドチェーンを可能に
class Formatter
  def initialize(text)
    @text = text
  end

  def upcase
    @text = @text.upcase
    self
  end

  def trim
    @text = @text.strip
    self
  end

  def reverse
    @text = @text.reverse
    self
  end

  def to_s
    @text
  end
end

result = Formatter.new("  hello  ")
  .trim
  .upcase
  .reverse

puts result  #=> OLLEH
  1. selfの省略は慎重に
class Example
  attr_accessor :value

  def calculate
    @value = 10

    # GOOD: 明示的にselfを使う(可読性重視の場合)
    result = self.value * 2

    # ALSO GOOD: selfを省略(簡潔性重視の場合)
    result = value * 2

    result
  end

  def update(new_value)
    # MUST: setterメソッドではselfが必要
    self.value = new_value
  end
end
  1. コンテキストを明確にする
# GOOD: コメントでselfの意味を明確に
class User
  class << self  # selfはUserクラス
    attr_accessor :default_role

    def create(attributes)
      # ここでのselfはUserクラス
      user = new(attributes)
      user.role = self.default_role || :member
      user
    end
  end

  attr_accessor :role

  def initialize(attributes)
    # ここでのselfはUserインスタンス
    @name = attributes[:name]
  end
end

User.default_role = :guest
user = User.create(name: "Alice")
puts user.role  #=> guest

Ruby 3.4での改善点

  • Prismパーサーによる最適化 - selfの解析が高速化
  • YJITの最適化 - selfへのアクセスがより効率的に
  • エラーメッセージの改善 - selfに関するエラーメッセージが明確に
  • 型システムとの統合 - RBSでselfの型を明示的に指定可能
# Ruby 3.4では、selfの使用がより効率的
class PerformanceTest
  def initialize(value)
    @value = value
  end

  def process
    # selfへのアクセスが最適化されている
    100000.times do
      self.class
      self.object_id
      self.instance_variables
    end
  end
end

test = PerformanceTest.new(100)
start = Time.now
test.process
puts "Time: #{Time.now - start}s"

まとめ

この記事では、selfキーワードについて以下の内容を学びました:

  • 基本概念と重要性 - 現在のレシーバ、コンテキストによる変化
  • 基本的な使い方と構文 - インスタンスメソッド、クラスメソッド、setter呼び出し
  • 実践的なユースケース - メソッドチェーン、ビルダー、委譲、シングルトン、DSL
  • 注意点とベストプラクティス - setterでのself、privateメソッド、明示的な使用

selfを正しく理解することで、Rubyのオブジェクト指向プログラミングがより深く理解できます。

参考資料

Discussion