Thursday, December 29, 2005

ActiveRecord & Metaprogramming

ถ้าเราดู source code ของ ActiveRecord ใน Rails
จะเห็นว่า 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 ของจริงเป็นสปาเก็ตตี้กว่านี้)
อ่านเพิ่มเติมได้ที่นี่

Related link from Roti

No comments: