include
モジュールのメソッドをクラスのメソッドとして取り込める便利なあれですね。
module M
def m
puts "Module M!"
end
end
class A
include M
end
A.new.m
#=> Module M!
定数利用
ところで、取り込まれるクラスの定数を利用したいとします。
module M
def m
puts CONST
end
end
class A
CONST = "Class A!"
include M
end
しかし悲しい哉、これは例外を吐きます。
A.new.m
#=> uninitialized constant M::CONST (NameError)
定数を探すスコープがincludeされるモジュールのままなのですね。
これはいけない。
include先の定数を指定する
self
を使ってやれば望み通り、includeする側の定数を参照できます。
module M
def m
puts self.class::CONST
end
end
class A
CONST = "Class A!"
include M
end
A.new.m #=> Class A!
class B
CONST = "Class B!"
include M
end
B.new.m #=> Class B!
extendでも同様に
self
が指し示すものがインスタンスではなくクラスそのものである点に注意しましょう。
module M
def m
puts self::CONST
end
end
class C
CONST = "Class C!"
extend M
end
C.m #=> Class C!
includeでもextendでも同じように使いたい
self
がClass
かModule
の時とそれ以外で場合分けする?
module M
def m
puts (self.is_a?(Class) || self.is_a?(Module) ? self : self.class)::CONST
end
end
もうちょっと綺麗な方法がありそうな気もしますが……。
そもそも同じメソッドをインスタンスメソッドとしてもクラスメソッドとしても使いたいという状況があまり無いかも。
定数がない場合、事前に警告する
上記のやり方でincludeするクラスの定数を取得する事ができました。
しかしこの方法を取り、間違えて必要な定数の存在しないクラスにincludeしてしまった場合、そのメソッドが呼び出されるまで例外が飛びません。
module M
def m
puts self.class::CONST
end
end
class A
include M # CONSTが必要なモジュールをincludeしてしまった!
end
# しかしこの段階では異常は起きない。
a = A.new
#
# 何か処理...
#
a.m #=> ここで例外!
これはバグの元ですね。
そこでincludedを利用して、クラス定義時にincludeの可否を決定してあげましょう。
module M
def self.included(klass)
klass::CONST
rescue NameError
raise "#{klass} is expected const CONST to include module M."
end
def m
self.class::CONST
end
end
これでクラス定義時に、必要な定数が無い場合例外を飛ばすようになりました。
ただし注意してください。
Rubyは極めて自由な言語なので、後からクラス内に定数を追加する事も可能です。
class A
include M
end
# これは例外を吐く
# A.new.m
class A
CONST = "Class A!"
end
A.new.m #=> Class A!
こうした機能を活用している場合、include時に必要な定数が無いからといって例外を投げるようでは困る事態もあるやもしれません。