Wednesday, August 15, 2007

Seed Data

ผมชอบวิธี load data ใน Ofbiz ที่ใช้วิธีประกาศ
<entity-resource type="data" reader-name="seed" loader="main" location="data/AccountingTypeData.xml"/>

ส่วน file ที่เก็บ data จะมีหน้าตาแบบนี้

<GlAccount parentGlAccountId="" glAccountId="100000" accountCode="100000"
glAccountClassId="ASSET" glAccountTypeId="" glResourceTypeId="MONEY"
accountName="ASSETS" description="" postedBalance="0.0"/>

<GlAccount parentGlAccountId="100000" glAccountId="110000" accountCode="110000"
glAccountClassId="CASH_EQUIVALENT" glAccountTypeId="CURRENT_ASSET" glResourceTypeId="MONEY"
accountName="CASH" description="" postedBalance="0.0"/>

<GlAccount parentGlAccountId="110000" glAccountId="111000" accountCode="111000"
glAccountClassId="CASH_EQUIVALENT" glAccountTypeId="CURRENT_ASSET" glResourceTypeId="MONEY"
accountName="CASH IN BANK AND ON HAND" description="" postedBalance="0.0"/>


ตอนนี้กำลังเขียนโปรแกรมบัญชีด้วย Rails อยู่เหมือนกัน
ก็เลยอยากลอกวิธี setup seed data มาไว้ใช้บ้าง

เดิิมใน rails เราสามารถ load data ที่อยู่ในรูป format YML ได้เหมือนกัน
โดย rails เรียกว่า Fixtures
แต่ข้อเสียของ Fixtures ก็คือ
มันแยกเก็บเป็น 1 file ต่อ 1 table
ซึ่งสำหรับผม ในการ config ที่ cross ไป cross มา
มันมองภาพรวมยากไปนิด

กลับมาเข้าเรื่องต่อ ถ้าเราลอก feature seed data มาทำด้วย Rails
ก็จะมี constraint ในการ design อยู่ 2 เรื่องคือ
1. เราคงไม่ใช้ xml
2. ใน ofbiz, primary key มันเป็น String, ทำให้ง่ายต่อการ reference กันระหว่าง entity
แต่ใน ruby, primary key มันเป็น integer ดังนั้นต้องมี concept ที่ใช้ตั้งชื่อ record
เพื่อให้ง่ายต่อการอ้างใช้

ตัว syntax เมื่อไม่ใช้ xml ก็ลองเอา block มาใช้ดู
หน้าตาแบบนี้
setup do |s|

s.model :AcctChart do |m|
m.data :all, :code => "all", :name => "Mx Group"
end

s.model :AcctGroup do |m|
m.data :cash, :description => "Cash"
m.data :liability, :description => "Liability"
m.data :equity, :description => "Equity"
m.data :revenue, :description => "Revenue"
m.data :expense, :description => "Expense"
end

s.model :AcctEntity do |m|
m.data :mx, :code => "mx",
:party_id => s.Organization[:mx],
:acct_chart_id => s.AcctChart[:all]
end

s.model :AcctChartItem do |m|
m.data 1000,
:code => "1000",
:long_name => "cash"
m.data 1001,
:code => "1010",
:short_name => "cash on hand",
:acct_chart_id => s.AcctChart[:all],
:acct_group_id => s.AcctGroup[:cash],
:parent_id => m[1000]
m.data 1002,
:code => '1020',
:short_name => "cash in bank",
:acct_chart_id => s.AcctChart[:all],
:acct_group_id => s.AcctGroup[:cash],
:parent_id => m[1000]
end

end

จะเห็นว่า เราใช้วิธีอ้างถึง record ที่เกิดขึ้นแล้ว อยู่ 2 แบบ

:acct_chart_id => s.AcctChart[:all]

:parent_id => m[1000]

ทั้งสองวิธีใช้ hash เหมือนกัน แต่ต่างกันที่ scope ที่ระบุแค่นั้น

สำหรับการ implement, ประเด็นที่สนุกหน่อย ก็คือ
เรารู้ Model Class โดยชื่อของมัน
จากชื่อ เราจะ solve ไปเป็น class Object ได้อย่างไร
อย่างใน java เราก็จะมี Syntax พวกนี้ใช้
Class.forName("x.y.Z").newInstance()


ใน ruby ถ้าเล่นแบบลูกทุ่งสุด ก็ง่ายๆแบบนี้

eval("#{clsName}.new")


ถ้าให้สวยขึ้น ก็ต้องมองที่ concept ว่า Class ใน ruby
มันเก็บไว้เป็น constant ใน Object
ดังนั้นเราสามารถใช้คำสั่งแบบนี้ได้
Objet.const_get(clsName).new


ตัว code เต็มๆ หน้าตาแบบนี้
module DataUtil
module Setup
def setup()
setup = Setup.new()
yield setup
end

class Setup
def initialize()
@cache = {}
end

def model(name)
puts "loading model #{name}"
m = Model.new(name)
@cache[name] = m
yield m
end

def method_missing(sym)
@cache[sym]
end
end

class Model
def initialize(name)
# delete old data
Object.const_get(name).delete_all
@name = name
@cache = {}
end

def data(id, attrs)
m = (Object.const_get @name).new(attrs)
m.save
internal_id = m.id
@cache[id] = internal_id
end

def [](key)
@cache[key]
end
end
end
end


ตัว config ทำให้สวยขึ้นได้กว่านี้
เช่น เปลี่ยนไปอ้างตรงๆเลย แทนที่จะใช้ hash accessor
:acct_chart_id => :all,
:acct_group_id => :cash,

แต่เข้าข่าย 80-20 แล้ว
เมื่อได้ feature เพียงพอต่อการใช้งานแล้ว ก็จงหยุด

Related link from Roti

6 comments:

Ofbiz Expert said...

ตอนนี้ผมกำลังศึกษาเอา Eclipse RCP + XUI อยู่ครับพอดีลองใช้ POS ของ OFBiz แล้วไม่ค่อยจะสวยงานเท่าไหร่ตอนนี้จะลองทำ POS ด้วย RCP + XUI ว่างๆมาช่วยผมดูหน่อยก็ดีนะครับอิอิจะเอาเป็น Project แรกของ orangegears.sf.net ครับพี่

polawat phetra said...

XUI มันอยู่บน swing ไม่ใช่เหรือ
รู้สึกแปลกๆ กับ combination นี้
swing UI + eclipse platform

polawat phetra said...

ลืมบอกไป
ที่เคยบอกว่าจะช่วยทำ
ตัวแรกที่คิดจะทำ ก็คือตัว POS ที่ทำด้วย RCP นี่แหล่ะ

Ofbiz Expert said...

ตอนนี้ XUI มี CoreSWT แล้วครับตอนนี้กำลังเล่นอยุ่ครับ พอดีไปเห็น project http://www.wazaabi.org/ เอา XUL รันบน RCP ก็เข้าท่าดีครับ

------------
ลืมบอกไป
ที่เคยบอกว่าจะช่วยทำ
ตัวแรกที่คิดจะทำ ก็คือตัว POS ที่ทำด้วย RCP นี่แหล่ะ
------------
งั้นดีเลยครับพี่ Version แรกอาจจะไปถึงกับใช้ XUI อาจจะเอาแค่ RCP เพียวๆก่อนครับ Call ผ่าน RMI แต่ผมยังไม่ได้ทดสอบประสิทธิภาพ RMI ของ OFBiz ว่ามี Bug ตรงไหนหรือเปล่าครับ

polawat phetra said...

หาเจอแล้ว
http://xui.sourceforge.net/wikka/wikka.php?wakka=SwtWidgets

ตอนนี้พี่คงเริ่มจากแกะส่วนของ ofbiz-pos ก่อน
จะได้เข้าใจว่ามันมีแนวคิดอย่างไรบ้าง

Ofbiz Expert said...

ยังไงเดี่ยวเอามาแชร์ครับผมก้กำลังนั่งแกะ เหมอืนกัน