第55回 Ruby/Rails 勉強会@関西で初級者向けレッスンやってきた

初級者向けレッスンを担当したので、以下スライドを解説。*1 *2

クラスとは


クラスを作ってみよう

class Person; end

obj = Person.new
  # => #<Person:0x10102718>

obj.class           # => Person
Person.superclass   # => Object


  • Person という名前のクラスを作る。
  • class から end までがクラス。
  • クラス名は大文字で始める (キャメルケース)

属性を持たせてみよう

class Person
  def initialize name
    @name = name
  end
end

matz = Person.new('matz')
  # => #<Person:0x10138598 @name="matz">


  • initialize は特別なメソッド
  • def から end がメソッド

属性にアクセスしてみよう

class Person
  attr_reader :name
end

matz.name       # => "matz"


attr_writer
セッター
attr_reader
ゲッター
attr_accessor
セッター&ゲッター

変数・定数のおさらい

ローカル変数
person
インスタンス変数
@person
クラス変数
@@person
グローバル変数
$person
定数
Person


  • 最初の数文字を見れば区別できる
  • クラス名は定数

属性を増やしてみよう

class Person
  def initialize name, born = nil
    @name, @born = name, born
  end
  attr_accessor :born
end

matz.methods.map(&:to_s).grep(/born/)
  # => ["born", "born="]


  • initialize メソッドを変更
  • attr_accessor で born メソッドと born= メソッドが生成された

属性を増やしてみよう (2)

  • アクセスしてみる
matz.born = Time.local(1965, 4, 14)
dhh = Person.new('dhh',
        Time.local(1979, 10, 15))

matz.born
  # => 1965-04-14 00:00:00 +0900
dhh.born
  # => 1979-10-15 00:00:00 +0900


  • 代入のような表記で born= メソッドが呼ばれる

メソッドを作ってみよう

class Person
  def age
    (Time.now.strftime('%Y%m%d').to_i -
     @born.strftime('%Y%m%d').to_i) /
      10000
  end
end
matz.age    # => 47
dhh.age     # => 32


  • メソッド名は小文字で始める (スネークケース)
  • return は省略可能
    • 最後に評価した式の値がメソッドの値になる

メソッドを上書きしてみよう

matz.to_s  # => "#<Person:0x10138598>"

class Person
  def to_s
    "#{@name}(#{age})"
  end
end
matz.to_s  # => "matz(47)"
dhh.to_s   # => "dhh(32)"


  • to_s はオブジェクトの文字列表現
    • 未定義なら Object#to_s が実行される*3

インスタンスを比較してみると……

person = Marshal.load(Marshal.dump matz)

person == dhh   # => false
person == matz  # => false # おかしい


  • 深いコピー
    • Marshal.dump して Marshal.load する
  • == はメソッド
    • Person#== を定義しても良いが……
    • ==, !=, >, <, >=, <= 別々に定義するの面倒

順序を決めよう

class Person
  include Comparable
  def <=> o
    @name <=> o.name
  end
end
person == matz  # => true
person == dhh   # => false
matz > dhh      # => true


  • <=> 演算子で順序を決定
  • Comparable で ==, != , >, <, >=, <= を生成

Array#sort してみよう

  • 順序が決まれば sort できる
people = [matz, dhh]

people.sort  # => [dhh(32), matz(47)]

Array ときたら、次は ……

Hash のキーにしてみると……

値に入れてもおもしろくないので

h = {matz => "Ruby", dhh => "Rails"}

h[matz]     # => "Ruby"
h[dhh]      # => "Rails"

key = Marshal.load(Marshal.dump matz)

key == matz # => true
h[key]      # => nil # おかしい


  • 深いコピーをしたオブジェクトをキーにすると
    • 値が取り出せない!

hash 値を計算しよう

class Person
  def hash
    code = 17
    code = 37 * code + @name.hash
    code = 37 * code + @born.hash
  end
end
matz.hash   # => -22068619118
dhh.hash    # => 14923733106


  • 計算方法はハチドリ本に書いてあった*4
    • 出所は Effective Java らしい

eql? を上書きしよう

  • hash 値がぶつかっていないか調べる必要がある
  • Hash クラスは eql? でキーが正しいか判断する
class Person
  def eql? o
    return false unless @name.eql? o.name
    return false unless @born.eql? o.born
    true
  end
end
key.eql? matz   # => true
key.eql? dhh    # => false


  • a.eql? b が真の場合 a.hash == b.hash であること
    • そのように hash, eql? を定義する

Hash にアクセスしてみよう

h = {matz => "Ruby", dhh => "Rails"}

h[matz]     # => "Ruby"
h[dhh]      # => "Rails"

h[key]      # => "Ruby"


  • 最初に作った h (Hash) は Person#hash を定義する前に作成したもの
  • 格納位置がおかしいので作り直す*5

等値性のおさらい

==
内容が等しいか?
===
case 式で使用
eql?
Hash クラスが使用
equal?
同一オブジェクトか?


アクセス制御してみよう

オマケ

  • public
  • protected
  • private
class Person
  protected :born
end
matz.born
# ~> protected method `born' called for matz(47):Person (NoMethodError)


  • protected なメソッドを呼ぶと例外が発生する

今日 話さなかったこと

  • 委譲と継承
  • モジュール
  • クラス変数・クラスメソッド
  • Range の始点と終点

上記を調べて初級者を抜け出そう。

まとめ

  • クラスの作り方
    • 属性・アクセサ
    • メソッド
  • オブジェクトの等値性

以上、クラスを作る際の決まり事を紹介した。

*1:スライドだけ欲しい人は直接どうぞ。http://higaki-it.jp/ruby/55/slide.pdf

*2:コードが必要な人は gist からどうぞ。

*3:Kernel モジュールで定義されている

*4:会場の @nayutaya さんによると [@name, @born].hash とするのが一般的らしい

*5:会場の @no6v さんによると h.rehash するといいらしい