จะเห็นว่า magic ส่วนหนึ่งของ ActiveRecord ก็มาจาก
Metaprogramming นี่เอง
ผลลัพท์ที่ได้ ต้องใช้คำว่า งามมาก
แต่ source code นี่สุดๆเลย
ลองดูว่าเขาเขียนกันอย่างไร
เริ่มด้วย Base Class ก่อน
Model ทุกตัวของเราต้อง extend Base Class นี้
module ActiveRecord
class Base
#... lot of instance method
def find(*args)
# ...
end
#...
end
end
ใน base class นี้จะมี instance method อยู่เต็มเลย
เวลาเรานิยาม model ของเรา ใน rails
class Person << ActiveRecord::Base
has_one :address
end
ผลของ has_one ที่ใส่ลงไป
ทำให้เราสามารถ access attribute
ที่ชื่อ address ได้
p = Person.new
p.address = Address.new
p.address.line1 = 'xxxxxx'
คำถาม ก็คือ has_one มันเขียนอย่างไร
ใน ActiveRecord จะมี Module ที่เรียกว่า Association
(ซึ่งถูก include เข้าไปใน Base class)
module ActiveRecord
module Associations
def self.append_features(base)
super
base.extend(ClassMethods)
end
module ClassMethods
def has_one(name)
define_method("#{name}=") { |value|
instance_variable_set("@#{name}", value)
}
define_method(name) {
instance_variable_get("@#{name}")
}
end
end
end
end
Magic เริ่มต้นที่
append_feature
method นี้จะถูกเรียกใช้เมื่อมีการสั่ง
include ActiveRecord::Associations
พร้อมทั้งส่ง parameter มาเป็น class ที่ include method นั้น
เช่น
class Pok
include ActiveRecord::Associations
end
parameter
base
ที่ append_features
ได้รับก็คือ
Pok
class นั่นเองจะเห็นว่า เมื่อได้รับ parameter base มา มันก็แค่สั่ง extend
ด้วย
ClassMethods
ต่ออีกทีmagic อยู่ในส่วนนี้แหล่ะ
จะเห็นว่ามีการ define has_one ใน
ClassMethods
(อันนี้ลดรูปมาให้ดูง่ายๆ code จริงๆซับซ้อนกว่านี้เยอะ)
def has_one(name)
define_method("#{name}=") { |value|
instance_variable_set("@#{name}", value)
}
define_method(name) {
instance_variable_get("@#{name}")
}
end
จะเห็นว่ามีการ define method
has_one
ไว้ภายในก็จะมีการเรียกใช้
define_method
เพื่อกำหนด method ที่ชื่อ
address
กับ address=
(assigment)การ access instance variable ก็ไม่สามารถทำได้ตรงๆ
ต้องใช้ผ่าน
instance_variable_get/set
จะเห็นว่ายุ่งยากเหลือใจทีเดียว (code ของจริงเป็นสปาเก็ตตี้กว่านี้)
อ่านเพิ่มเติมได้ที่นี่
No comments:
Post a Comment