Friday, July 08, 2005

Custom Schema Mapping in ActiveRecord (Rails)

ActiveRecord คือ module ที่ทำหน้าที่
Object/relation mapping ใน RubyOnRails Framework
วิธีการที่เขาใช้ จะใช้ reflection เป็นหลัก
และก็มี assumption บางอย่างที่ใช้ในการ
map column name, table name เข้ากับ Object

ตัวอย่างแรกสุดเลย
สมมติเราจะเขียน blog ด้วย Rails
ในแง่ของตัว relational database
เราก็ต้องมี table สำหรับเก็บ blog post
assumption ของ active record ก็คือ
  • ต้องมี column "id" เป็น primary key
  • ถ้ามี column created_on กับ updated_on
    เจ้า ActiveRecord จะดูแลการ set ค่าพวกนี้ให้โดยอัตโนมัติ
  • ชื่อ table ต้องอยู่ในรูปพหูพจน์


ดังนั้นเจ้า table blog post ของเราก็เลยออกมา
หน้าตาประมาณนี้
create table blog_posts (
id serial,
post text,
created_on timestamp,
primary key (id)
);


ส่วนเจ้า class BlogPost
ก็หน้าตาประมาณนี้
class BlogPost < ActiveRecord::Base
end

จะเห็นได้ว่าไม่ต้องระบุอะไรเลย
เดี๋ยว Reflection จัดให้เอง ตอน runtime

ส่วนการใช้งาน ก็เช่น
blogpost = BlogPost.new
blogpost.post = "hello world"
blogpost.save


ในกรณีที่เราเขียน app โดยออกแบบใหม่หมด
ตั้งแต่ table เลย ก็คงไม่มีปัญหาอะไร
แต่ถ้าเป็น Web Application ที่ต้องใช้
legacy table จากระบบเก่า
ก็จะเกิดปัญหาเรื่อง naming หรือ mapping ขึ้น
(แค่ชื่อ table ก็เป็นปัญหาแล้ว
บางคนเขานิยมตั้งชื่อ table เป็นรหัส)

ดังนั้นใน post วันนี้ เราจะลองมาดูว่า
เราสามารถ override assumption ต่างๆของ ActiveRecord
ได้อย่างไรบ้าง

เริ่มด้วยชื่อ table
ค้นเจอ 2 แบบ
class MyClassName < ActiveRecord::Base
def self.table_name() "my_table_name" end
end


class Mouse < ActiveRecord::Base
set_table_name "mice"
end

คืออันแรกใช้วิธี override method table_name()
ส่วนอันที่ 2 ใช้วิธี call method set_table_name("name")

กรณี primary key ของเราไม่ได้ชื่อ "id"
class MyClass < ActiveRecord::Base
set_primary_key "my_pk_name"
end


ส่วนกรณีที่เราต้องการชื่อ attribute
ที่ไม่ตรงกับชื่อ column
เท่าที่ค้นดู ยังไม่มีพูดถึงประเด็นนี้
แต่ถ้าทำด้วยวิธี object style ก็คงใช้วิธีเขียน method set, get ด้วยชื่อใหม่
แล้วข้างไนก็ค่อยไปเรียก set, get ด้วยชื่อจริง
class Song < ActiveRecord::Base

def length=(minutes)
write_attribute(:len, minutes)
end

def length
read_attribute(:len)
end
end

Related link from Roti

No comments: