Ruby Method Lookup: include, prepend, extend và singleton class

Khi gọi:

1
receiver.some_method

Ruby tìm method qua ancestor chain của class tương ứng. Hiểu chain này giúp giải thích inheritance, module mixin và những bug kiểu “method này từ đâu chui ra?”.

Instance method lookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Trackable
def status
"tracked"
end
end

class Animal
def status
"animal"
end
end

class Cat < Animal
include Trackable
end

Cat.ancestors
# => [Cat, Trackable, Animal, Object, Kernel, BasicObject]

Ruby tìm từ trái sang phải trong ancestors và dừng ở method đầu tiên.

prepend

1
2
3
4
5
6
7
8
9
10
module Logging
def status
puts "calling status"
super
end
end

Cat.prepend(Logging)
Cat.ancestors
# => [Logging, Cat, Trackable, Animal, ...]

prepend đặt module trước class, phù hợp để wrap behavior với super. Nó rõ hơn alias-method chain cổ điển và ít mang mùi khảo cổ.

extend

1
2
3
4
5
6
7
8
module Findable
def find(id)
"finding #{id}"
end
end

Cat.extend(Findable)
Cat.find(1)

extend include module vào singleton class của object. Với object Cat (bản thân class cũng là object), module methods trở thành class-level methods.

1
Cat.singleton_class.ancestors

Singleton method

1
2
3
4
5
cat = Cat.new

def cat.name
"Milo"
end

Method nằm trên singleton class của riêng cat, nên object khác không có.

super

super tiếp tục lookup sau vị trí method hiện tại trong chain. Nó không đơn giản là “gọi parent class”; module prepend/include cũng tham gia.

1
2
3
4
def status
value = super
"#{value}:cat"
end

Debug lookup

1
2
3
4
cat.method(:status).owner
cat.method(:status).source_location
Cat.ancestors
Cat.singleton_class.ancestors

Bốn dòng này thường hữu ích hơn việc nhìn code và đoán bằng trực giác. Metaprogramming không thần bí; nó chỉ có nhiều cửa, và Ruby khá lịch sự khi cho ta xem sơ đồ hành lang.

Reference