えいのうにっき

a-knowの日記です

【自分用メモ】9年弱ぶりに Ruby をキャッチアップしなおした際に、不意を突かれたポイントリスト

この春頃から、Ruby / Rails を業務として使う・書くことになった。僕は、2016年6月まではソフトウェアエンジニアとして、主にWebアプリケーションのバックエンドを書いていたが、その時使っていたのが Ruby / Rails だったので、そのブランクにして実に9年弱になる。コードを書くこと自体は趣味の個人開発で続けてきてはいたものの、言語が違う(Go)なので、こと「Ruby を書く」ことに関していえば、やはり9年弱のブランクがあると言える。

当時の Ruby の最新は2.3系だったようなのだが、今はというとどうやら 3.4系まで進んでいるようで、そのあまりの差の大きさにクラクラする。これくらいのブランクがあると、「以前 Ruby / Rails を書いていたことがありました」「この9年弱は趣味で Go を書いてました」というのはむしろ弊害のほうが多いのでは?とすら思えてしまう。というわけで(?)、今回、改めて Ruby というプログラミング言語をキャッチアップすべく、「プロを目指す人のためのRuby入門[改訂2版] 言語仕様からテスト駆動開発・デバッグ技法まで Software Design plus」(俗に言う "チェリー本")を読み返すことにした。

現状、一通り通読し終えただけではあるけれど、とても素晴らしい一冊だった。

さらにこの本のありがたいことに、執筆当時は3.0系を前提として詳細な解説をしてくれてるのだけれど、その後のバージョンアップによって発生した差分についてはサポートサイトでカバーしてくれているので、それについても確認を行った。具体的には以下。

qiita.com

qiita.com

qiita.com

qiita.com

以下に、この本を読みながら「あ〜〜あったなぁこれ」「こんなのあったか?」「確かあったような気もするな......」「今はこんな書き方できるのか」「これの使い所が思い浮かばんのだが。。」などの、様々な感情を覚えた・不意を突かれたポイントを、自分用のメモとして(あとは、上記のような属性の人間が久々に Ruby を触ると、どういう点で不意を突かれるのかというのは面白そうだと思ったので)残しておきたい。

基礎

  • ++ -- はない。 += -= を使う
  • 定数
    • メソッド内にスコープを限定した定数は定義できない。
    • 定数には再代入が可能。
  • グローバル変数は $ で変数名を始める。クラスの内外問わず、プログラムのどこからでも代入、参照が可能。
  • Rational(有理数)クラスとBigDecimalクラス
0.1r * 3.0r #=> (3/10)
0.1r * 3r == 0.3 #=> true

a = 0.1
b = 3.0
a.rationalize * b.rationalize #=> (3/10)

(BigDecimal("0.1") * BigDecimal("3.0")).to_f #=> 0.3
  • Ruby の if文は最後に評価された式を戻り値として返す
  • Ruby で真偽値の偽として扱われるのは nil か false のみのため、そのことを活用し、式の戻り値や真偽値が確定した時点で評価が終了されることを活用する場面がある
  • case文
    • break のようなキーワードを用いなくても、次の when 節が実行されたりすることはない
    • when節に複数の値を指定し、どれかに一致すれば処理を実行する、という条件分岐を書ける
  • ! の付くメソッドが常に破壊的変更をおこなう、というわけではない。
    • !がつかないメソッドよりも危険、という意味。
    • 破壊的メソッドと非破壊的メソッドの2種類が存在する場合には、前者に ! が付く。
  • エンドレスメソッド定義(1行メソッド定義)
def greet = 'Hello!'
  • require_releative : 自ファイルからの相対パスで読み込むファイルを指定するときに使用
    • require を使って、実行時のカレントディレクトリからの相対パスで指定することもできるが、カレントディレクトリによって変わってくるため非推奨
  • pp メソッドを使えば複雑なオブジェクトの内容を見やすく整形して出力してくれる

配列・繰り返し処理

  • << を使うと配列の最後に要素を追加できる
  • map メソッド: 各要素に対してブロックを評価した結果を新しい配列にして返す
  • & とシンボルを使うことで、ブロックを使うメソッドは簡潔に書くことができる(以下の条件が揃ったときのみ。)
    • ブロックパラメータが1個だけ
    • ブロックの中で呼び出すメソッドに引数がない
    • ブロックの中では、ブロックパラメータに対してメソッドを1回呼び出す以外の処理がない
['ruby', 'java', 'python'].map { |s| s.upcase }
['ruby', 'java', 'python'].map(&:upcase)
# 1. `&:upcase` はシンボルの `:upcase` に対して `to_proc` メソッドを呼び出す。
# 2. シンボルの `:upcase` が Proc オブジェクトに変換され、 `map` メソッドにブロックとして渡される。
# 3. 2. で作った Proc オブジェクトは `map` メソッドから配列の各要素を実行時の第1引数として受け取る。第1引数は `upcase` メソッドのレシーバとなる。つまり、配列の各要素に対して `upcase` メソッドを呼び出すことになる。
# 4. `map` メソッドは Proc オブジェクトの戻り値を順に新しい配列に詰め込む。
# 5. 3. と 4. の組み合わせにより、配列の各要素が大文字に変換された新しい配列が `map` メソッドの戻り値になる。
  • 範囲オブジェクト
    • 最初の値..最後の値 (最後の値を含む)
    • 最初の値...最後の値 (最後の値を含まない)
    • 最初の値.. (最初の値以上を表す範囲オブジェクト)
    • ..最後の値 (最後の値以下を表す範囲オブジェクト)
  • * を使った多重代入のパターン
e, *f = 100, 200, 300
f #=> [200, 300]
e, * = 100, 200, 300 # *の後ろの変数名を省略
e,  = 100, 200, 300 # ,で終わっても同様に無視
a, *b, c, d = 1, 2, 3, 4, 5
b #=> [2, 3]
a, *b, c, d = 1, 2, 3
b #=> []
  • 配列の展開
a = []
b = [2, 3]
a.push(1)
a.push(*b)
a #=> [1, 2, 3]
jp = ['japan', '日本']
country = '日本'
case country
when *jp
  'こんにちは'
end
  • 自分で定義するメソッドで可変長引数を使う場合
def greet(*names)
  "#{names.join('')、こんにちは"
end
  • % 記法で文字列の配列を簡潔に作る: %w! apple melon orange!
  • 添字付きの繰り返し処理: `hoge.each_with_index { |v, i| puts "#{i}: #{v}" }
  • with_index メソッドを用いた添字付きの繰り返し処理: `list.map.with_index { |v, i| puts "#{i}: #{v}" }
    • with_index は Enumerator クラスのインスタンスメソッド。
  • 2次元配列に対してシンプルに繰り返し処理を行う
dimensions = [
  [10, 20],
  [30, 40],
  [50, 60],
]
dimensions.each do |length, width]
    p length * width
end
  • 番号指定パラメータ: 明示的にブロックパラメータを指定する代わりに、パラメータの順番に応じて _1 から _9 までの番号を使うことができる・ _1 については it も同じ役割を持つ。
['1', '2', '3'].map { _1.rjust(3, '0')}

['japan', 'us', 'italy'].map.with_index { [_2, _1] }
#=> [[0, "japan"], [1, "us"], [2, "italy"]]
  • 数値を1つずつ増やしながら/減らしながら処理をしたい場合: upto メソッド / downto メソッド
10.upto(14) { |n| a << n }
a #=> [10, 11, 12, 13, 14]
  • begin ... end で囲むと最低1回は実行される
begin
  a << 1
end while false
a #=> [1]
  • break に引数を渡すと、while や for 文の戻り値になる。
  • 一気に外側のループまで抜けたい場合: throw メソッドと catch メソッドを使う。
    • throw メソッドに第2引数を渡すと、catch メソッドの戻り値になる。
    • Ruby のこれらのメソッドは例外処理とは関係ない。
catch タグ do
  # do something
  throw タグ
end
  • breakreturn の違い
    • break : 繰り返し処理から抜ける
    • return : メソッドから抜ける
  • 繰り返し処理をやり直したい場合: redo

ハッシュやシンボル

  • ハッシュリテラルでは値を省略することもできる
name = 'Alice'
age = 20
h = {name:, age:} # {name: name, age: age} と書いたのと同じ
  • シンボル
    • Ruby の内部では整数として管理される
    • 同じシンボルであれば全く同じオブジェクトである
    • シンボルはイミュータブルなオブジェクト
  • 状態を変数に入れて管理するなどの場合、シンボルを使うことで可読性のみならず処理効率も向上させられる
  • ** をハッシュの前に付けると、ハッシュリテラル内でほかのハッシュの要素を展開できる。
    • 代わりに merge メソッドを使うこともできる。
h = { us: 'dollar', india: 'rupee' }
{ japan: 'yen', **h } #=> { :japan => 'yen',  :us => 'dollar', :india => 'rupee'}
{ japan: 'yen' }.merge(h) #=> { :japan => 'yen',  :us => 'dollar', :india => 'rupee'}
  • メソッドに定義されていない任意のキーワード引数をハッシュで受け取る
def buy_burger(menu, drink: true, potato: true, **others)
  #
end
  • ** を使ってハッシュをキーワード引数に明示的に変換して渡す: buy_burger('fish', **params)
  • メソッドへの最後の引数がハッシュであれば、ハッシュリテラルの {} を省略可能。
  • 存在しないキーが指定されたときに nil 以外の値を返すハッシュを作りたいときは、 Hash.new の引数に戻り値のデフォルト値を指定する: h = Hash.new('hello')
  • Hash.new にブロックを与えると、ブロックパラメータとして「ハッシュ自身」と「見つからなかったキー」が渡される。このブロックパラメータを使って、ハッシュにキーとデフォルト値を同時に設定するケースも多い。
h = Hash.new { |hash, key| hash[key] = 'hello' }
  • 通常 SyntaxError となるようなシンボルを作る(シングルクォートで囲む)
:'12345'
:'ruby-is-fun'
:'ruby is fun'
:'()'

正規表現の利用

  • 正規表現オブジェクトを作成する方法
Regexp.new('\d{3}-\d{4}')
/https:\/\/example\.com/
%r!https://example\.com!
  • 正規表現オブジェクト作成時のオプション: /正規表現/オプション or Regexp.new(正規表現, オプション)
    • Regexp.new に渡すオプションは、 Regexp::IGNORECASE のような指定に加え、 'i' のような文字列でも指定可能。
  • =~ : 文字列中の最初にマッチした位置(0以上の数値)が返る・マッチしなかった場合は nil
  • キャプチャにメタ文字を使って名前をつける
m = /(?<year>\d+)(?<month>\d+)(?<day>\d+)/.match(text)
m[:year]
m[:month]
m[:day]
if /(?<year>\d+)(?<month>\d+)(?<day>\d+)/ =~ text
  puts "#{year}/#{month}/#{day}"
end
  • Regexp.timeout で正規表現のタイムアウト秒数を指定できる。これはグローバルな設定だが、一部の正規表現だけにタイムアウト秒数を指定したい場合は Regexp.newtimeout キーワードを指定する。
  • マッチした結果が代入される組み込み変数
    • $~ : MatchDataオブジェクト
    • $& : マッチした部分全体
    • $1 $2 $3... : 1,2,3番目のキャプチャを取得する
    • $+ : 最後のキャプチャ文字列を取得する
  • match? メソッドは組み込み変数を書き換えない
    • マッチすれば true しなければ false を返す
  • scan メソッド: 引数で渡した正規表現にマッチする部分を配列に入れて返す
    • `'123 456 789'.scan(/\d+/) #=> ["123", "456", "789"]
    • 正規表現に () があると、キャプチャされた部分が配列の配列になって返る。
      • '1977年7月17日 2021年12月31日'.scan(/d+)年(\d+)月(/d+)日/) #=> [["1977", "7", "17"], ["2021", "12", "31"]]
  • [] に正規表現を渡すと、文字列から正規表現にマッチした部分を抜き出す。
text = '郵便番号は123-4567です'
text[/\d{3}-\d{4}/] #=> "123-4567"
  • split に正規表現を渡すと、マッチした文字列を区切り文字にして文字列を分解し、配列として返す。
text = '123,456-789'

text.split(/,|-/) #=> ["123, "456", "789"]
  • gsub の置換ルールをハッシュとして第2引数で渡すことができる
text = '123,456-789'
hash = { ',' => ':', '-' => '/' }
text.gsub(/,|-/, hash) #=> "123:456/789"

クラス

  • クラスメソッドを定義する方法
class クラス名
  def self.クラスメソッド
    # do something
  end
end
class クラス名
  class << self
    def クラスメソッド
      # do something
    end
  end
end
  • 継承元を指定せずに作成したクラスはデフォルトで Object クラスを継承している
  • super : スーパークラスの同名メソッドを呼び出せる・スーパークラスとサブクラスで引数の数が同じだった場合は、引数なしの super を呼ぶだけで、自身に渡された引数をすべてスーパークラスに引き渡すことができる。
    • そのメソッドでは引数を使わないのであれば * で可変長引数として任意の数の引数を受け取り、そのまま super を呼び出すことで引数もそのまま渡すことができる
    • ただし、 super() と書いた場合は「引数0個でスーパークラスの同名メソッドを呼び出す」という意味になるので注意。
  • privateメソッド
    • クラスの外からは呼び出せず、クラスの内部でのみ使えるメソッド(レシーバが self に限定されるメソッド)。サブクラスでも呼び出せる。
    • クラス内で private キーワードを書くと、そこから下で定義されたメソッドは private メソッドになる。もしくは、既存のメソッド名を private キーワード(メソッド)に渡すことでも、そのメソッドを private メソッドにすることもできる。この場合、その下に定義しためそっどの可視性は変更されない。
    • privateなアクセサメソッドを定義したいときは private attr_accessor :name などと一行で書ける。
    • クラスメソッドを private にしたい場合は、 class << self 構文を使う・もしくはクラスメソッドの定義後に private_class_method で可視性を変更する。
class User
  class << self
    private

    def hello
      'Hello!'
    end
  end
end
class User
  def self.hello
    'Hello'
  end
  private_class_method :hello
end
  • protectedメソッド: そのメソッドを定義したクラス自身と、そのサブクラスのインスタンスメソッドからレシーバ付きで呼び出せる。
  • 定数やクラスの可視性を private にしたい場合は、 private_constant を使う。
class Foo
  class Bar
    #
  end

  private_constant :Bar
end
  • サブクラスでメソッドをオーバーライドすると、可視性も同時に変更できる。
  • 特異メソッド: 特定のオブジェクトにだけ紐づくメソッドのこと。「クラスメソッド」も、「特定のクラスの特異メソッド」。
alice = 'I am Alice.'
bob = 'I am Bob.'

# aliceのオブジェクトにだけ、shuffleメソッドを定義する
def alice.shuffle
  chars.shuffle.join
end

alice.shuffle #=> NoMethodErrorにならない
  • Classクラスのスーパークラスは Module クラス。

モジュール

  • 名前空間を切るためにも使用される
  • クラスであるモジュールを include すると、モジュールで定義したメソッドがインスタンスメソッドとして呼び出せるようになる(ミックスイン)。
  • extend を使うと、モジュール内のメソッドをそのクラスの特異メソッド(クラスメソッド)にすることができる。
  • クラス名.extend モジュール名 の形式でモジュールを include/extend することも可能。
  • ミックスインとしても使えて、なおかつモジュールの特異メソッドとしても使えるような定義の仕方: module_function メソッドを使う。
    • モジュール関数: ミックスインとしてもモジュールの特異メソッドとしても使えるメソッドのこと。
      • モジュール関数は モジュール名.#メソッド名 と書くこともある。
  • 可視性は include したクラスでも引き継がれる。
  • prepend でもミックスインすることができるが、これを使うとミックスインしたクラスよりも先に prepend したモジュールからメソッドが検索される。
    • prependするモジュール側で super を用いて同名のメソッドを実装することで、ミックスインするクラスの同名メソッドを直接書き換えることなく、かつ、元の実装を活かしながら振る舞いを変更することができる。
  • クラスやモジュールがどの順番でメソッド探索されるかを確認する: Classオブジェクトに対して ancestors メソッドを呼び出す
  • Enumerableモジュールを include するためには、include先のクラスで each メソッドが実装されていることが必要条件となる。
  • Comparable モジュールを include できるようにするための条件は、include 先のクラスで <=> 演算子(宇宙船演算子 / UFO演算子)を実装しておくこと。
    • <=> 演算子の要件( a <=> b を例に)
      • aがbより大きいなら正の整数を返す
      • aとbが等しいなら0を返す
      • aがbより小さいなら負の整数を返す
      • あとbが比較できない場合はnilを返す
  • refinements: オーバーライドや独自のメソッドの追加などの独自の変更の有効範囲(スコープ)を限定することができる。refine メソッドを使って refinements を適用するクラスを指定し、refinements を有効にするためには using メソッドを使う。
module StringShuffle
  refine String do
    def shuffle
      chars.shuffle.join
    end
  end
end

class User
  using StringShuffle

  def shuffled_name
    @name.shuffle
  end
end

例外処理

  • 例外処理の最も単純な構文
begin
  # 例外が起きうる処理
rescue
  # 例外が発生した場合の処理
end
  • rescue 節で retry 文を実行することで、 begin 節の最初からやり直すことができる
  • rescue 節に何もクラスを指定しなかった場合に捕捉されるのは StandardError とそのサブクラス。
    • 他の言語でいうところの Exception クラスが Ruby でいうところの StandardError クラスと同じ扱いだったりすることがある
    • 通常のコードで捕捉するのは `StandardErrorP クラスか、そのサブクラスに限定すべき。
  • raise メソッド
    • 文字列だけを渡したときは RuntimeError クラスの例外が発生する
    • 第1引数に例外クラスを、第2引数にエラーメッセージを渡すと任意の例外クラスで例外を発生させられる
    • 例外クラスのインスタンスを渡すこともできる
  • full_message メソッド: クラス名、エラーメッセージ、バックトレースの3つを一度に取り出せる
  • rescue は修飾子としても使える。 例外が発生し得る処理 rescue 例外が発生したときの戻り値
    • 捕捉する例外クラスを指定することはできない。
  • rescue 節内で raise メソッドを使うこともできる。その際 raise メソッドの引数を省略すると、 rescue 節で捕捉した例外をもう一度発生させることができる。
  • 最後に発生した例外は組み込み変数の $! に格納される / バックトレース情報は $@ に格納される。

yield と Proc

  • yield を使うことで、渡されたブロックを実行できる。
def greet
  puts 'おはよう'
  yield
  puts 'こんばんは'
end

greet do
  puts 'こんにちは'
end

#=> おはよう
# こんにちは
# こんばんは
  • block_given? メソッド: ブロックが渡されたかどうかを確認する
  • ブロックを引数として明示的に受け取る(ただしブロックの引数はメソッド定義につき1つしか指定できない・他にも引数がある場合には、ブロックの引数は必ず最後に指定):
def メソッド(&引数)
  # `&` の役割は Proc オブジェクトをブロックと認識させるだけではなく、厳密には、
  # 右辺のオブジェクトに対して `to_proc` メソッドを呼び出し、その戻り値として得られた Proc オブジェクトを、ブロックを利用するメソッドに与える、ということをしている。
  引数.call
end
  • 他のメソッドにブロックを渡すだけなら、メソッド定義時に引数名を付けなくても良い。
def foo(&)
  bar(&)
end

def bar(&b)
  b.call
end
  • Procオブジェクトの作成:
add_proc = Proc.new { |a = 0, b = 0| a + b }
add_proc = proc { |a, b| a + b }
# 以下2つは Proc クラスのオブジェクトではあるが、ラムダ。( `->` はラムダリテラル
->(a, b) { a+ b }
lambda { |a, b| a + b }
  • ラムダは Proc.new よりも引数の数のチェックが厳密。
  • Procオブジェクトをブロックの代わりに渡す際には、変数名の戦闘に & を付ける。普通のオブジェクトとして受け渡しする場合には、渡す側/受け取る側両方において & の指定は不要。
  • Ruby のブロックや Proc オブジェクトはクロージャとして振る舞う: 生成時のコンテキスト(変数情報など)が保持され続ける

パターンマッチ

  • case / when ではなく、 case / in を使う
casein パターン1
  パターン1にマッチしたときの処理
in パターン2
  パターン1にマッチせず、パターン2にマッチしたときの処理
else
  パターン1にも2にもマッチしなかったときの処理
end
  • else 節がなく、どの条件にもマッチしなかった場合には例外 NoMatchingPatternError が発生する
  • in 節で、事前に定義された変数とのマッチをさせたい場合にはピン演算子 ^ を用いる。インスタンス変数やクラス変数、グローバル変数も渡すことができる。式も渡すことができるが、その場合には () で囲む必要がある。
alice = 'Alice'
bob = 'bob'
def charlie = 'charlie'

case name
in ^alice
  #
in ^bob
  #
in ^(charlie)
  #
end
  • ハッシュに対するパターンマッチ
case car
in {name:, engine:, motor:}
  puts "#{name} / #{engine} / #{motor}"
in {name:, engine:}
  puts "#{name} / #{engine}"
in {name:, motor:}
  puts "#{name} / #{motor}"
end
  • 各要素のマッチ判定には === が使われるため、クラス名(クラスオブジェクト)や範囲オブジェクトを in 節で指定して「そのクラスのインスタンスか?」「その範囲に収まる値か?」といった条件を指定することもできる。
case ['Alice', 999, 3]
in [String, 10.., n]
  "n=#{n}"
end
#=> "n=3"
  • hashパターンはハッシュの各要素が in 節で指定したパターン(キーと値 / キーのみ)に部分一致すればマッチしたと判定される。
case {name: 'Alice', age: 20, gender: :female}
in {name: 'Alice', gender:}
  "gender=#{gender}"
end
  • ** を使って「任意のキーと値」を指定することもできる。
case {name: 'Alice', age: 20, gender: :female}
in {name: 'Alice', **rest}
  "gender=#{gender}"
end
  • asパターン: パターンマッチでマッチしたオブジェクトを変数に代入する・一番外側に => 変数名 と書くと、マッチしたオブジェクト全体を変数に代入できる
case {name: 'Alice', age: 20, gender: :female}
in {name: String => name, age: 18.. => age}
  #
end

case {name: 'Alice', age: 20, gender: :female}
in {name: String, age: 18..} => person
  #
end
  • alternativeパターン: 2つ以上のパターンを指定し、どれか1つにマッチすればマッチしたと見なす
case {name] 'Bob', age: 25}
in {name: 'Alice' | 'Bob' => name, age:} # :name の値が 'Alice' または 'Bob' で、さらに :age というキーがあればマッチする・さらに、 :name と :age の値をそれぞれ変数 name と age に代入する
  #
end
  • in 節に追加の条件式(ガード式)を追加できる・ガード式の中ではパターンマッチで代入された変数を参照することも可能。
casein パターン if 条件式
  パターンにマッチし、なおかつ条件式が真になった場合に実行する処理
end
  • 1行パターンマッチ: case 節を省略して 評価したい式 in パターン と書くことも可能。マッチすれば true しなければ false が返る。
person = {name: 'Alice', children: ['Bob']} # 右辺の括弧は省略する( `person = name: 'Alice', children: ['Bob']` と書く)ことも可能
if person in {name:, children: [_]}
  # :name と :children をキーにもち、かつ :children が要素1つの配列であれば処理を実行する
end
  • 1行パターンマッチのもう一つの記法: 式 => パターン 主にパターンマッチを使った変数代入を利用するために使う。あたかも左辺の式を右辺の変数に代入しているように見える場合があるため、「右代入」と呼ばれることもある。この記法でのパターンマッチ自体の戻り値は、マッチするとnilが返り、マッチしないと例外 NoMatchingPatternError が発生する。
{name: 'Alice', children: ['Bob']} => {name: , children: [child]}
name #=> "Alice"
child #=> "Bob"
words = 'Ruby is fun'
words.split(' ').map { |word| word.upcase + '!' * 3 }.join(' ') => loudvoice
loudvoice #=> "RUBY!!! IS!!! FUN!!!"
  • Ruby のパターンマッチは独自の変数スコープを作らない。また、パターンマッチ内で新たに定義されたローカル変数はパターンマッチを抜けても使用可能。
  • 自作クラスをパターンマッチ array パターンに対応させるためには deconstruct メソッドを、hash パターンに対応させるためには deconstruct_keys メソッドをそれぞれ定義する。
    • deconstruct メソッドは自分自身の配列表現を戻り値として返すようにする。
    • deconstruct_keys メソッドは自分自身のハッシュ表現を戻り値として返すようにする。
  • クラス名(パターン) または クラス名[パターン] という形式: arrayパターンやhashパターンを利用しつつ、マッチさせたいオブジェクトの形を限定する
  • MatchDataオブジェクト、Timeオブジェクト、Dateオブジェクトもパターンマッチで使える。

便利

  • キーワード引数を指定する際、値を省略できる
chomp = true
text.lines(chomp:) # text.lines(chomp: chomp) と書いたのと同じ
  • nil かもしれないオブジェクトに対して安全にメソッドを呼び出したい場合: &. 演算子
a = nil
a&.upcase #=> nil
  • ||= を使った自己代入(nilガード)
  • !! : 値に対応する真偽値を true または false として得る
  • エイリアスメソッドの定義: alias 新しい名前 元の名前
  • equal? メソッドは object_id が等しい場合に true を返す。
  • === の代表的な用途として、case 文の when 節がある。仮に独自に定義したクラスのオブジェクトをcase文のwhen節の中で使いたい場合は、 === を要件に合わせて再定義する必要がある。
  • メソッドの存在確認: respond_to?
  • gets メソッド: ターミナル上でユーザーの入力を受け付ける。Kernelモジュールで定義されているメソッド。
  • chomp メソッド: 改行文字を取り除く
  • ファイルの読み書きを行う場合は open メソッドにブロックを渡すことで、 ensure 節やクローズ処理を書かずに済ませることができる
  • Ruby2.7以降、メソッドチェーンの行間にコメントを挟んでも構文エラーが発生しなくなった。
  • inject メソッド: たたみ込み演算を行うメソッド。引数は、メソッドに渡すブロックの初回の第1引数に用いられる。
numbers = [1, 2, 3, 4]
sum = numbers.inject(0) { |result, n| result + n)
sum #=> 10
  • tap メソッドはレシーバをそのまま返す。
'#043c78'.scan(/\w/\w/).tap { |rgb| p rgb }.map(&:hex)
#=> ["04", "3c", "78"]
  • ワンライナーを使う場合
    • ワンライナーを使う場合は ruby コマンドに -e オプションを渡す。
    • ワンライナーで初期化処理や終了処理を記述する場合は BEGIN 文や END 文を使う。BEGIN 文はほかのどのコードよりも先に実行され、 END 文はプログラムの終了時に実行される。
$ ruby -e 'BEGIN{$sum=0};[1,2,3,4].each{|n|$sum+=n};END{p $sum}'
  • バッククオートリテラルは、それで囲まれた文字列をOSコマンドとして実行する。
  • send メソッドはレシーバに対して指定したシンボル(または文字列)のメソッドを実行する。
str.send(:upcase) # str.upcase を呼ぶのと同じ
str.send(:split, ',') # str.split(',') を呼ぶのと同じ
  • Data クラスを使うことでイミュータブルなクラスを簡単に定義できる。 == メソッドや deconstruct メソッド、 deconstruct_keys メソッドも自動的に実装される。
  • Data クラスと似たもので Struct クラスがある。
  • Time.new では日時文字列をパースできる。

その他

  • Rubyはすべてがオブジェクト。
  • インスタンス変数は、作成(値を代入)する前にいきなり参照してもエラーにならず、nilが返る
  • メソッド名の表記法
    • インスタンスメソッドを表す場合: クラス名#メソッド名
    • クラスメソッドを表す場合: クラス名.メソッド名 または クラス名::メソッド名
  • Rubyのクラスは変更に対してオープンなため、「オープンクラス」と呼ばれることもある
  • Ruby にはメソッドのオーバーロード(多重定義)という考え方はない。
    • メソッドのなかで引数のクラスをチェックしたり、引数のデフォルト値や可変長引数を用いることでメソッド呼び出し時の引数の個数を柔軟に変えることができることを利用し、オーバーロードと同じようなしくみは実現できる。
  • シングルトンパターン: 「唯一、1つだけ」のオブジェクトを作る手法のこと
  • DateTime クラスは非推奨クラスになっており、日時を扱う場合は Time クラスを使うように推奨されている。
  • Rake: Ruby で作られているビルドツール。
  • Gemfile での '~> 2.17.0' のようなバージョン指定: 悲観的なバージョン指定、「2.17.0 以上かつ 2.18 未満」を指定したことになる。 '~> 2.17' であれば「2.17以上、3.0未満」を指定したことになる。
  • Ruby 3.0 から、プログラム本体に新しい構文を導入することなく、なおかつ人間がなるべく型情報を書かずに済む独自のアプローチとして、「RBS」「TypeProf( typeprof コマンド)」「Steep(形検査のために使う外部の gem)」を採用している
    • 型情報はスクリプト本体(rbファイル)とは別の rbs ファイルに記述する
    • TypeProfは、与えられた Ruby コードを解析し型情報を推定、rbs ファイルを生成する
      • あくまで推定した結果であり、推定に失敗するケースもある。
    • rbsファイルは Ruby の型情報を記述するための言語(RBS言語)で書かれている
    • TypeProf で型情報を自動生成するためには、目的のメソッドやクラスを実行するプログラムが必要になる。解析対象の本体のrbファイルだけだと、TypeProfは型情報を生成できない。テストコードや実行用プログラムが存在しない場合は、開発者がrbsファイルを手書きする必要がある。
  • File.exists?Dir.exists? は削除された
  • 文字列の破壊的変更を行う破壊的メソッドが Ruby 3.4 で警告扱いになった
  • rubyist: ruby に対して単なるお客さん以上の気持を持っている人