【備忘録】久しぶりにRubyGoldの勉強をしてみた

どうもこんにちは。

半年前に受けて**落ちた**RubyGoldをまた受けようかなと思い、久々に[Rex](https://rex.libertyfish.co.jp/exam_histories)を使用して模擬問題を受けてみました。

https://qiita.com/PDC-Kurashinak/items/2a4279fae328bbc7937b

https://rex.libertyfish.co.jp

# 結果は...

70点...（合格点は75点）

![スクリーンショット 2024-04-04 23.04.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711491/a759ec71-e99f-a7a2-699f-db84302bd6b5.png)

# 解説

自分なりに解説してみました。ご指摘あったらコメントください。

## 1

`Singleton`モジュールは、「**あるモジュールのインスタンスがプログラム全体で１つしか存在しないことを保証する**」モジュールです。

```ruby
require 'singleton'

class Message
  include Singleton

  def morning
    'Hi, good morning!'
  end
end

p Message.__(1)__.morning  ## (1)には「instance」が入ります。
```

なので、**`include Singleton`したクラスのインスタンスは`new`で作成できず、`instance`メソッドを使用して作成することになります。**

## 2

`C.new`でインスタンスが作成されます。
インスタンス内ではインスタンスメソッドしか呼ばれないので、`class << self`の部分は実行されないと考えられます。

`@val`は「インスタンス変数」呼ばれるものになりますが、**クラス内で定義されているため`@val`はクラスメソッドでのみ呼ばれます。**

`attr_accessor :val`では、`val`というゲッターメソッドとセッターメソッドが定義されます。ゲッターメソッドとセッターメソッドは、`@val`というインスタンス変数を共有します。

※わかりづらいですが、クラス内で定義された`@val`とattr_accessorで定義された`@val`は別物です。

```ruby
class C
  @val = 3
  attr_accessor :val
  class << self
    @val = 10
  end
  def initialize
    @val *= 2 if val
  end
end

c = C.new
c.val += 10

p c.val
```

以上のことを踏まえると、`C.new`で作成されたインスタンスから呼ぶことができるコードの中には`@val`に値が代入されているコードはありません。そのため、`initialize`メソッドのif文はFalseとなり、`c.val += 10`のコードはエラーとなります。

## 3

`succ!`メソッドは数字や文字を1つ進めるメソッドです。

`Child1`と`Child2`は`Object`クラスを継承しています。（継承記号`<`が使われていないので分かりづらいですが、気をつけてください。）

以下のコードは1行目から順にRubyインタプリタに読まれていきます。
読み込まれる時には、クラスからアクセスできるコードが呼ばれます。
`Object`クラスが読み込まれたときは、`CONST`定数に`1`が代入されます。（`const_succ`メソッドはインスタンスメソッドなので実行されず。）
`Child1`クラスが読み込まれた時は、`const_succ`メソッド二つが呼ばれます。一つ目の`const_succ`は`Child1`クラスの継承元クラスを順に探していき、`Object`クラスの`const_succ`が見つかり次第実行されます。二つ目の`const_succ`は`class << self`の中で呼ばれているので、一つ目の`const_succ`と同様に呼ばれます。
`Child2`クラスが読み込まれた時は、一つ目の`const_succ`は呼ばれますが、二つ目は呼ばれません。なぜなら、`initialize`メソッドはインスタンスメソッドだからです。**インスタンスメソッドはRubyインタプリタによってクラスが読み込まれている時は呼ばれません。**

`Child1.new`が実行された時には、`Child1`にインスタンスメソッドは存在しないので何も実行されません。
`Child2.new`が実行された時には、`initialize`メソッドが実行されて、`const_succ`が呼ばれます。

```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
```

以上のことから、`const_succ`が呼ばれた回数は4回なので、最初の`CONST = "1"`と合わせると、"5"になります。

## 4

以下のコードも上のコードと同じ理屈です。

Rubyインタプリタにクラスが読み込まれた段階では、`@@val`の値は`1`です。
`C.new`が2回呼ばれていますが、ここでは、`@@val`の値は更新されません。
`S.new`が呼ばれた時は、`initialize`メソッドの中で`@@val`の値が更新されます。

```ruby
class S
  @@val = 0
  def initialize
    @@val += 1
  end
end

class C < S
  class << C
    @@val += 1
  end

  def initialize
  end
end

C.new
C.new
S.new
S.new

p C.class_variable_get(:@@val)
```

以上から`@@val`の値は`3`になります。

## 5

`refine`は訳わからなくなる...
`refine`はメソッドを再定義する時に使います。`refine C.singleton_class do`はクラスメソッドを再定義するときに使用します。`refine C do`の場合はインスタンスメソッドを再定義するときに使用します。

再定義を適用するには、`using R`を使用します。

```ruby
class C
  def self.m1
    200
  end
end

module R
  refine C.singleton_class do
    def m1
      100
    end
  end
end

using R

puts C.m1
```

なので、`puts C.m1`の結果は`100`になります。

## 6

`&`から始まるブロック引数は、仮引数の中で最後に記述する必要があります。

```ruby
def hoge(&block, *args)
  block.call(*args)
end

hoge(1,2,3,4) do |*args|
  p args.length > 0 ? "hello" : args
end
```

なので、このコードではエラーが出ます。

## 7

以下のコードでは、`do...end`と`{...}`の違いによって結果が変わります。
`do...end`と`{...}`では、**`{...}`の方が結合度は強いです。**
今回は、`do...end`でブロックが定義されているため、`m2`は`m1`の引数として読み込まれてしまいます。Rubyインタプリタによって、メソッドの呼び出し時には先に引数の評価がされます。
そのため、`m1`よりも`m2`の方が先に呼ばれます。

```ruby
def m1(*)
  str = yield if block_given?
  p "m1 #{str}"
end

def m2(*)
  str = yield if block_given?
  p "m2 #{str}"
end

m1 m2 do
  "hello"
end
```

なので、次のように出力されます。

```
"m2 "
"m1 hello"
```

## 8

以下のコードでは、`m1`メソッドは全てインスタンスメソッドです。
最初に`K.new.m1`が呼ばれるので、「ポイントA」の`super value - 100`が呼ばれます。これは、「`value - 100`という引数を持った継承元の同名メソッド」を呼び出しています。
先に引数が計算され、その後に継承元の同名メソッド（`module M`の`m1`メソッド）が呼ばれます。
その後、「ポイントB」のsuperが呼ばれます。ここでのsuperは、`using M`が適用される前の`m1`メソッドが呼ばれるので、「ポイントC」のメソッドが呼ばれます。


```ruby
class C
end

module M
  refine C do
    def m1(value)
      super value - 100 # ポイントB
    end
  end
end

class C
  def m1(value)
    value - 100 # ポイントC
  end
end

using M

class K < C
  def m1(value)
    super value - 100 # ポイントA
  end
end

puts K.new.m1 400
```

よって、結果は`100`となります。

## 9

ここでの、ポイントは、`using M`がクラスの中で定義されていることです。これは、クラスCが呼ばれた時に`using M`が呼ばれるということです。

今回は`C.new`で、クラスが呼ばれているのではなく、インスタンスが呼ばれています。そのため、`using M`は呼ばれません。

```ruby
class C
  def m1
    400
  end
end

module M
  refine C do
    def m1
      100
    end
  end
end

class C
  using M
end

puts C.new.m1
```

したがって、`400`が出力されます。

`using M`がメソッド内で呼ばれるとエラーになるので注意しましょう。

## 10

たまに混乱しますが、`freeze`メソッドは「オブジェクトの変更を禁止しているのではなく、**破壊的なオブジェクトの変更を禁止**」しています。

```ruby
array = ["a", "b", "c"].freeze
array = array.map!{|content| content.succ}

p array
```

なので、今回は`map!`メソッドが破壊的な動作をするので、例外が発生します。
しかし、`map`メソッドであった場合は、例外が発生せずに`["b", "c", "d"]`が出力されます。

## 11

ややこしいですが、`freeze`メソッドは「破壊的なオブジェクトの変更を禁止はしていますが、**破壊的なオブジェクトの要素の変更していません。**」

```ruby
array = ["a", "b", "c"].freeze

p array.frozen? # true

array.each do |a|
    p a.frozen? # 各要素に対して、[false, false, false]となる
    a.upcase!
end
```

配列自体は凍結されていますが、配列の要素は凍結されていません。なので、`a.upcase!`ではエラーが発生しません。

一つ前の`map!`メソッドは、`array`という凍結された配列に対して実行されているのに対し、今回の`upcase!`メソッドは要素に対して実行されています。

## 12

キーワード引数のついたメソッドを呼び出すときは、`key`と`value`を同時に渡さないとエラーが発生します。

```ruby
def foo(arg:)
  puts arg
end

foo 100 # foo arg: 100 ならOK
```

## 13

キーワード引数のついたメソッドを呼び出すときに、ハッシュを渡すこともできます。
ハッシュを渡す時には、ハッシュの格納された変数の先頭に`**`をつけます。（`*`だと別の意味になるので注意）

```ruby
def foo(arg1:100, arg2:200)
  puts arg1
  puts arg2
end

option = {arg2: 900}

foo arg1: 200, **option
```

## 14

`Fiber`は、「軽量スレッド」を意味します。
`Fiber`クラスの`resume`メソッドは、`Fiber.new`に指定したブロックの最初の評価、または`Fiber.yield`をが最後に実行された行から評価を行います。

```ruby
f = Fiber.new do |total|
  Fiber.yield total + 10
end

p f.resume(100) + f.resume(5)
```

1. `f.resume(100)`を実行する
2. `Fiber.new`のブロックを評価し、引数`total`には`100`がセットされる
3. `100 + 10`は`Fiber.yield`の引数なので、先に計算がされてから`Fiber.yield`が呼ばれる
4. `Fiber.yield`が呼ばれた時の結果が、`resume`メソッドを実行した時の戻り値になるので、`f.resume(100)`の結果は`110`となる
5. 次に`f.resume(5)`を実行する
6. 前回の続きから処理が始まるが、すでに前回`resume`メソッドを実行した時に処理が終了しているので、`f.resume(5)`の戻り値が5になる
8. 結果的に、`110 + 5`が計算されて`115`が出力される

## 15

以下のコードでは、`f.resume`が3回以上実行された場合にエラーが発生します。
`Fiber.yield`の数+1の回数分`f.resume`が実行できる考えて良いのかもしれません。

```ruby
f = Fiber.new do
  Fiber.yield 15
  5
end
```

## 16

`Date`クラス同士の引き算は`Rational`クラスになります。

```ruby
d = Date.today - Date.new(2015,10,1)
p d.class
```

同じようなオブジェクトであっても引き算された後のクラスが異なるので注意しましょう。

| 引き算されるクラス | 引き算された後のクラス |
| --- | --- |
| Date同士 | Rational |
| Time同士 | Float |
| DateTime同士 | Rational |

## 17

この問題は、継承チェーンの順序を正しく理解する必要があります。
以下のコードの`include`がない場合、クラスCはクラスC2を継承しています。
ここに`include`が入った場合、クラスCは、クラスC２とモジュールMを継承していることになります。

`include`の継承チェーンは、親クラスより手前にモジュールがつながります。
`prepend`が使用された場合には、継承したクラスの処理が実行された後に、モジュールの処理が実行されます。

```ruby
module M
  def foo
    super
    puts "M#foo"
  end
end

class C2
  def foo
    puts "C2#foo"
  end
end

class C < C2
  def foo
    super
    puts "C#foo"
  end
  include M
end

C.new.foo
```

なので、答えは以下のようになります。

```ruby
C2#foo
M#foo
C#foo
```

# まとめ

Rails開発とは直接的な繋がりは少ないが、かなり大事な要素を含んでいる気がしたのでもう一回勉強してみようと思います。

以上