最初に
Ruby技術者認定試験がversion 2.1 -> 3.1に改訂されました。
Gold 2.1は受けたことがないので3.1で初めてのGold受験でしたが無事合格することができました。
2023年1月20日までに受ければ落ちても2回目無料キャンペーン中だったのでメンタル的にもちょうど良かったです!
私の場合は昨年シルバー2.1を取得済みだったので、今回ゴールド3.1を取得したことで認定ver3プログラマGoldを名乗れるようです。
新しいバージョンってどうなの??とまだ様子見している方もいらっしゃると思います。
私も試験前にGold 3.1の情報が少なく対策に不安があったので、少しでも参考になればと思い記憶が新しいうちに情報を残そうと思います!
経歴
私の経歴としてはこんな感じです。レベル感の目安になれば。
2021年2月 初めてプログラミング学習開始(ずっと文系でメーカーの営業・事務職でした)
2021年7月 IT企業に入社
~ Ruby on RailsやVue.jsを実務で使う ~
2021年12月 Ruby Silver 2.1合格(92点)
~ Ruby on Railsを実務で使う ~
2022年11月 Ruby Gold 3.1合格(80点)
勉強方法
これまでのバージョンとの違いは公式に出されているこちらの模擬問題集が役に立ちました。
(むしろ最新版試験の対策はこれしかないような・・・)
あとは従来のGold対策と同じようにRExと公式合格教本の問題をひたすら解いて、
解説を読んで理解しておきました。
答え合わせしていて腑に落ちないところなどは必ず自分でコードを書いて動かして確かめることで理解が深まったと思います!
合格教本だけだと理解しにくい箇所もあるので、知識を補うために「プロを目指す人のためのRuby入門」(チェリー本)を大変参考にしておりました。改訂前のものしか持ってなかったのですがProcやメソッド探索、クラス変数などとてもわかりやすく解説していただいているので大変お世話になりました。
2.1と3.1で変わっているところ
旧試験と正誤が変わってくる重要度が高いのがこれらです。
他にも思い出したらまた追加します。
項目 | 2.1 | 3.1 |
---|---|---|
privateメソッド | レシーバ付きでは呼び出せない | レシーバがselfなら呼び出せる |
open-uriライブラリ | openを再定義する | openを再定義しない ( URI.open を使う) |
Integerクラス | FixnumとBignumに分かれている | Integerに統合されている |
privateメソッドの挙動コード例
class Sample
def hoge
self.fuga # 2.1の問題ではここがエラーになるが3.1では呼び出せる
end
private
def fuga
puts "fuga"
end
end
Sample.new.hoge # => fuga
3.1の新しい概念
番号指定パラメータ
_1
, _2
などの記法。
Numbered Parameters(ナンパラ)。ブロックの仮引数として使用する。
_0
ではなく_1
から始まる点に注意。
> ["foo", "bar", "baz"].map { _1.upcase }
=> ["FOO", "BAR", "BAZ"]
転送引数
...
引数を転送する記法。
すでに指定されている引数がある場合は残りの全ての引数を転送する。
括弧が必須である点に注意。
def bar(test)
p test
end
def foo(...)
bar(...)
end
foo(["Ruby Silver!", "Ruby Gold!"]) # => ["Ruby Silver!", "Ruby Gold!"]
foo("Ruby Gold!") # => "Ruby Gold!"
tally
(公式模擬問題にもなかったし今後出題されるかは不明ですが使いやすそうなので・・)
要素ごとの個数を返すメソッド。
selfに含まれる要素を数え上げた結果をHashで返す。
Hash のキーはselfに含まれる要素で、valueは対応する要素が出現する回数。
結果を加算したい場合には引数にHashオブジェクトを指定する。
idols = ['TWICE', 'IZ*ONE', 'aespa', 'BLACKPINK', 'aespa', 'TWICE']
idols.tally
=> {"TWICE"=>2, "IZ*ONE"=>1, "aespa"=>2, "BLACKPINK"=>1}
["kep1er", "LE SSERAFIM", "IZ*ONE"].tally(idols.tally)
=>
{"TWICE"=>2,
"IZ*ONE"=>2,
"aespa"=>2,
"BLACKPINK"=>1,
"kep1er"=>1,
"LE SSERAFIM"=>1}
filter_map
各要素に対してブロックを評価した値のうち、真であった値のみの配列を返すメソッド。
nil
だけを除外して返すcompact
メソッドと少し異なり、偽の値(つまりfalse
とnil
)を除外する。
(1..10).filter_map { |i| i * 2 if i.even? } #=> [4, 8, 12, 16, 20]
[1, 2, 3].filter_map {|x| x.odd? ? x.to_s : nil } #=> ["1", "3"]
ary = ["foo", "bar", nil, "baz"]
p ary.filter_map { |i| i&.upcase }
# 真の値に対してupcaseされた配列が返る
=> ["FOO", "BAR", "BAZ"]
ary = [1, nil, false, "test"]
p ary.filter_map(&:itself)
# nilとfalseを除外
=> [1, "test"]
flat_map(エイリアス: collect_concat)
各要素をブロックに渡し、その返り値を連結した配列を返すメソッド。
必ず1次元配列を返す訳ではなく、配列を1次元減らすメソッドである点に注意。
# flat_mapメソッドの場合
[[0, 1], [2, 3]].flat_map {|e| e + [100] } # => [0, 1, 100, 2, 3, 100]
# mapメソッドの場合
[[0, 1], [2, 3]].map {|e| e + [100] } # => [[0, 1, 100], [2, 3, 100]]
# 配列を1次元だけ減らす仕様
values = [[[1,2,3]]]
values.flat_map { |value| value } # => [[1, 2, 3]]
# 必ず1次元配列にしたい場合はflattenを使う必要がある
values = [[[1,2,3]]]
values.flat_map { |value| value }.flatten # => [1, 2, 3]
ぼっち演算子
正式名称はsafe navigation operatorと言うらしいです。
レシーバがnil
でない時のみ後続のメソッドを呼び出す演算子。
# レシーバがnilだとnilが返る
no_number = nil
no_number&.succ # => nil
# レシーバが有効であれば後続のメソッドが実行される
number = 0
number&.succ # => 1
strptime
DateTime, Date, Timeクラスの特異メソッド。
与えられた雛型で日時表現を解析し、その情報に基づいてオブジェクトを生成する。
基本形:
Time.strptime(date, format, now=self.now)
Time.strptime(date, format, now=self.now) {|y| ... }
Time.strptime("00000024021993", "%S%M%H%d%m%Y")
=> 1993-02-24 00:00:00 +0900
# ブロックを渡すと年の部分を変更できる
Time.strptime('91/5/18 4:13:00', '%Y/%m/%d %T'){|y| # ←yにはyear(91)が渡されている
if y > 100 then y
elsif y >= 69 then y + 1900
else y + 2000
end
}
=> 1991-05-18 04:13:00 +0900
DateクラスとDateTimeクラスにもあり、引数がTimeクラスと異なる
基本形:
DateTime.strptime(str = '-4712-01-01T00:00:00+00:00', format = '%FT%T%z', start = Date::ITALY)
datetime = DateTime.strptime('2022-01-01T00:00:00+00:00')
=> #<DateTime: 2022-01-01T00:00:00+00:00 ((2459581j,0s,0n),+0s,2299161j)>
datetime.to_s
=> "2022-01-01T00:00:00+00:00"
基本形:
Date.strptime(str = '-4712-01-01', format = '%F', start = Date::ITALY)
date = Date.strptime('2022-01-01T00:00:00+00:00')
=> #<Date: 2022-01-01 ((2459581j,0s,0n),+0s,2299161j)>
date.to_s
=> "2022-01-01"
Timeクラスのインスタンスメソッド iso8601
XML Schema で定義されている dateTime として表現される形式の文字列を返す。
t = Time.strptime("00000001122022", "%S%M%H%d%m%Y") # => 2022-12-01 00:00:00 +0900
# Timeクラスのインスタンスに対して呼び出すとフォーマットされた文字列が返る
t.iso8601 # => "2022-12-01T00:00:00+09:00"
Timeのクラスメソッド iso8601
XML Schemaで定義されているdateTimeとしてdateをパースしてTimeオブジェクトに変換する。
dateがISO8601で定義されている形式に準拠していない、
またはTimeクラスが指定された日時を表現できないときにArgumentErrorが発生。
# 引数にdatetimeの文字列を指定し、Timeオブジェクトを生成
t = Time.iso8601(DateTime.now.to_s) # => 2022-11-19 17:30:46 +0900
t.class # => Time
Forwardableモジュール
クラスに対し、メソッドの委譲機能を定義するモジュール。
委譲元のオブジェクトでali
が呼び出された場合に、委譲先のオブジェクトのメソッドへ処理が委譲されるようになる。
委譲元と委譲先のメソッド名が同じ場合は,ali
を省略することが可能。
def_delegator
はdef_instance_delegator
のエイリアス。
includeではなくextendして使う点に注意。
以下の1と2は同じ意味
# 1
def_delegators :@records, :size, :<<, :map
# 2
def_delegator :@records, :size
def_delegator :@records, :<<
def_delegator :@records, :map
パターンマッチ
case whenと似てるけど違う。
case
in
in
else
大変詳しく解説されているのでこちらをぜひ。
他にも思い出したら書き足そうと思います。
ポイントまとめ
Goldを勉強しながら整理するために書いたメモです。
試験前に見返してました。
数値を表す組み込みクラス
Numeric
をスーパークラスとして以下のサブクラスがあります。
Numeric.subclasses => [Date::Infinity, Complex, Rational, Float, Integer]
クラス名 | 内容 |
---|---|
Numeric | 数値を表す抽象クラス |
Integer | 整数を表すクラス |
Float | 少数を表すクラス |
Rational | 分数で表せる数値のクラス(有理数) |
Complex | 虚数iを含めたクラス(複素数) |
Date::Infinity | 無限の日付を扱うクラス |
演算の戻り値クラス
ここは混乱しがちなので、Integer
< Rational
< Float
< Complex
の順で強いと覚えました。
演算 | 戻り値クラス | 例 |
---|---|---|
IntegerとRationalの演算 | Rational |
t = 1 + 3r t.class # => Rational
|
FloatとRationalの演算 | Float |
t = 1.0 + 3r t.class # => Float
|
FloatとIntegerの演算 | Float |
t = 1.0 + 3 t.class # => Float
|
IntegerとComplexの演算 | Complex |
t = 1 + 3i t.class # => Complex
|
FloatとComplexの演算 | Complex |
t = 1.0 + 3i t.class # => Complex
|
RationalとComplexの演算 | Complex |
t = 3r + 1i t.class # => Complex
|
Date同士の減算 | Rational |
t = Date.new(2022,12,25) - Date.new(2022,12,24) t.class # => Rational
|
DateTime同士の減算 | Rational |
t = DateTime.new(2022,12,25) - DateTime.new(2022,12,24) t.class # => Rational
|
Time同士の減算 | Float |
t = Time.new(2022,11,1,10,00) - Time.new(2022,11,1,9,00) t.class # => Float
|
requireとloadの違い
require | load |
---|---|
同じファイルを1回のみロード | 無条件にロードする |
拡張子(.rbや.so)を自動補完する | 拡張子を補完しない |
主にライブラリのロードに使用 | 主に設定ファイルの読み込みに使用 |
rubyのオプション
-
-t
と-f
はrubyのオプションに存在しない
Rationalオブジェクト
作り方は以下の3パターン
1 / 3r
1 / 3.to_r
Rational(1, 3)
変数
クラス変数(@@
で始まる)
- スーパークラス・サブクラスで共有される。
- クラスメソッド内・インスタンスメソッド内で共有される
- 定義されていなければ呼び出しても
NameError
になる
インスタンス変数(@
で始まる)
- インスタンスを生成した際にデータが作成される。
- インスタンスが生成されていないとnilになる。
- スーパークラス・サブクラスで共有される。
クラスインスタンス変数(@
で始まる)
- インスタンスの生成とは無関係に、クラス自身が保持しているデータ。
- クラス構文の直下やクラスメソッドの内部で@から始まる変数を操作するとクラスインスタンス変数にアクセスすることになる。
- スーパークラスとサブクラスで共有されない
- インスタンスメソッド内で共有されない
LambdaとProc
Proc.new
の引数の数は曖昧
lambda
の引数の数は厳密
procの記法
Proc.new {|...|}
proc {|...| }
lambdaの記法
->(...}{ }
lambda { |...| }
aliasを定義する時の記法
# カンマ不要
alias new_method old_method
alias :new_method :old_method
alias $new_method $old_method
# カンマが必要、文字列かシンボルのみ
alias_method "new_method", "old_method"
alias_method :new_method, :old_method
定数探索
- レキシカルスコープ→スーパークラスの順に探索する
- ネストされている時は内側から外側に順番に探索
- トップレベルの定数から探索したい時は左辺に
::
を付ける
メソッド探索
- 基本的には自クラス→なければスーパークラスを探索する
- prependされているモジュールがある時
prependされたモジュールのメソッド→自クラスのメソッド→スーパークラスのメソッド
- includeされているモジュールがある時
自クラスのメソッド→includeされたモジュールのメソッド→スーパークラスのメソッド
Refinement
- usingを定義するとスコープ内でのみ有効。スコープを抜けると無効になる。
- usingより後に同名のメソッドが定義されたとしてもusingが有効なスコープ内である限りusingが使用される
- メソッド内ではusingは定義できない
undef(未定義)
# 識別子 or シンボル
undef hoge
undef :hoge
# シンボル or 文字列
undef_method :hoge
undef_method "hoge"
メソッドをundefした場合の挙動
- undefを呼び出したクラスとそのサブクラスで未定義にしたメソッドの呼び出し不可になる
- スーパークラスのメソッドには影響しないが、サブクラスでそのメソッドが「未定義」となっているのでスーパークラスの同名メソッドを探索しない。よってスーパークラスに同名メソッドがあっても
NoMethodError
で終わる。
例外処理
NoMethodError
< NameError
< StandardError
NoMethodError
のスーパークラスはNameError
。
NoMethodError
が発生する場面でNameError
をrescue
していたらそこでresuce
される。
begin
"example".test
rescue NameError => e # ここで捕捉される
puts "NameErrorで捉えました: #{e.class}"
rescue => e # こっちではない
puts "StandardErrorで捉えました: #{e.class}"
end
=> NameErrorで捉えました: NoMethodError
ancestors
- クラス、モジュールのスーパークラスとインクルードしているモジュールを優先順位順に配列に格納して返す。
- レシーバがクラスかモジュールの場合のみ有効。
- レシーバがインスタンスの場合は
NoMethodError
になる。
組み込みライブラリ
$0
はファイル名、$1
以降はグループ化された正規表現にそれぞれマッチした値を返す
%r|(http://www(\.)(.*)/)| =~ "http://www.example.com/"
p $0 => ファイル名
p $1 => "http://www.example.com/"
p $2 => "."
p $3 => "example.com"
p $4 => nil
splat演算子の*
と可変長引数の*
- splat演算子の
*
は配列を展開する - 可変長引数の
*
は引数に配列を指定する時に用いる
splat演算子
- 配列を展開する
def add(x,y)
x + y
end
p add(*[3,2]) # 引数が展開されてadd(3,2)と同じになる
# => 5
可変長引数
- 引数の数が決まっていない場合に使う
- 複数は指定できない
- 他の引数がある場合、可変長引数は最後にしか指定できない
- メソッドは引数なしでも呼び出せる
def test(*args)
args.map(&:itself)
end
# 引数なしで呼び出してもOK, 空配列が返る
p test
=>[]
p test(3,2)
=> [3, 2]
p test([1,2,3,4])
=> [[1, 2, 3, 4]]
p test([1,2],[3,4])
=> [[1, 2], [3, 4]]
出題された問題
覚えている範囲で似たようなコードで記載します。
新試験ならではの新しい問題と、旧試験から出ているであろう問題を分けてみました。
新試験からの出題傾向
filter_map
filter_map
メソッドの結果を選択肢から選ぶ問題です。
難しいものではなく、真の値だけが返ることを押さえていれば答えられました。
array = [30.4, -1.3, 2.76, 1/2r]
p array.filter_map {|n| n >= 0 && n.round }
[実行結果]
# ここを問う問題
[30, 3, 1]
←正解の選択肢
[30, -1, 3, 1]
←誤りの選択肢
パターンマッチ
パターンマッチの基本的な記法を問う問題が2問ほど出ました。
1つは公式模擬問題と同じ感じでwhen
やin
の選択肢からin
を選ぶ問題。
もう1つはin
の後ろの記法を問う問題でした。
case {name: 'Bob', age: 20}
in _(1)_ # ここを問う問題
puts "#{name} is #{age} years old"
end
選択肢にはこういう感じのがあり、そういう書き方もあるのか・・?と一瞬迷いましたが1つ選択だったので特に難しくはなかったです。
{:name => name, :age => age}
←誤りの選択肢
{name: name, age: age}
←正解の選択肢
ナンパラ
Hashを使った番号指定パラメータの問題が出ました。
h = {a: "gold", b: "silver"}
puts h.map { puts "#{_1_}, #{_2_}"} # ここを問う問題
[実行結果]
a, gold
b, silver
_1
と_2
の部分を選択肢から選ぶ感じだったと思います。
1: _1
, 2: _2
←正解の選択肢
1: $1
, 2: $2
←誤りの選択肢
Timeクラスのインスタンスメソッドiso8601
iso8601
で変換したフォーマット形式を当問題でした。
t = Time.strptime("00000024021993", "%S%M%H%d%m%Y")
puts t.iso8601
[実行結果]
# ここを問う問題
こんな感じの選択肢があった気がします。
1993-02-24T00:00:00+09:00
←正解の選択肢
1993-02-24T00:00:00z
←誤りの選択肢
旧試験からの共通の出題傾向
Refinement
Refinementが有効になっている時に同名メソッドを他のメソッドから呼び出す際、どちらのメソッドが呼ばれるのかを問う問題。
class A
def foo
puts "A"
end
def bar
foo
end
end
module M
refine A do
def foo
puts "B"
end
end
end
x = A.new
x.foo # => A
using M
x.foo # => B
x.bar # => _(1)_ # ここを問う問題
「...!?」ってなりましたがAでした。
見直した結果Bにしてしまい間違えました。。。
メソッドの内部で呼び出されているメソッド
print
メソッドの内部で呼び出すメソッドを問う問題が出ました。
class ShoppingList
def initialize
@items = []
end
def add_item(item)
@items << item
end
def _(1)_ # ここを問う問題
@items.map { |e| "- #{e}" }.join("\n")
end
end
list = ShoppingList.new
list.add_item("Milk")
list.add_item("Bread")
list.add_item("Eggs")
print list
公式模擬問題の中でp
とputs
は出てたので覚えていたのですが、print
が出てinspect
とto_s
どっちだっけ。。。となり間違えました。
print
はto_s
が呼ばれますのでto_s
をオーバーライドすることでカスタマイズできます。
メソッド | 呼び出すメソッド |
---|---|
p | inspect |
puts | to_s |
to_s |
メソッドのオーバーライド
class Sample
def _(1)_(name, *args) # ここを問う問題
puts "Ruby #{name}"
end
end
s = Sample.new
s.ruby
s.test
[実行結果]
Ruby ruby
Ruby test
define_method
, const_missing
, method_missing
(とあと何か1つ忘れた)から選ぶ問題でした。
答えはmethod_missing
です。
ヒアドキュメント
ヒアドキュメントのリテラルを問う問題です。
出力された結果から、最初のコードをこんな感じの選択肢から2つ選べというものでした。
<<~EOS
<<EOS.gsub(/ /, "")
<<-EOS.gsub(/ /, "")
<<-EOS
a = _(1)_ # ここを問う問題
Ruby Gold
version 3
EOS
puts a
(実行結果)
Ruby Gold
version 3
1はすぐ選べたのですがヒアドキュメントにgsub
メソッドを使っての記法は初めて見たので迷いました...
答えは1と3です。
<<EOS
は末尾をインデントできないので2はエラーになり、4の場合は出力結果が異なる(インデントされた状態で結果が出力される)ので誤りです。
こういう初めて見る問題も落ち着いて考えればわかるものが多かった気がします。
その他
- FloatとRationalの演算で結果のクラスが何になるかを問う問題
- 実行結果からsplat演算子と可変長引数の位置を問う問題
- 親クラスのprivateメソッドをサブクラスでpublicに変更できるかの問題
- 例外処理(複数出た)
- 正規表現(複数出た)
- Proc, lambda(複数出た)
- レシーバの
class
やsuperclass
は何かを問う問題 - 例外を表すクラスに共通するスーパークラスは何かを問う問題(
Exception
) - ENVの記法を問う問題 (
ENV['test']
という感じで文字列)
など
出なかった問題
- class_eval, instance_eval
- Fiber
- Thread
- Rubyオプション
など
出題傾向の割合
完全に感覚ではありますが、こんな感じの割合になりそうです。
- 新試験から新しく出題されている問題で、かつ難易度が公式模擬問題と同等レベルの問題 : 20%
- 旧試験の時から出ている問題で、かつ難易度が合格教本やRExレベルの問題 : 70%
- 旧試験の時から出ている問題で、合格教本やRExで見かけない(難しいor珍しい)問題 : 10%
何を間違えたかは不明なのですが、自信がない問題はほとんど旧試験の時からの出題範囲のものでした・・・。
逆に新試験からの出題範囲はひねった問題は特になく、公式模擬問題からそのままだったり基本的な文法だけだったので「これ知らないんだけど!?」っていう驚きの問題はなかったです。
Ruby技術者認定試験を受けた感想
Silverの時は直前まで暗記を必死にした記憶があり、短期記憶に頼った部分も多かったように思います。
覚える量で言えばSilverの方が多かった印象です。
Goldはロジックを理解して答えることが求められるため、記憶力はそこまで使わなかったように思います。
実務で曖昧だった部分をまとめて学ぶことができ、コードの可読性が上がり見通しが良くなったと思います。
あとはお恥ずかしながらRubyとRailsを混同していた部分もあり、「これってRailsじゃなくてRubyのメソッドだったんだ」といった発見もあったりして面白かったです!
試験時間自体は90分あって余ると思うので、余った時間に見直しが必須です。
特に問題文をよく読み直して、「適切なものを選択」「不適切なものを選択」「2つ選択」などを読み間違えないように注意しましょう。
どうしても分からない問題は一旦仮で答えて旗を立ててすぐ次の問題に行くのが良いです。
一通り解いてから見直しに入ると、他の問題からヒントを得てさっき分からなかったのが分かるようになっていたりもするので見直しに時間をかけた方が良さそうです。
反省点
時間の兼ね合いもあり試験前にはできなかったですが、これをやっておけばさらに良かったかもと思う点を挙げてみます。
ここまでやっておけば恐らくもっと安心感のある点数が取れそうな気がします。
- メタプログラミングRubyを読む(メタプロ的な理解が浅かった)
- チェリー本の改訂版を読む(改訂前のチェリー本+ @jnchito さんのQiitaを大変参考にさせていただきました)
- RExで安定して9割以上取れるようにしておく(8割後半くらいが平均だった)
最後に
しっかり対策していけば新しい出題範囲も怖がる必要はなさそうです。
この記事を読んで少しでも受験するモチベーションやきっかけになれば大変幸いです。
参考にさせていただいた記事等