Ruby Gold受験振り返り

# RubyGoldを受験した結果を振り返る

RubyGoldを受験してきましたので、備忘録としてまとめます。

## 経緯

2022年7月（当時大学４年生）に自分のスキルアップのために、RubySilverの試験を受験しまして、その時は80/100（合格ボーダーは75）で合格しました。

それから一ヶ月程度経った後に、RubyGoldの試験対策を始め、手始め模擬問題集を見てみましたが、最初の感想は「レベルが違う。。。」でした。（実際に初見で解いて30点しか取れませんでした。。。）

そこから、大学の研究室やアルバイトの合間を縫ってRubyGoldの勉強（後に説明）をしていましたが、気づいたら2023年3月。

「やばい！もう社会人じゃん！」

これはまずいと思いましたよね。

そこから勉強の時間を増やして3ヶ月経過しました。

しかし、結果は落ちました。。。

66点。。。原因はわかっています。それを後半説明します。

## 勉強方法

箇条書きで説明していきますね。

- 公式問題集で勉強（使用したのは、後ろについている問題集）
- メタプログラミングRubyの1章〜5章までを一周
- [Rex](https://rex.libertyfish.co.jp/)で勉強
- [このブログを読んでRexにない知識をインプット](https://tamata78.hatenablog.com/entry/2015/10/16/205829)

個人的に時間がかかったのは、メタプログラミングRubyです。

結構量あるし、何より頭に入ってきづらかった！（個人差あり）

ただ、公式問題集に載っていないのに試験に出てくる知識はメタプログラミングRubyで勉強していくしかなさそうでした。

あとはRexで問題をひたすら解いていくことをお勧めします。

1. 問題を解いていく
2. 答え合わせをして解説を読む
3. `irb`で動作確認
（詳しい原理や機能は、ChatGPTに聞くのをお勧めします。）

この繰り返しです。

## Rex

Rexを解いていく中で理解に苦戦した問題５つを紹介します。

### 第一問

```ruby
class C
  CONST = "Hello, world"
end

ccc = C.new

class D
  class << ccc
    def say
      CONST
    end
  end
end

p $c.say   #=> Hello, world
```

わかる人にはわかるんだろうけど、なんか苦手意識を持っている。。。

`$c`はクラスCのインスタンス。`say`メソッドは`$c`の特異クラスのメソッド（クラスCのインスタンスの特異クラスのメソッド）。

1. `$c`の特異クラスを探索→定数`CONST`がない。
1. `$c`のスーパークラス（クラスC）を探索→`CONST`が定義されている！

なので答えは`Hello world`となります。（解説を書いていたら理解できた。。。）

### 第二問

```ruby
mmm = Module.new

mmm.module_eval do
  EVAL_CONST = 100
end

puts "EVAL_CONST is defined? #{mmm.const_defined?(:EVAL_CONST)}"   #=> true
puts "EVAL_CONST is defined? #{mmm.const_defined?(:EVAL_CONST, false)}"   #=> false
puts "EVAL_CONST is defined? #{Object.const_defined?(:EVAL_CONST)}"   #=> true
puts "EVAL_CONST is defined? #{Object.const_defined?(:EVAL_CONST, false)}"   #=> true
```

この問題は`mod`のレキシカルスコープ内で`EVAL_CONST`が定義されているかいないかの問題となります。

まず、`module_eval`で定義された定数はトップレベルで定義されます。そのため、３、４行目の評価式はどちらも`true`になります。

次に、`mod`に`EVAL_CONST`はあるか？という話になるのですが、`mod`に`EVAL_CONST`は定義されていません。ただ、`const_defined?`メソッドは第二引数に`false`を設定すると、「継承元の探索を行わない」。`false`を指定しないと、「継承元の探索を行う」という形となります。

つまり、１行目の評価式は継承元の探索を行い、２行目の評価式は継承元の探索を行わないとという形になります。

`mod`の継承元は、「Moduleクラス→Objectクラス」となります。Objectクラスには`EVAL_CONST`が定義されていますので、1行目では`true`が返されます。

一方で、2行目の評価式では、継承元の探索が行われないので、`mod`のスコープ内を探索したらそれで終了。なので、`false`が返されます。

1行目が難しいですよね。

### 第三問

```ruby
mmm = Module.new

mmm.module_eval <<-RUBY
  EVAL_CONST = 100
RUBY

puts "EVAL_CONST is defined? #{mmm.const_defined?(:EVAL_CONST)}"   #=> true
puts "EVAL_CONST is defined? #{mmm.const_defined?(:EVAL_CONST, false)}"   #=> true
puts "EVAL_CONST is defined? #{Object.const_defined?(:EVAL_CONST)}"   #=> false
puts "EVAL_CONST is defined? #{Object.const_defined?(:EVAL_CONST, false)}"   #=> false
```

これは、第二問の定数の定義をヒアドキュメントを使用して定義した場合です。

ヒアドキュメントで定義した時の`EVAL_CONST`は`mod`のスコープ内でのみ探索することが可能となります。

つまり、１、2行目の評価式ではtrueが、３、4行目の評価式ではfalseが返されます。

### 第四問

「同じ結果になる選択肢はどれですか（複数選択）」という問題です。

```ruby
module M
  CONST = "Hello, world"

  class C
    def awesome_method
      CONST
    end
  end
end

p M::C.new.awesome_method   #=> Hello, world

---

選択肢1
module M
  CONST = "Hello, world"
end

class M::C
  def awesome_method
    CONST
  end
end

p M::C.new.awesome_method   #=>例外が起こります

---

選択肢２
class C
end

module M
  CONST = "Hello, world"

  C.class_eval do
    def awesome_method
      CONST
    end
  end
end

p C.new.awesome_method   #=> Hello, world

---

選択肢3
class C
  CONST = "Hello, world"
end

module M
  C.class_eval(<<-CODE)
    def awesome_method
      CONST
    end
  CODE
end

p C.new.awesome_method   #=> Hello, world

---

選択肢4
class C
  CONST = "Hello, world"
end

module M
  C.class_eval do
    def awesome_method
      CONST
    end
  end
end

p C.new.awesome_method   #=> 例外が起こります
```

これはややこしいです。。。

まず、選択肢１は、`awesome_method`メソッド内のCONSTが参照できるものがないため、例外が起こります。
参照できない理由として、CONSTを定義しているコードが同じコンテキスト内に存在しないからです。

選択肢２は、`class_eval`（ブロック）で定義した場合のネストの状態、スコープを考えます。ネストの状態は、ブロックで定義されているので、**`class_eval`ブロックが定義されている場所がネストとなります**。つまり、モジュールMです。ネストがモジュールM＝定数のスコープもモジュールMであることがわかります。運よくモジュールMで定数は定義されているので、正常に文字列が出力されます。

選択肢３は、選択肢２と似ていますが、定数が定義されている場所とヒアドキュメントが使用されていることに注意が必要です。ヒアドキュメントが使用されている場合のネストは、**`class_eval`のレシーバです**。つまり、クラスCです。ネストがクラスC＝定数のスコープもクラスCです。運よく定数はクラスCに定義されているので、正常に文字列が出力されます。

選択肢４は、運が良くないですね。動きは選択肢２と同じですが、スコープ内（モジュールM内）に定数が定義されていないですね。なので、例外が起こります。

### 第五問

```ruby
class Object
  CONST = "1"
  def const_succ
    CONST.succ!
  end
end

class Child1
  const_succ
  class << self
    const_succ
  end
end

class Child2
  const_succ
  def initialize
    const_succ
  end
end

Child1.new
Child2.new

p Object::CONST
```

まず、このコードたちを実行した時に、以下の順番で処理がされていきます。

1. クラスの定義
1. インスタンスの呼び出し
1. Object::CONSTの呼び出し

まず、クラスの定義時に行われる処理を説明していきます。Objectクラスを定義する時には、定数の定義（CONST="1"）と`const_succ`メソッドの定義（CONST="2"）がされます。次にChild１クラスを定義する時には、最初の`const_succ`（CONST="3")が実行されます。しかし、Child1クラスの特異クラス内にある`const_succ`が実行されません。次にChild2クラスを定義する時には、最初の`const_succ`（CONST="4"）が実行されます。しかし、`initialize`メソッド内の`const_succ`は実行されません。

次にインスタンスの呼び出し時に行われる処理を説明します。`Child1.new`を呼び出したときには、最初の`const_succ`は呼び出されません。また、Child1クラスの特異クラス内にある`const_succ`も呼び出されません。次に、`Child2.new`を呼び出したときには、`initialize`メソッド内の`const_succ`（CONST="5"）が呼び出されます。

最後に`Object::CONST`が呼ばれた時にはCONSTの値は"5"なので、出力される値は"5"です。

ちなみに、インスタンスの呼び出し時に`const_succ`が呼ばれない理由として、`const_succ`がそれぞれのクラスメソッドとして呼び出されているからです。

### 落ちた原因

さて、最後に試験に落ちてしまった原因ですが、自分がRubySilverを受験したのはバージョン2.1のものです。しかし、今回受験したのはバージョン3.0でした。

テストセンターで問題を解く時、「Version3.0」という文字が目に入り、「マジか、、、やばい」と思いました。
それに試験問題は全然違かったです。

Rexをメインに使用してバージョン3.0を受験することを考えている人は一旦考えてください。
[こちらの記事](https://qiita.com/heysan/items/82bdfb7af4325f05e0e5)や[こちらの記事](https://maeda-seina.hatenablog.com/entry/2023/01/18/111315)で対策記事が書かれていますので、一度こちらをお読みください！

その他記事は以下にまとめておきます。

```
https://zenn.dev/universato/articles/20221011-z-rubygold3
https://ankh-systems.co.jp/blog/51
```

しかし、バージョン3.0の模擬問題が出ていないので対策しづらいです。。。
コツコツ地道に対策をしていきます。。。

### まとめ

RubyGoldを受けようとしている人は、RubySilverよりも格段にレベルが上がると思っておいた方がいいです。

個人差はあるかもしれないですが、以下の点については十分に対策をした方が良いと思います。

- 定数のスコープ、ネスト
- ブロック
- クラスメソッドとインスタンスメソッドの違い
- `module_eval`、`class_eval`、`instance_eval`の違い
- メソッドや定数の探索順

あと、バージョン2.1と3.0はそこそこ問題がずれているので注意をしてください！

以上