:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

クラス

Ruby技術者認定試験の書籍写経メモ

クラス定義

class Moko
  def initialize(a)
    @a = a
  end

  def method1
    @a
  end
end

moko1 = Moko.new(1)
moko2 = Moko.new(2)

moko1.method1
=> 1
moko2.method1
=> 2

クラスは必ず定数に一度は紐付けられるが、その後は煮ようと焼こうと自由である

class Hage
end

hage = Hage
Hage = nil
Object.send(:remove_const, :Hage) # Hage定数完全削除できそうかな

hage.new.class
=> Hage
class Moko
  p 'moko1'
  def method1
    p 'method1'
  end
  p 'moko2'
end
"moko1"
"moko2"
=> "moko2"

Moko.new.method1
"method1"
=> "method1"

インスタンスメソッド

知ってるって?まぁそう言わずに

定義時でも呼び出し時でもカッコは省略できる でもそんな事はしない

def hage a, b
  p a, b
end

hage 1, 2
1
2
=> [1, 2]

仮引数のデフォルト値

基本的にメソッド呼び出し時の引数の数はピッタリ合わないと例外を出す

def hage(a, b)
  p a, b
end
hage(1)
ArgumentError: wrong number of arguments (given 1, expected 2)

def hage(a, b = 2)
  p a, b
end
hage(1)
1
2
=> [1, 2]

キーワード引数

2.0から使えるんですけどね

Hash の辺りも重要

def hage(a:, b:)
  p a, b
end
hage(a: 1, b: 2)
1
2
=> [1, 2]

# ハッシュで引数を渡すが、ハッシュ定義の {} を省略できる、と思えばいい?
hage({a: 1, b: 2})
1
2
=> [1, 2]

デフォルト値もいける

def hage(a: 1, b:)
  p a, b
end
hage(b: 2)
1
2
=> [1, 2]

# デフォルト値を設定していないキーワードを指定していないと例外発生
hage(a: 2)
ArgumentError: missing keyword: b

# キーワード引数に存在しないキーワードを渡すと例外発生
hage(b: 1, c: 3)
ArgumentError: unknown keyword: c

# なかなか厳しい、想定しているキーワードのみの使用を強制される

もちろん1つのハッシュとして引数を引き受けることも出来る、1.9まではこうやって記述していた気がする(一般的かどうかは知らん)

def hage(options)
  p options
end
hage(a:1, b: 2)
=> {:a=>1, :b=>2}
def hage(a:, **c)
 p a, c
end
hage(a:1, b: 2, c: 3)
1
{:b=>2, :c=>3}
=> [1, {:b=>2, :c=>3}]

** は引数のラストにしか書けない

def hage(**c, a:)
end
#=> SyntaxError

デフォルト値とも組み合わせられる

def hage(a: 1, **c)
 p a, c
end
hage(b: 2, c: 3)
1
{:b=>2, :c=>3}
=> [1, {:b=>2, :c=>3}]

仮引数は参照渡し

def hage(obu)
  obu.object_id
end

b = 'bb'
b.object_id
=> 70271490886920
hage(b)
=> 70271490886920

メソッド内で破壊的メソッドを呼ぶと、呼び出し元の変数が破壊される。当然

配列の多重代入みたいな動きをするメソッド呼び出し

この辺は熱い

def moko((a, b), c)
  p [a, b, c]
end

moko([1, 2], 3)
[1, 2, 3]
=> [1, 2, 3]
a, b, *c  = 10, 20, 30, 40, 50
a #=> 10
b #=> 20
c #=> [30, 40, 50]
def hage(a, *b)
  p a
  p b
end

hage(1, 2, 3, 4)
1
[2, 3, 4]
=> [2, 3, 4]

仮引数に渡す実引数で使用すると配列を複数の引数としてメソッドを呼び出せる 引数展開、JavaScriptの Function#apply みたいな動作

as = [1, 2, 3]

def hage(a, *b)
  p a
  p b
end

hage(*as)
1
[2, 3]
=> [2, 3]

# 普通に呼び出すとこうなってしまう
hage(as)
[1, 2, 3]
[]
=> []

インスタンス変数

知ってるって?

object#class

生成元のクラスを返す

class Moko
end

Moko.new.class
=> Moko
Moko.new.class == Moko
=> true

クラス継承

この辺からソースが無駄に長くなるけどがんばって

class Oyaji
  def initialize(a)
    @a = a
  end

  def method1
    @a
  end
end

class Moko < Oyaji
  def initialize(a, b)
    @b = b
    super(a)
  end

  def method2(c)
    @a + @b + c
  end
end

moko = Moko.new(1, 2)
moko.method1
=> 1
moko.method2(3)
=> 6

initializeの継承

class Oyaji
  def initialize
    p 'init!'
  end
end

class Moko < Oyaji
end

Moko.new
"init!"

インスタンスメソッド探索経路の話

インスタンスメソッドについて、上記でこんな感じで説明した

** Rubyインタプリタが、呼び出されたインスタンスメソッドがどこに定義してあったのか探索する経路を知るとRubyが分かった気になれる **

クラスのレイヤとインスタンスのレイヤを分けると次のような感じだろうか

Class界 (抽象的方面)
  Moko

----------------
インスタンス(オブジェクト)界 (具体的方面)
  moko1
  moko2

インスタンスメソッドとインスタンス変数がメモリ上ではどこに存在するかというと

Class界
  Moko
    インスタンスメソッド(method2)

----------------

インスタンス界
  moko1
    インスタンス変数(@b)
  moko2
    インスタンス変数(@b)

では、継承しているクラスならどうなる

インスタンス化は縦に表現してましたが、継承関係は横に表現してます

Class界                     => superclass =>
  Moko ===================================================> Oyaji =========> Object => BasicObject
    インスタンスメソッド(method2)                                                                                インスタンスメソッド(method1)

--------------------------------------------------------------------------------------------------------------------------------

インスタンス(オブジェクト)界 (具体的方面)
  moko1                                                                                                                              oyaji
    インスタンス変数(@a, @b)                                                                                               インスタンス変数(@a)
  moko2
    インスタンス変数(@a, @b)

メソッド探索経路を辿る

↑上の図で

つまり

さらにつまり

クラス継承チェーン

BasicObject
  ↑superclass
Kernel
  ↑変な奴が居る!
Object
  ↑superclass
Oyaji =====> oyajiオブジェクト
  ↑superclass
Moko     =====> mokoオブジェクト

継承チェーンを確認する

Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]

Oyaji.ancestors
=> [Oyaji, Object, Kernel, BasicObject]

Module.ancestors にも書いてある

クラスの包含関係の比較

moko < Oyaji
=> true

moko > Oyaji
=> false

Object < Kernel
=> true

Kernel < BasicObject
=> nil # あれ?????????(後述

Object < BasicObject
=> true

↓こんなゴタクなんか無視してもいいんじゃないのかなーと思っている

継承元ほど、より一般的な性質を持つ(汎化)
↑
↑継承元(スーパークラス: Oyaji)
↓継承先(self: Moko)
↓
継承先ほど、より固有的な性質を持つ(特化)

オブジェクトが持つインスタンスメソッド、インスタンス変数を確認する

オブジェクトが持つインスタンスメソッド

クラス#instance_methods publicメソッドの一覧 + protectedメソッドの一覧
クラス#public_instance_methods publicメソッドの一覧
クラス#protected_instance_methods protectedメソッドの一覧
クラス#private_instance_methods privateメソッドの一覧

引数にfalseを指定すると、スーパークラスに実装してあるメソッドは表示されない。自分自身に実装されているものだけを表示する

Oyaji.instance_methods(false)
=> [:method1]

Moko.instance_methods(false)
=> [:method2]

オブジェクトが持つインスタンス変数

オブジェクト#instance_variables

Oyaji.new(1).instance_variables
=> [:@a]

Moko.new(1, 2).instance_variables
=> [:@b, :@a]

メソッドに別名を付ける

alias式の構文

alias 新メソッド名 旧メソッド名
alias 新グローバル変数名 旧グローバル変数名
class Hage
  def zura1; p 1; end
  def zura2; p 2; end
end

Hage.instance_methods(false)
=> [:zura1, :zura2]

class Hage
  def zura1; p 1; end
  def zura2; p 2; end

  alias :zura3 :zura1
end

Hage.instance_methods(false)
=> [:zura1, :zura2, :zura3]

Hage.new.zura3
1
=> 1

メソッドを取り消す

alias式の構文

undef メソッド名
undef メソッド名, メソッド名
class Hage
  def zura1; p 1; end
  def zura2; p 2; end
end

Hage.instance_methods(false)
=> [:zura1, :zura2]

class Hage
  def zura1; p 1; end
  def zura2; p 2; end

  undef :zura1
end

Hage.instance_methods(false)
=> [:zura2]

Hage.new.zura1
NoMethodError: undefined method 'zura1'

エイリアスが定義されている片方をundefしても、もう片方でアクセスが可能(まさにエイリアスのハードリンクを作るような感じなのかね?)

class Hage
  def zura1; p 1; end

  alias :zura2 :zura1
  undef :zura1
  alias :zura3 :zura2
  undef :zura2
end

Hage.instance_methods(false)
=> [:zura3]

Hage.new.zura3
1
=> 1

method_missing

ようこそ黒魔術の世界へ

↑実は、この本にはここまで書いていない。しかし、こうとしか思えない動作をするのだ。ツッコんで下さいお願いします

いくつか例を出します

class Oyaji
  def hage(*args)
    puts "Oyaji called: hage(#{args})"
  end
end

class Moko < Oyaji
  def method_missing(method_name, *args)
    puts "Moko called: #{method_name}(#{args})"
    super
  end
end

Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]

Moko.new.hage('zura')
Oyaji called: hage(["zura"])
=> nil
class Oyaji
end

class Moko < Oyaji
  def method_missing(method_name, *args)
    puts "Moko called: #{method_name}(#{args})"
    super
  end
end

Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]

Moko.new.hage('zura')
Moko called: hage(["zura"])
NoMethodError: undefined method 'hage'
class Oyaji
  def method_missing(method_name, *args)
    puts "Oyaji called: #{method_name}(#{args})"
    super
  end
end

class Moko < Oyaji
  def method_missing(method_name, *args)
    puts "Moko called: #{method_name}(#{args})"
    super
  end
end

Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]

Moko.new.hage('zura')
Moko called: hage(["zura"])
Oyaji called: hage(["zura"])
NoMethodError: undefined method 'hage'
class Oyaji
end

class Moko < Oyaji
  def method_missing(method_name, *args)
    if method_name == :hage
      return 'hage-!'
    end

    super
  end
end

Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]

Moko.new.hage('zura')
=> "hage-!"

オープンクラス クラスをオープンする

同名のクラスを複数定義してもエラーにはならない。何が起こっているの?

class Moko
  def method1
    p '1'
  end
end

class Moko
  def method2
    p '2'
  end
end

Moko.new.method1
=> "1"
Moko.new.method2
=> "2"
class Moko
  def method1
    p '1'
  end
end

class Moko
  alias :method2 :method1
  undef :method1
end

Moko.new.method1
NoMethodError: undefined method 'method1'

Moko.new.method2
=> "1"

Moko.instance_methods(false)
=> [:method2]
class String
  def hage
    'hage-!'
  end
end

'moko'.hage
=> "hage-!"
class Oyaji
end

class Hage
end

class Moko < Oyaji
end

class Moko < Hage
end
TypeError: superclass mismatch for class Moko

class Moko
  def method
    'ok'
  end
end

Moko.new.method
=> "ok"

Mix-in

モジュールとクラスの違い

モジュールの目的

モジュールを定義してみる

module Hage
  def hage
    @a
  end
end

クラス定義にそっくり過ぎて笑う

モジュールオブジェクトの確認

Hage.ancestors
=> [Hage]

Hage.instance_methods(false)
=> [:hage]

Hage.new
NoMethodError: undefined method 'new' for Hage:Module

生成したモジュールは Module#include や Module#prepend メソッドなどでクラスに取り込むことができる

class Moko
  include Hage

  def initialize(a)
    @a = a
  end
end

Moko.new(1).hage
=> 1 # @aが表示された

モジュールのメソッドの探索経路

継承チェーンを確認してみる

Moko.ancestors
=> [Moko, Hage, Object, Kernel, BasicObject]

Moko.superclass
=> Object

Moko.instance_methods(false)
=> []

複数モジュールをインクルードしたら継承チェーンはどうなるんすか

module Moko1
end

module Moko2
end

class Moko
  include Moko1
  include Moko2
end

Moko.ancestors
=> [Moko, Moko2, Moko1, Object, Kernel, BasicObject]

Module#prependメソッド

includeの場合

module Hage
  def zura1
    true
  end

  def zura2
    true
  end
end

class Moko
  def zura1
    false
  end

  include Hage

  def zura2
    false
  end
end

Moko.ancestors
=> [Moko, Hage, Object, Kernel, BasicObject]

Moko.new.zura1
=> false

Moko.new.zura2
=> false

prependの場合

module Hage
  def zura1
    true
  end

  def zura2
    true
  end
end

class Moko
  def zura1
    false
  end

  prepend Hage

  def zura2
    false
  end
end

Moko.ancestors
=> [Hage, Moko, Object, Kernel, BasicObject]

Moko.new.zura1
=> true

Moko.new.zura2
=> true

でも、prependって何に使うの?

class Moko
  def initialize(hage)
    @hage = hage
  end

  def hage?
    @hage ? 'hage-!' : ''
  end
end

Moko.new(false).hage?
=> ""

Moko.new(true).hage?
=> "hage-!"

module Hage
  def hage?
    @hage = true # prependされたHage#hage?が@hageをtrueに変更してからMoko.hage?が呼ばれることになる。必ずハゲ
    super
  end
end

class Moko
  prepend Hage
end

Moko.new(false).hage?
=> "hage-!"

Moko.new(true).hage?
=> "hage-!"

includeとprepend両方ともされた場合、早いもん勝ち

module M1
end

module M2
  include M1
end

M2.ancestors
=> [M2, M1]

module M2
  prepend M1
end

M2.ancestors
=> [M2, M1]

module M3
  prepend M1
end

M3.ancestors
=> [M1, M3]

module M3
  include M1
end

M3.ancestors
=> [M1, M3]

特異クラス

特異クラスの性質

こんな時、Rubyではインスタンスに直接メソッドを定義する事ができる。このメソッドを特異メソッドと呼ぶ

特異クラスの前に特異メソッド

特異メソッドの定義

def <オブジェクト名>.<新メソッド名>
  ....
end

実際のオブジェクトに対して特異メソッドを定義してみる。片方のオブジェクトには新たにメソッドが定義されて、もう片方には存在していない

class Moko
end

moko1 = Moko.new
moko2 = Moko.new

def moko1.hage
  true
end

moko1.hage
=> true

moko2.hage
NoMethodError: undefined method 'hage'
a = 'a'
b = 'b'

def b.<=>(other)
  raise NoMethodError
end

a <=> b
=> -1

b <=> a
NoMethodError: NoMethodError
a = 1

def a.fa
end
TypeError: cant define singleton

さて、この特異メソッドはどういう仕組で実現されているのか

ここに図が必要だよなー

特異クラスの参照と再オープン

すぐ上で「参照できないようになっている」と書いたが、方法が無いわけでもない。特殊な構文を使用する事で可能となる

class Moko
end

moko = Moko.new

def moko.hage
  true
end

singleton_class = class << moko
  self
end

singleton_class
=> #<Class:#<Moko:0x007f8d4e22ff28>>

特異クラスの再オープン式

これって「classにクラスメソッド追加するあの書き方」じゃん!

class << 対象オブジェクト
end
class << moko
  def zura?
    true
  end
end

moko.zura?
=> true

さて、何が行われているのか?の前にselfとメソッドの定義先をもう一度見てみる

selfの参照先

class Moko
  p self # => Moko # Mokoクラスそのものが出力される

  def method
    self
  end
end

moko = Moko.new

moko == moko.method
=> true # moko.method は mokoそのものを返す

メソッドの定義先

class Moko
  def method1
    def method2
    end
  end
end

Moko.instance_methods(false)
=> [:method1]

Moko.new.method1

Moko.instance_methods(false)
=> [:method1, :method2]

特異クラスの再オープン再び

ちょっと前に出てきたソースの説明をもう一度します

class Moko
end

moko = Moko.new

class << moko
  def zura?
    true
  end
end

moko.zura?

頭がこんがらがってきたか?俺もだ

extendメソッド

moko1 = Moko.new

class << moko1
  include Hage
end

moko1.method
# ....
moko1 = Moko.new
moko1.extend(Hage)
moko1.method
# ....

Refinements

Refinementsはまだ歴史も浅く、仕様が枯れるのはいつなのか分かりません。試験に出題される際には基本的な部分を問われるくらいらしい

module Haging
  refine String do
    def hage
      'hage-!'
    end
  end
end

class Moko
  def method
    'moko'.hage
  end
end

Moko.new.method
NoMethodError: undefined method 'hage' for "moko":String

class Moko
  using Haging

  def method
    'moko'.hage
  end
end

Moko.new.method
=> "hage-!"

クラスメソッド

これまで クラス と呼んできたモノの招待を見ていきます

Classクラス

↓こいつがClassクラスです

String.class
=> Class
Moko = Class.new

Moko.superclass
=> Object

Moko.new
=> #<Moko:0x007fbd331feaf8>

Classクラスのnewメソッドの引数でスーパークラス、ブロックでメソッド定義を指定できる

class Oyaji
  def initialize(a)
    @a = a
  end

  def method1
    @a
  end
end

Moko = Class.new(Oyaji) do
  def initialize(a, b)
    @b = b
    super(a)
  end

  def method2(c)
    @a + @b + c
  end
end

Moko.new(1, 2).method1
=> 1

Moko.new(1, 2).method2(3)
=> 6
Classクラス界 (さらに抽象的方面)
  Class => superclass => Module => superclass => Object

----------------
Classオブジェクト界 (抽象的方面)
  Moko => superclass => Oyaji

----------------
インスタンス(オブジェクト)界 (具体的方面)
  moko1
  moko2

クラスメソッドの定義

おそらくまだ混乱していると思いますが、クラスメソッドを見ていきましょう

クラスメソッドの定義 その1 (全てのクラスで有効)

class Class
  def c_method1
    'hage-!'
  end
end

String.c_method1
=> "hage-!"

Integer.c_method1
=> "hage-!"

クラスメソッドの定義 その2

# オブジェクトに特異メソッドを定義する時にはこう書いていた
class Moko
end

moko = Moko.new

def moko.method
  'hage-!'
end

moko.method
=> "hage-!"

同様に、オブジェクトの代わりにクラス(クラスオブジェクト)を指定するだけ

class Moko
end

def Moko.method
  'hage-!'
end

Moko.method
=> "hage-!"

クラスメソッドの定義 その3

class Moko
  def Moko.method
    'hage-!'
  end
end

Moko.method
=> "hage-!"

クラスメソッドの定義 その4

class Moko
  def self.method # クラス定義内ではselfがMokoを指す
    'hage-!'
  end
end

Moko.method
=> "hage-!"

クラスメソッドの定義 その5 (特異クラスの再オープン)

class Moko
  class << self
    def method
      'hage-!'
    end
  end
end

Moko.method
=> "hage-!"

クラスメソッドの定義とインスタンスへの特異メソッドの定義の違い

(このあたりにメモ画像を貼らないと、たぶん伝わらない、ごめんなさい)

class Moko
  TOKUI_KURASU = class << self
    self
  end
end

class Object
  TOKUI_KURASU = class << self
    self
  end
end

Moko::TOKUI_KURASU.superclass == Object::TOKUI_KURASU
=> true

Moko::TOKUI_KURASU.ancestors
=> [#<Class:Moko>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Moko::TOKUI_KURASU.ancestors[1] == Object::TOKUI_KURASU
=> true

extendでクラスメソッドを定義する

extendはレシーバに特異メソッドを追加するのでクラスオブジェクトの特異メソッド==クラスメソッドとして定義される

class Moko
end

module KURASU_METHOD
  def method
    'hage-!'
  end
end

Moko.extend(KURASU_METHOD)

Moko.method
=> "hage-!"

おつかれさま!これでRubyのメソッド探索経路の話は終わりですよ!

メソッドの可視性

デフォルトはpublicです

class Moko
  def public_method1; 1 ; end # デフォルトはpublicメソッドになる

  public # 以降はpublicメソッドになる

  def public_method2; 2 ; end

  protected # 以降はprotectedメソッドになる

  def protected_method3; 3 ; end
  def protected_method4; 4 ; end # 同じくprotectedメソッドになる

  private # 以降はprivateメソッドになる

  def private_method5; 5 ; end
end

Moko.new.public_method1
=> 1
Moko.new.public_method2
=> 2
Moko.new.protected_method3
=> NoMethodError: protected method 'protected_method3' called
Moko.new.protected_method4
=> NoMethodError: protected method 'protected_method4' called
Moko.new.private_method5
=> NoMethodError: private method 'private_method5' called

メソッド名を指定して可視性を変更する

各可視性変更メソッドへメソッド名を指定する事で、後から可視性を変更する事もできる

class Moko
  def public_method1; 1 ; end # デフォルトはpublicメソッドになる
  def public_method2; 2 ; end
  def protected_method3; 3 ; end
  def protected_method4; 4 ; end
  def private_method5; 5 ; end

  public :public_method1, :public_method2 # 指定したメソッドがpublicになる(と言ってもデフォルトでpublicだが)
  protected :protected_method3, :protected_method4 # 指定したメソッドがprotectedになる
  private :private_method5 # 指定したメソッドがprivateになる
end

Moko.new.public_method1
=> 1
Moko.new.public_method2
=> 2
Moko.new.protected_method3
=> NoMethodError: protected method 'protected_method3' called
Moko.new.protected_method4
=> NoMethodError: protected method 'protected_method4' called
Moko.new.private_method5
=> NoMethodError: private method 'private_method5' called

privateの振る舞い

class Moko
  def public_method1
    private_method
  end

  def public_method2
    self.private_method # レシーバを指定した呼び出し
  end

  private

  def private_method
    'hage-!'
  end
end

Moko.new.public_method1
=> "hage-!"

Moko.new.public_method2
NoMethodError: private method 'private_method' called

サブクラスにおける可視性の変更

メソッドの可視性はクラスに結び付けられているので、サブクラスで自由に変更することができる

class Oyaji
  private

  def private_method
    'hage-!'
  end
end

# Oyajiクラスに定義されているprivate_methodはプライベートなので呼び出せない
Oyaji.new.private_method
=> NoMethodError: private method 'private_method' called

# 継承した直後に可視性を変更する
class Moko < Oyaji
  public :private_method
end

Moko.new.private_method
=> "hage-!"

# もちろんOyajiクラスを再オープンして可視性を変更してもいいんですけどね
class Oyaji
  public :private_method
end

Oyaji.new.private_method
=> "hage-!"

メモ

Kernelモジュール

# 呼び出せるやんけ・・・どうしてや
Kernel.p 'hage-!'
"hage-!"

独自の組み込み関数の定義(Kernelを拡張)

module Kernel
  private

  def hage
    'hage-!'
  end
end

hage
"hage-!"

self.hage
NoMethodError: private method 'hage'

独自の組み込み関数の定義(Objectを拡張)

class Object
  private

  def hage
    'hage-!'
  end
end

hage
"hage-!"

self.hage
NoMethodError: private method 'hage'

アクセッサメソッド

インスタンス変数へアクセスする専用のメソッドみたいな 変数、定数、予約語、演算子関係

class Moko
  def hage
    return @hage
  end

  def hage=(value)
    @hage = value
  end
end

moko = Moko.new

moko.hage
=> nil

moko.hage = 'hage-!'
=> 'hage-!'

moko.hage
=> 'hage-!'

値を取り出すメソッドをゲッター(getter)、値をセットするメソッドをsetter(セッター)を言います

アクセッサメソッドの生成を行うメソッド

でも、インスタンス変数の数だけゲッターとセッターを書くのはめんどくさいので、アクセッサ生成用のクラスメソッドが用意されている

attr_reader getterメソッドを生成する
attr_writer setterメソッドを生成する
attr_accessor getterとsetterメソッドを生成する
attr 1.8以前ではこう書いていた。getterメソッドを生成する、第二引数にtrueを指定するとsetterメソッドも生成する
class Moko
  attr_accessor :hage

  def test
    @hage
  end
end

moko = Moko.new

moko.hage
=> nil

moko.hage = 'hage-!'
=> "hage-!"

moko.hage
=> "hage-!"

moko.test
=> "hage-!"

インスタンス変数は継承されないが、メソッドは継承されるのでサブクラスからアクセッサを経由してスーパークラスのインスタンス変数にアクセスすることが可能・・・って、マジで?

スーパークラスのメソッドを使うけど、アクセスするのは自分自身のインスタンス変数なんじゃないの?

これ、この本を書いた人がトチ狂ったんじゃないかと思ってるんだけど どうなの?

class Moko2 < Moko
end

moko2 = Moko2.new

moko2.instance_variables
#=> []

moko2.hage
#=> nil

moko2.hage = 'hage-!'
#=> "hage-!"

moko2.hage
#=> "hage-!"

moko2.instance_variables
#=> [:@hage]

ネストしたスコープ

クラス式やモジュール式は定数にクラスオブジェクト、モジュールオブジェクトが格納されますが、:: 演算子を使ってネストを指定できる

module A1
end

A1::B = 1

A1::B
=> 1
module A2
  B = 1
end

A2::B
=> 1

この例はエラーになる A3::Bは1(数値オブジェクト)になる為。::はクラスオブジェクトかモジュールオブジェクトにしか存在しない

module A3
  B = 1
end

A3::B::C
#=> TypeError: 1 is not a class/module

こうするべきですかね

module A4
  module B
    C = 1
  end
end

A4::B::C
=> 1

トップレベルで定義した定数はObjectクラスの定数、内部で定義した初期化されたクラス、モジュールに配置される

A5 = 'A5'

class A6
  A7 = 'A7'
end

Object.constants
=> [... :A5, :A6, ...]

A6.constants
=> [:A7]

ネストした定数に相対位置でアクセス

module A8
  module B2
    C1 = 'C1'
  end

  p B2::C1
  #=> "C1"
end

前頭に :: を書く事で、ネストした定数に絶対位置でアクセス

module A9
  module B2
    C1 = 'C1'
    p ::A9::B2::C1
    #=> "C1"
  end
end

定数が見つからない場合はネストの外側に向かって順に探索される

module A9
  Z1 = 'Z1_1'
  module B3
    Z1 = 'Z1_2'
    module C3
      p Z1
    end
  end
end
=> "Z1_2"
module A10
  Z1 = 'Z1_1'
  module B3
    module C3
      p Z1
    end
  end
end
=> "Z1_1"

スコープの外側でも定数が見つからない場合はスーパークラスで定義された変数やincludeされたモジュールまでも、継承チェーンを掛け登って探索する

探索と言えば、missingです。定数にもconst_missingがあります

# 定数が見つからない場合、1を返す
module Hage
  def self.const_missing(id)
    p id
    1
  end
end

Hage::FDASFADSFDS
#=> :FDASFADSFDS
#=> 1