Open5

プロを目指す人のためのRuby入門メモ

meimeimeimei

2章

ダブルクウォートとシングルクウォートの挙動の違い

ダブルクウォート シングルクウォート
改行文字(\n)が改行として認識される 改行文字(\n)がただの\nという文字列として認識される
式展開が使える 式典会が使えない

演算子

  • Rubyには前置/後置演算子がない
     - +=-=は使える

丸め誤差対策

  • Rational(有理数)クラスを使う
(通常) 0.1 * 0.3 #=> 0.30000000000000004
(Rational使用) 0.1r * 0.3r #=> (3/10)
  • 変数に値が入っている場合は、 rationalize メソッドを呼ぶ
a = 0.1
b = 3.0
a.rationalize * b.rationalize #=> (3/10)
meimeimeimei

4章

配列に初期値を設定する場合の注意点

配列に初期値を設定する場合、第2引数に初期値を指定する方法と、ブロックで初期値を渡す方法で挙動が異なりうる(文字列などミュータブルな場合)

  • 第2引数に初期値を指定する方法
a = Array.new(3, 'hoge')
a[0].upcase!
# 同じ文字列オブジェクトを参照するため、配列の要素すべて変更されてしまう
a #=> ["HOGE", "HOGE", "HOGE"]
  • ブロックで初期値を渡す方法
a = Array.new(3) { 'hoge' }
a[0].upcase!
# 1番目の要素のみ変更される
puts a #=> ["HOGE", "hoge", "hoge"]

do...endと{}の結合度の違い

基本的には do...end{} は書き換え可能だが、do...end より {}のほうが結合度が強い

  • 以下の場合には、構文エラーになる
a = [1, 2, 3]
a.delete 5 { 'Not Found' } # 5 { 'Not Found' }と解釈され、5にブロックが渡せずエラー
  • 解決版
a = [1, 2, 3]
a.delete(5) { 'Not Found' } # ()で囲む
meimeimeimei

5章

メソッドの引数にキーワード引数を使う

メソッドの引数が何を表しているかわかりづらいとき、キーワード引数 を使うと、何を表しているかわかりやすくなる

# わかりづらい例
def buy_coffee(menu, syrup, milk)
    # コーヒーを購入
    if syrup
        # シロップをつける
    end
    if milk
        # ミルクをつける
    end
end

buy_coffee('black', true, false)

# わかりやすい例
def buy_coffee(menu, syrup: true, milk: true) # デフォルト値を指定しない場合は (menu, syrup:, milk:) 
    # コーヒーを購入
    if syrup
        # シロップをつける
    end
    if milk
        # ミルクをつける
    end
end

buy_coffee('black', syrup: true, milk: false)
# デフォルト値と同じ値を渡す場合省略可能
buy_coffee('black')
meimeimeimei

7章

privateメソッドはレシーバを指定できない

# 例1
class Person
    private

    def greet
        "Hi!"
    end
end
person = Person.new
person.greet
#=> レシーバ `person` を指定できないので `NoMethodError`

# 例2
class Person
    def greet
        "Hi, my name is #{self.name}!"
    end

    private

    def name
        'Bob'
    end
end
person = Person.new
person.greet
#=> `greet` メソッド内で `self` というレシーバを指定して `name` メソッドを呼び出そうとしているため `NoMethodError`

privateメソッドはサブクラスでも呼び出せる

class Car
    private

    def name
        'A great car'
    end
end

class Taxi < Car
    def to_s
        "name: #{name}"
    end
end

taxi = Taxi.new
taxi.to_s
#=> `to_s` メソッドの内部でスーパークラスのprivateメソッド `name` を呼び出しているがエラーにならない

※オーバーライドも可能

クラスメソッドは private キーワードの下に書いてもprivateにならない

  • private キーワードの下にクラスメソッドを書く場合、privateにならない
class Car
    private

    def self.name
        'A great car'
    end
end
Car.name #=> "A great car"
  • クラスメソッドをprivateにしたいのであれば、 class << self を使用する or private_class_method を使う
# `class << self` を使用する場合
class Car
    class << self
        private

        def name
            'A great car'
        end
    end
end
Car.name #=> NoMethodError

# `private_class_method` を使用する場合
class Car
    def self.name
        'A great car'
    end
    private_class_method :name
end
Car.name #=> NoMethodError

protected メソッド

protected メソッドは、レシーバ付きで そのクラスとサブクラスから呼び出せる
レシーバ付きで というところがprivateメソッドとのちがい

class Car
   attr_reader :model

   def initialize(model, speed)
       @model = model
       @speed = speed
   end

   def faster_than?(other_car)
       other_car.speed < @speed
   end

   protected

   def speed
       @speed
   end
end

ferrari = Car.new('Ferrari', 200)
lamborghini = Car.new('Lamborghini', 220)

# 同じクラスのインスタンスメソッド内であればspeedメソッドが呼び出せる
ferrari.faster_than?(lamborgini) #=> false

# クラス外部からspeedメソッドを呼び出すとエラー
ferrari.speed #=> NoMethodError

定数は再代入できてしまう

class User
  DEFAULT_NAME = 'Taro'
  # 再代入して定数の値を変更する
  DEFAULT_NAME = 'Jiro'
end

# 再代入後の値が返る
puts User::DEFAULT_NAME # => "Jiro"

# クラス外部からでも再代入できる
User::DEFAULT_NAME = 'Saburo'
  • 再代入を防ぎたい場合は freeze する必要がある
# クラスをfreeze
User.freeze

# クラス内でfreezeを呼ぶ
class User
    DEFAULT_NAME = 'Taro'
    freeze
end

User::DEFAULT_NAME = 'Shiro' # => RuntimeError

※ただし、定数を上書きする人はまずいないと見て、上記のようにクラスをfeeezeしたり、メソッド内でfreezeを呼ぶことはあまりない

  • ミュータブルなオブジェクトで定数の値の変更を防ぐ方法
class User
  NAME = 'Alice'.freeze
  SOME_NAMES = ['Alice', 'Bob', 'Carol'].map(&:freeze).freeze
end

※真偽値や数値、シンボルなどはイミュータブルな値なので、このような対応は不要

比較メソッドのちがい

equal?
object_id が等しい場合にtrueを返す
==
オブジェクトの内容が等しい場合にtrueを返す
eql?
ハッシュのキーとして2つのオブジェクトが等しい場合にtrueを返す

ダックタイピング

  • オブジェクトのクラスがなんであろうと、そのメソッドが呼び出せればOKとするプログラミングスタイルのこと
  • 「もしもそれがアヒルのように歩き、鳴くのであれば、それはアヒルである」という言葉に由来
def show_title(object)
    puts "The title is <<#{object.title}>>"
end

class Movie
    def title
        'A great movie'    
    end
end

class Book
    def title
        'A great book'    
    end
end

# MovieとBookは無関係なクラスだが、show_titleメソッドはクラスを気にせず使える
movie = Movie.new
show_title(movie) #=> A great movie

book = Book.new
show_title(book) #=> A great book

Rubyにオーバーロードはない

  • 1つのメソッドで、可変長引数をとったり、内部でデータ型を変換することで様々なデータ型を受け取ったりすることができるため、オーバーロードの概念がない
meimeimeimei

8章

  • トップレベルのモジュールやクラスを指定したいときは、クラス名やモジュール名の前に :: をつけると名前の衝突を回避できる
class Work
    def initialize(company, working_hours)
        @company = company
        @working_hours = working_hours
    end
end

module Art
    class Work
        def initialize(title)
            @title = title
            @work_for_company = ::Work.new('Company name', 8) # トップレベルのWorkクラスを参照できる
        end
    end
end