学んだことをなぐり書き



オープンクラス

# rubyにとってクラスとは特別な識別子ではなく、スコープの境界線を定めてそれをオブジェクトの形にしているものに過ぎない。
class A
  puts 'aiueo'  # よって class の中にある処理は普通に走る
  def a; puts 'a'; end
end

# クラスはただのスコープの境界線なので、
# 「class A を再定義する」という形ではなく「A スコープを開いて追加する」という形になる。
# なので、a と b のメソッドを両方呼ぶことが出来る。
class A
  def b; puts 'b'; end
end

obj = A.new

# a も b も両方よべる
obj.a
obj.b

class A
  # 既存のメソッドの上書きすることが可能。しかし場合によっては既存のプログラムが動かなくなる。
  # 組込みライブラリやフレームワークを迂闊にオープンクラスによって拡張したものを「モンキーパッチ」という
  # 上書きは継承と違って override にならない。
  def a
    # 「override」ではなく「上書き」なので super で呼べない
    # super => error

    puts 'aa'
  end
end

A.new.a
aiueo
a
b
aa

class_eval(module_eval) と instance_eval の違い

http://d.hatena.ne.jp/LukeSilvia/20090222/p1
# Object#instance_eval は全てのオブジェクトにあるが Module#class_eval はクラスオブジェクトにしかない。
obj = Object.new
obj.instance_eval   { def hoge; 'hoge'; end }
puts obj.hoge
#obj.class_eval {}

clazz = Class.new
# instance_eval の場合は clazz のクラスメソッドに追加して、
# class_eval の場合は clazz のインスタンスメソッド を追加する。
clazz.instance_eval { def foo; 'foo'; end }
clazz.class_eval    { def bar; 'bar'; end }
puts clazz.foo
puts clazz.new.bar

# クラスオブジェクトに class_eval か instance_eval のどちらを使うか迷った場合
# self に変更を加えたい場合は instance_eval を
# オープンクラス として使用した場合は class_eval を使用するといい。
hoge
foo
bar

class_eval(module_eval) でのクラス変数とクラス定数

class C
  @@a = 'aiueo'
  A   = 30
end

# class_eval を使ってクラス変数(@@)やクラス定数する場合
# 直接コードでアクセス使用することは出来ない
C.class_eval do
  #p @@a #=> NameError
  #p A   #=> NameError
end

# 文字列を渡せばアクセス出来る
C.class_eval <<-EOS
  p @@a
  p A  
EOS
"aiueo"
30

アクセサメソッドの明示的な呼出し

class A
  attr_accessor :val

  def set_local
    # ローカル変数の val か self.val=() のどちらかを判別する術がない。
    # こういう場合 ruby はローカル変数と認識する
    val = 10
  end # ここで ローカル変数 val のスコープが切れて解放される。

  def set_self
    # 明示的に self. を付けてやれば アクセサメソッドだと判別することができる。
    self.val = 20
  end
end

a = A.new
a.set_local
puts a.val

a.set_self
puts a.val
nil
20

ミミックメソッド

# 継承はクラスオブジェクトを指定すれば動く
# なのでクラスに見えるメソッドを作って継承できる
# このような他の言語要素に偽装したメソッドを「ミミックメソッド」と言う
def A msg
  # do - end でなく { - } にする場合引数の括弧は省略できない
  # define_method :msg { msg } => error
  Class.new { define_method(:msg){ msg } }
end

# 最終的な形がクラスオブジェクトならばメソッドを呼んでも継承は動く
class B < A 'aiueo'
end

p B.new.msg
"aiueo"

ブランクスレート

必要最小限のメソッドしかないクラスのこと
ruby 1.9 では BasicObject があるが、1.8では自分で作る
# Module.undef_method(メソッド名)
# メソッドを消す。
class C
  # __send__, __id__, method_missing, respond_to? 以外のメソッドを消す。
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/
  end

  def method_missing(name, *args)
    puts "call #{name}"
  end
end

obj = C.new

# method_missing で class などの Object のメソッドも呼べるようになる。
obj.class
call class

フラットスコープ

# ruby の変数のスコープは def, class, module 単位で切り変わる。
# java のように「外部スコープ」「内部スコープ」という機能はなくスコープが切りかわると前(外や内)のスコープにはアクセスできない。
# 変数のスコープを越えたい場合 define_method, Class.new, Module.new を使う。
local = 0

self.class.send :define_method, :show do
  puts "local = #{local}"
end

show

C = Class.new do
  local += 1

  define_method :add do
    local += 1
    show
  end
end

show

obj = C.new
obj.add
obj.add
puts local
local = 0
local = 1
local = 2
local = 3
3

特異メソッド(ダックタイピング)

# Rubyでは特定のオブジェクトにメソッドを追加することが出来る。
# このようなメソッドを「特異メソッド」と言う。

str = 'aiueo'
# オブジェクト.メソッド名 の形でそのオブジェクトだけが使用できるメソッドが定義できる。
def str.title?
  self.upcase == self
end

puts str.title?

# str のオブジェクトには title? メソッドは追加されている。
puts str.methods.grep(/title?/)

# オブジェクトの特異メソッドを取得する場合は Object#singleton_methods を使用する。
puts str.singleton_methods 

# title? は str のオブジェクトのみに追加されたメソッドなので他の String では使用できない。
# "AIUEO".title?

# 厳密な型よりも walk()(歩く) や quack()(鳴く) に反応さえ返せば型がどれかは問題ない。
# このような流動的な型のことを「ダックタイピング」を呼ぶ。
# 「アヒルのように歩いて、アヒルのように鳴けば、それはアヒルに違いない」という話が由来。
# Ruby にとって「型」はオブジェクトが反応するメソッドの集合に過ぎない。

# ちなみに実はクラスメソッドの正体はクラスオブジェクトの特異メソッド
class C 
  # オブジェクト.メソッド名 という特異メソッドの定義をとっているのが分かる。
  def self.foo; 'foo'; end
  def self.bar; 'bar'; end
end

puts C.singleton_methods
false
title?
title?
bar
foo

特異クラス(class << object)

# obj に特異メソッド hoge を定義したとする。
# この hoge はどこに住んでいるのだろうか?
obj = Object.new
def obj.hoge; 'hoge'; end
p obj.hoge

# obj はクラスではないので住むことはできない
# hoge は obj 専用なので Object にも住むことが出来ない
p Object.instance_methods.grep(/hoge/) # hoge なんてメソッドないよ!

# 特異メソッドが住んでいる場所を特異クラスという
# 特異クラスは全てのオブジェクトが一つだけもっている(だから特異クラスはシングルトンクラスとも呼ばれる)
# 通常だと特異クラスは隠れているが 
# class << obj の後に self を戻すことによって取得することが出来る
eigenclass = class << obj
  self
end

p eigenclass.class
p eigenclass.instance_methods.grep(/hoge/)

# 特異クラスは継承出来ない
# class A < eigenclass; end
"hoge"
[]
Class
["hoge"]

クラス・オブジェクト拡張(Object#extend)

module M1
  # モジュールのクラス(特異)メソッドを定義
  def self.say; puts 'M1'; end
end

class C1
  # include したのでクラスメソッドもミックスインして欲しい顔になる
  include M1
end

# が!!!だめ!!
# C1.say #=> NoMethodError

# クラスがモジュールをインクルードすると、モジュールのインスタンスメソッドが手に入る
# しかしクラスメソッド(モジュールの特異メソッド)は手に入らない
# クラスメソッドを拡張したい場合はモジュールに通常のインスタンスメソッドを定義して
# 特異クラスのなかでモジュールをインクルードすればいい
# これを「クラス拡張」という
module M2
  def say; puts 'M2'; end
end

class C2
  class << self
    include M2
  end
end

C2.say

# 普通のオブジェクトでも特異クラスのなかで include すれば同じように拡張出来る
# これを「オブジェクト拡張」という
obj2 = Object.new

class << obj2
  include M2
end

obj2.say
p obj2.singleton_methods

# クラス拡張やオブジェクト拡張をする Object#extend メソッドが用意されていて
# やっていることは上記の特異クラスのスコープを開いて include をするという作業と一緒である。
module M3; def say; puts 'M3'; end; end
obj3 = Object.new
obj3.extend M3
obj3.say

class C3
  extend M3
end
C3.say
M2
M2
["say"]
M3
M3

フックメソッド

# inherited: クラスを継承した場合呼び出されるメソッド
class A
  def self.inherited(subclass)
    puts "#{subclass} extends #{self}"
  end
end

class B < A; end

# include: モジュールをミックスインした場合呼び出されるメソッド
module M
  def self.included(othermod)
    puts "#{othermod} include #{M}"
  end
end

class C
  include M
end

# method_added:     新しいメソッドを追加した場合呼び出されるメソッド
# method_removed:   メソッドを削除した場合呼び出されるメソッド
# method_undefined  メソッドを未定義にした場合呼び出されるメソッド
#
# TODO: method_removed と method_undefined の違いを調べる。
module M2
  def self.method_added(method)
    puts "new method: #{method}"
  end

  def self.method_removed(method)
    puts "removed method: #{method}"
  end

  def self.method_undefined(method)
    puts "undefined method: #{method}"
  end

  def hoge; end
  remove_method :hoge

  def moke; end
  undef_method :moke
end

# 上記のメソッドの先頭に singleton_ を付けると特異メソッドに対しての追加、削除、未定義になる。
class C2 
  def singleton_method_added(method)
    puts "new singleton_method: #{method}"
  end

  def singleton_method_removed(method)
    puts "removed singleton_method: #{method}"
  end

  def singleton_method_undefined(method)
    puts "undefined singleton_method: #{method}"
  end
end

obj = C2.new
def obj.hoge; end
def obj.moke; end
class << obj
  remove_method :hoge
  undef_method :moke
end
B extends A
C include M
new method: hoge
removed method: hoge
new method: moke
undefined method: moke
new singleton_method: hoge
new singleton_method: moke
removed singleton_method: hoge
undefined singleton_method: moke

クラス拡張ミックスイン

# クラスメソッドの拡張を全てモジュール内で済ませる場合
# フックメソッドの included が呼ばれた時に extend する。
class Base
  def self.before_filter name; puts "before_filter: #{name}"; end
end

module M
  def self.included(base)
    base.class_eval do
      extend ClassMethods

      # include 先のクラスメソッドを呼びたい場合も
      # included時にコールする。
      before_filter :check
    end
  end

  module ClassMethods
    def after_filter name; puts "after_filter: #{name}"; end
  end
end

class C < Base
  include M

  after_filter :show
end
before_filter: check
after_filter: show

メンバーのみ編集できます