【Ruby】instance_evalとclass_evalって何が違うんだヨォ...

どうもこんにちは。

今回は、`instance_eval`と`class_eval`の違いがわからなかったので、調べました。

# そもそもこいつらいつ使う？

`instance_eval`と`class_eval`は、**動的にメソッドや属性を追加**したり、**クラスやインスタンスの内部状態に直接アクセスする**時に使用されます。

# Railsで使用することはある？

自分の感覚としては、Railsでアプリ開発を行う場合に`instance_eval`と`class_eval`は使用しないです。どちらかというと、Railsの内部動作に使用されているようです。

## 1. ActiveRecordでのclass_evalの使用

これが一番馴染み深いかもです。

Railsでデータを登録するときは、`User.name = '試験太郎'`というように記述したりします。
このコードが動作している背景として、`class_eval`が使用されているようです。

```rb
User.class_eval do
  def name
    # データベースから値を取得
  end

  def name=(value)
    # データベースに値をセット
  end
end
```

## 2. Rails ルーティング定義の時にinstance_evalを使用

Railsのルーティングを定義する場合、以下のように記述すると思います。

```rb
Rails.application.routes.draw do
  resources :users
end
```

`draw`メソッドの中で`instance_eval`が使われており、ブロックの中でメソッドを定義することで簡単にルーティングを定義することができます。

## 3. ActiveSupport::Concernでのclass_evalの使用

`ActiveSupport::Concern`は、Railsでモジュールを簡単に拡張できる仕組みを提供しています。
このモジュールでは、includedブロック内でclass_evalが使われており、モジュールが含まれたクラスに対して追加のメソッドを動的に定義しています。

```rb
module MyModule
  extend ActiveSupport::Concern

  included do
    # class_evalが内部で使われることで、含まれたクラスに対してコードが追加される
    has_many :comments
  end

  def my_method
    puts "This is a method from MyModule"
  end
end

class MyClass < ApplicationRecord
  include MyModule
end
```

## 4. Validationやコールバックの定義での使用

Railsのバリデーションやコールバックも、クラスやインスタンスに対して動的にメソッドを定義するために`class_eval`や`instance_eval`を使っています。例えば、`before_save`や`after_create`といったコールバックメソッドを定義する際、これらのメソッドはクラスに追加されます。

```rb
class User < ApplicationRecord
  before_save :do_something
  validate :validate_custom

  private
  def do_something
    # コールバック用のロジック
  end

  def validate_custom
    # カスタムバリデーションのロジック
  end
end
```

# 具体的な動作

ここからは具体的な動作を見ていきます。

## instance_eval

`instance_eval`は、特定のインスタンスに対してブロックまたは文字列を評価し、そのインスタンスのコンテキストで動作します。用途は以下です。

- **インスタンスのプライベートメソッドや属性にアクセスするため**
    - インスタンスの外部からは通常アクセスできないプライベートメソッドや変数にアクセスできるようになります
- **特定のインスタンスにのみメソッドを追加する場合**
    - クラス全体ではなく、特定のオブジェクトに対してだけメソッドを動的に追加することができます

```rb:サンプルコード
class MyClass
  def initialize
    @name = "Hello"
  end

  private
  def greet
    "Greetings!"
  end
end

obj = MyClass.new
sub_obj = MyClass.new
obj.instance_eval do  # インスタンスに対して`instance_eval`を使用している！
  @name   # => "Hello" （インスタンス変数に直接アクセス）
  greet   # => "Greetings!" （プライベートメソッドにアクセス）

  def custom_method   # インスタンスに対して`custom_method`を定義
      @name = "こんにちは"
  end
end
```

ポイントとして、`obj = MyClass.new`を実行した後にインスタンス`obj`に対して`instance_eval`メソッドを実行しています。

また、`instance_eval`ブロックの中で`custom_method`メソッドを定義しています。
これによって、`obj`インスタンスからは`custom_method`メソッドを呼び出すことができますが、`sub_obj`インスタンスからは`custom_method`メソッドを呼び出すことができません。

## class_eval

`class_eval`は、クラスやモジュールのコンテキストでブロックや文字列を評価し、そのクラスやモジュールの定義を動的に変更する際に使用します。

- **クラスに対してメソッドを動的に追加する場合**
    - 既存のクラスにメソッドを追加したり、クラスの内部状態にアクセスしたりできます
- **プライベートメソッドやインスタンス変数にクラスレベルでアクセスする場合**
    - class_evalを使うことで、クラス内のプライベートメソッドや変数にアクセスできます

```rb:サンプルコード
class MyClass
  def initialize
    @name = "Hello"
  end

  private
  def greet
    "Greetings!"
  end
end

MyClass.class_eval do   # クラスに対して`class_eval`を実行している！
  def new_method
    "This is a new method!"
  end
end

obj = MyClass.new
obj.new_method   # => "This is a new method!"
```

ポイントとして、`obj = MyClass.new`を実行する前に`MyClass`に対して`class_eval`を実行しています。こうすることで、全ての`MyClass`のインスタンスから`new_method`メソッドにアクセスすることができます。

# まとめ

Rails開発をしている自分としては、内部動作を勉強する良い機会となりました。
ただ、コーディング中に`instance_eval`や`class_eval`を使用することはあまりないかなぁと思います。

もしかしたら、Rubyのライブラリ開発をする時に使用するかもしれないですね！