Ruby Gold 3.1を受けた感想... 出題傾向や対策をまとめてみた（2023年11月）

### はじめに
2023年11月にRuby技術者認定試験Goldを受験しました。
結果は96点でした。

<img width="500" alt="test" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/20c1a947-8a1d-318e-935b-e70baf9d953d.png">

ある程度余裕をもって合格することができたので、以下を公開することにしました。
1. 実際に出題された問題例
1. 受験に向けて実施したこと
1. 対策としてまとめていた点

### 受験に向けて実施したこと
#### 勉強期間
Silver合格後、1ヶ月間（70H）ほど勉強しました。
- 10月28日 Ruby技術者認定試験 Silver 受験 
- 11月23日 Ruby技術者認定試験 Gold 受験 

#### 参考にしたサイトや、使用した教材
- [Rex](https://www.ruby.or.jp/ja/certification/examination/rex)
- [Ruby技術者認定試験合格教本](https://www.amazon.co.jp/%E6%94%B9%E8%A8%822%E7%89%88-Ruby%E6%8A%80%E8%A1%93%E8%80%85%E8%AA%8D%E5%AE%9A%E8%A9%A6%E9%A8%93%E5%90%88%E6%A0%BC%E6%95%99%E6%9C%AC-Silver-Gold%E5%AF%BE%E5%BF%9C-Ruby%E5%85%AC%E5%BC%8F%E8%B3%87%E6%A0%BC%E6%95%99%E7%A7%91%E6%9B%B8/dp/4774191949)

https://qiita.com/heysan/items/82bdfb7af4325f05e0e5

#### 勉強方法
問題集の解答から始めると、不明点が多すぎて、学習意欲がなくなりそうだったので、
受験者が公開しているサイトを参考にRuby Gold 3.1の出題傾向や、よく出る問題についての学習から開始することとしました。

その後、RexやRuby技術者認定試験合格教本などの模擬問題の解答を繰り返し、受験当日を迎えました。
Rexは平均90点以上取れるような状態となっておりました。

<img width="500" alt="test" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/57c55789-97e9-22d3-3b2b-9149c91d3679.png">


### 試験対策ポイントまとめ
私が試験学習をするにあたり、迷った点などを重点的にまとめておきます。

#### クラス変数
・レキシカルスコープは探索しない(現在のコンテキスト ⇒ 継承チェーンの順で探索)
・特異クラスと共通の値を保持
・特異クラス内のクラス変数の参照は特異クラスが定義されたブロックのコンテキストで行われる(自身のクラス及び継承チェーンは探索されない)
```ruby
class C
  @@val = 10 #=> 探索されない
end

module M
  @@val = 20 #=> コメントアウトすると例外となる

  class << C
    p @@val #=> 20
  end
end
```

#### クラスインスタンス変数
・特異クラスとは別の値を保持
```ruby
class C
  @val = 3
  class << self
    @val = 10
  end
  
  def self.val
      @val
  end
end

p C.val #⇒ 3
```

・サブクラスには継承されない
```ruby
class B
    @val = 100
    p @val #⇒ 100
end

class C < B
    p @val #⇒ nil
end
```

#### 特異クラス
・クラスの特異クラスは、自身のクラスの定数は参照できない
・インスタンスの特異クラスからは、自身のクラスの定数を参照できる

以下は例外
```ruby
class A
    CONST = 'CONST'
end

class B
  class << A 
    p CONST # => NameError
  end
end
```

以下は例外とはならない
```ruby
class A
    CONST = 'CONST'
end

$a = A.new

class B
  class << $a
    p CONST #⇒ 'CONST'
  end
end
```

#### 定数
・定数の探索順は「自身のコンテキスト => レキシカルスコープ => 継承チェーン => トップレベル」の順で行われる。

・特異クラスの定数探索は定義されたブロックのコンテキストをまずは探索し、その後継承チェーンを探索する
```ruby
class B
    CONST = 'B'
end

module M
  CONST = 'M' # コメントアウトするとBが出力
  b = B.new

  class << b
    p CONST #⇒ 'M'
  end
end
```

・モジュール内の定数参照はモジュールのコンテキストから定数を探索する(その後クラス定数は参照しない)
```ruby
module M
    CONST = 'M' #=> コメントアウトすると例外
    
    def hoge
        p CONST
    end
end

class C
    CONST = 'C'
    
    include M
end

C.new.hoge
```

・ネストされた名前空間の定数参照について
```ruby
# [コード1]
module M
    CONST = 'M'
end

module M
    class C
        p CONST #=> M
    end
end

# [コード2]
module M
    CONST = 'M'
end

module M::C
    class C
        p CONST #=> 例外
    end
end
```

・`const_get`はレシーバ（`self`）から検索を開始する
・`const_defined?(:CONST, false)` ⇒ 継承先を探索しない
・メソッド内の定数更新は例外が発生する。

#### クラス
- `super`は受け取った引数をそのままスーパークラスに渡す
    - 引数を渡さない場合は`super()`とする。

```ruby
class B
    def initialize
    end
end

class C < B
    def initialize(*)
        super #⇒ super()とすると例外は発生しない
    end
end

C.new('hoge') #⇒例外
```

- サブクラスで`initialize`が定義されていない場合、スーパークラスの`initialize`が実行される。

#### モジュール
- `prepend`や`include`はメソッドや定数を上書きはしない（参照を追加している）

`prepend`はクラスの前に参照を追加

```ruby
module M
end

class C
    prepend M
end

p C.ancestors #=> [M, C, Object, Kernel, BasicObject]
```

`include`はクラスの後に参照を追加

```ruby
module M
end

class C
    include M
end

p C.ancestors #=> [C, M, Object, Kernel, BasicObject]
```

- `prepend`や`include`の複数指定 ⇒ 左側から読み込まれる
```ruby
module M1
end

module M2
end

class C
    prepend M1, M2
end

p C.ancestors #=> [M1, M2, C, Object, Kernel, BasicObject]
```
- 順番に読み込ませた場合

```ruby
module M1
end

module M2
end

class C
    prepend M1
    prepend M2
end

p C.ancestors #=> [M2, M1, C, Object, Kernel, BasicObject]
```
- モジュールをミックスインした後に定義したメソッドも呼び出し可能
```ruby
module M
end

class C
    include M
end

module M
    def hoge
        p 'hoge'
    end
end

C.new.hoge #=> "hoge"
```

#### Date
- `date << 1`  1ヶ月前
- `date >> 1`  1ヶ月後

```ruby
require 'date'

p Date.new(2023, 11, 11) << 1 #=> #<Date: 2023-10-11 ((2460229j,0s,0n),+0s,2299161j)>
p Date.new(2023, 11, 11) >> 1 #=> #<Date: 2023-12-11 ((2460290j,0s,0n),+0s,2299161j)>
```

#### Refinement
・`using`を宣言したスコープでのみ上書きする
・`refine C do` は `C`のインスタンスメソッドを上書きする
・クラスメソッドを上書きする場合は`C.singleton_class`を指定して上書きする
・メソッド内で`using`を使用すると例外が発生する
・`using`後にメソッドを再定義しても`Refinement`が優先される
・`using`を複数定義した場合は最後に宣言した`using`のみ有効

#### Forwardable
- extendでミックスインする
    - `extend Forwardable`
- `def delegator`, `def delegators` を使用する

#### class_eval / module_eval / instance_eval
`class_eval` ⇒ クラスに対して、インスタンスメソッドを定義
`instance_eval` ⇒ インスタンスに対して特異メソッドを定義

・ブロックが定義されたコンテキストで定数やメソッドを探索する
・定義した定数はブロックが定義されたコンテキストで評価される（以下はTOPレベルで宣言したことになる）
```ruby
mod = Module.new

mod.module_eval do
  EVAL_CONST = 100
end

p EVAL_CONST #=> 100
```

・文字列を渡した場合はレシーバのコンテキストで評価される
```ruby
mod = Module.new

mod.module_eval(<<-EOS)
  EVAL_CONST = 100
EOS

p EVAL_CONST #=> 例外(トップレベルにはEVAL_CONSTは存在しない)
```

・クラスメソッドの定義する場合は`self.hoge`とする

・instanceに対して、module_evalで定義されたメソッドは呼び出せないので、注意
```ruby
m = Module.new

CONST = "Constant in Toplevel"

_proc = Proc.new do
  CONST = "Constant in Proc"
end

m.module_eval(<<-EOS)
  CONST = "Constant in Module instance"

  def const
    CONST
  end
EOS

m.module_eval(&_proc)

p m.const #=> 例外となる
```

#### clone, dup
- `clone`は特異メソッドも含めた複製を作成
- `dup`は特異メソッドは含めない
- `clone`も`dup`はシャローコピー

#### do endと{}の結合度の違い
`do ~ end`の方が結合度が高い

#### rescue
- `rescue Class`は指定したクラスとそのサブクラスを捕捉する
    - `rescue NameError`は`NoMethodError`も捕捉
- `rescue`ブロックでの`raise`は指定したクラスではなく実際に発生したエラーを`throw`
- `rescue else`は例外が発生しなかった場合のみ実行
- `rescue ensure`は例外の発生有無に関わらず、必ず実行される

#### *args
`*args`は引数がひとつでも配列として受け取る
```ruby
def hoge(*args)
    p args #=> [1]
end

hoge(1)
```

#### or, and
`or`, `and`は `||`, `&&`より結合度が低い。
```ruby
puts true and false #=> trueが出力
```

#### freeze 
・破壊的変更は不可 ※配列に指定した場合各要素の破壊的変更は可能
・変数自体の値再代入は可能
 
以下は例外
```ruby
array = ["a", "b", "c"].freeze
array = array.map!{|content| content.succ}

p array #⇒ 例外
```

以下は例外
```ruby
char = { :a => "A" }.freeze
 char[:a] = "B"
 p char

array = [1,2,3].freeze
array[0] = 0 #⇒ 例外
```

以下は例外にならない
```ruby
array = [1,2,[3,4]].freeze
array[2][0] = 0

p array #⇒ [1, 2, [0, 4]]
```

以下は例外にならない
```ruby
array = ["a", "b", "c"].freeze

array.each do |chr|
  chr.upcase!
end

p array #⇒ ["A", "B", "C"]
```

#### throw ~ catch
・戻り値は引数の二番目に指定
```ruby
throw :done, [a,b,c]
```

#### RDoc
・`*word*` ⇒ 太字
・`_word_` ⇒ 斜体
・`+word+` ⇒ タイポライター
・`*、 -` ⇒ 番号なしリスト

#### *methods
・`methods` ⇒ レシーバの`public + protected` なメソッドのみを取得
・`public_methods` ⇒ レシーバの`public`なメソッドのみを取得
・`private_methods` ⇒ レシーバの`private`なメソッドのみ

#### キーワード引数
引数が未指定の場合は例外が発生する

```ruby
def hoge(foo:)
  p foo
end

hoge(1) #⇒例外
```

#### alias, alias_method
- `alias` => エイリアス名 メソッド名
    - ※カンマがあるとエラー、識別子/シンボルのみ指定可能
- `alias_method` => エイリアス名, メソッド名
    - ※カンマは必要。シンボル/文字列のみ指定可能

#### undef, remove_method
`remove_method`は現在のクラスからメソッドを削除。
継承元にメソッドが定義されてあれば継承元のメソッドが呼ばれる

`undef_method`はメソッド呼び出しへのレスポンスを止める。
`remove_method`と違い、継承元のクラスにメソッドが定義されていてもエラーとな流。

`undef`後にメソッドを再定義した場合は呼び出し可能。
`undef`後にモジュールを`include`しても、呼び出しは不可。

#### Class#method_missing
```ruby
class Class
  def method_missing(id, *args)
    puts "Class#method_missing" #=> ここでキャッチされる
  end
end
class A
  def method_missing(id, *args)
    puts "A#method_missing"
  end
end

A.dummy_method
```

#### const_missing
定数が見つからない時に実行
クラスメソッドとして定義する
```ruby
class Object
  def self.const_missing a
    p "#{a}"
  end
end
B #⇒ 'B'
```

#### proc, yield
`Proc`の作成
・`Proc.new{...}`
・`proc {...}`
・`lambda {...}`

`Proc`オブジェクトの呼び出し
・`proc.call`, `proc.yield`, `proc[]`
・`yield` ※callは付けない。

#### &block
`&block` => ブロックを明示的に受け取る
引数の最後に書かないとエラー

```ruby
# 以下はエラー
def hoge(&block, *args)
  block.call(*args)
end
```

#### オプション
`-t`, `-f`はない。
`-I(include)` ⇒ `$LOAD_PATH`に追加で読み込むファイルを追加
`e(evaluate)` ⇒ 引数で渡した文字列を評価。結果を出力
`-d(debug)` ⇒ デバッグモード。スレッドの例外を捕捉
`-h(help)` ⇒ ヘルプ
`-c(check)` ⇒ 文法が正しいかチェック
`-w(warning)` ⇒ 冗長モード
`-r(require)` ⇒ スクリプト実行前に指定されたファイルの実行

#### 組み込み定数
`$0` ⇒ 実行中のファイル名
`$1..` ⇒ 正規表現でマッチした文字

`NULL`は組み込み定数ではない。

#### strptime / strftime
- `strptime` ⇒ 文字列をTimeオブジェクトに変換
- `strftime` ⇒ 日付オブジェクトを指定したフォーマットの文字列に変換

#### iso8601
- `Time.iso8601` ⇒ iso文字列をTimeオブジェクトに変換
- `Time#iso8601` ⇒ レシーバのTimeオブジェクトをiso8601形式の文字列に変換

#### classメソッド
- Classオブジェクトに対して実行 ⇒ Classが返る
- Classインスタンスに対して実行 ⇒ クラス名が返る

#### DATA, __END__
`DATA`(File obj) ⇒ `__END__`以降の文字列をファイルオブジェクトとして取得

#### ===
1. 範囲オブジェクトの範囲チェック
2. インスタンスであるかチェック

#### トップレベルのメソッド
- `Object`クラスの`private`メソッドとして扱われている
```ruby
def hoge
    puts 'hoge'
end

# 上記はhogeメソッドはObjectクラスのprivateメソッドとして取り扱われている
class Object
    private
        def hoge
            puts 'hoge'
        end
end
```

#### puts, print, p
- `puts`, `print` ⇒ `to_s`メソッドが呼ばれる
- `p` ⇒ `inspect`メソッドが呼ばれる

#### Module.nesting
・TOPレベルで実行すると、`[]`が変える
・実行結果は内側から返る

```
module SuperMod::BaseMod
  p Module.nesting
end
# => [SuperMod::BaseMod]

module SuperMod
    module BaseMod
        p Module.nesting
    end
end
#=> [SuperMod::BaseMod, SuperMod]
```

### 出題傾向
自分の想定よりRuby Silverで出題されるようなメソッドの利用方法などに関する問題が多かった気がします。
#### 出題された問題
- クラス, モジュール間の定数参照について
- モジュールをミックスインした場合の`super`によるメソッド呼び出し
- `using`で`Refinement`を有効にした場合の`super`によるメソッド呼び出し
- モジュールのクラスメソッドのミックスイン
    - モジュールのクラスメソッドは`include`や`prepend`ではクラスにミックスインできない。
```ruby
module M
    def self.hoge
        puts 'hoge'
    end
end

class C
    include M
end

puts C.methods.include?(:hoge) #=> false
```
- instance_variable_get
```ruby
# どちらが正しいか
obj.instance_variable_get(:@foo) #=> こちらが正しい
obj.instance_variable_get(:foo)   
```
- トップレベルに宣言したメソッドについて
- `Compartible`モジュールのミックスイン, `<=>`でのソート
- Procオブジェクトの作成方法について（`Proc.new {...}` or `lambda {...}`）

#### 出題されなかった問題
- `const_eval`, `instance_eval`
- `const_get`, `const_defined?`
- 特異クラス内での定数参照について
- `Enumerable`モジュール、`Enumerator`, `Enumerator::Yielder`

### さいごに
本記事が皆さんの合格に少しでも役立てれば幸いです🙏