Friday, December 28, 2007

Mail.app encoding

ช่วงนี้ที่บริษัทใช้ environment ผสมกัน ระหว่าง mac, windows, linux
ก็เลยเจอปัญหาพวก encoding กันบ่อยๆ

ตัว source code ไม่มีปัญหาแล้ว เพราะหลังๆผมบังคับให้ไปใช้ utf-8 กันหมดแล้ว

ตัวที่ทำให้ปวดหัวมากหน่อยก็คือ Mail
เนื่องจากผมใช้ Mail client เป็น Mail.app ของ apple
ทำให้เวลา forward, reply กันไปมา แล้วเกิดปัญหา encoding เพี้ยนขึ้น

ปัญหาหลักๆ ตัวหนึ่งเกิดที่ Mail.app
โดยปกติ mail ที่ส่งมา มันจะมี encoding แปะมาทั้งส่วน body และส่วน subject

Subject: =?windows-874?B?UkU6ILe0ys26wNLJ0uS3wg==?=
Content-Type: text/plain;
charset="windows-874"

แต่เวลาผม reply ต่อออกไป, เจ้า Mail.app ดัน set encoding เป็นแบบนี้แทน

Content-Type: text/plain;
charset=CP874;
delsp=yes;
format=flowed
Subject: =?UTF-8?B?RndkOiDguYDguKHguKXguYzguInguJrguLHguJrguJfguLXguYg=?=
=?UTF-8?B?4Liq4Lit4LiH?=

ตัว encoding ของ subject กับ body ที่ไม่ตรงกัน ถือเป็นจุดตั้งต้นของความเพี้ยน
(ยังไม่ตามต่อไป ว่า mail client ตัวไหนไปทำให้เพี้ยน แต่คาดว่าน่าจะเป็น outlook express)

ตอนนี้ผมก็เลยแก้ไข โดยการประพฤติตนเป็นพนักงานที่ดี โดยการ set default charset
ของผมให้เหมือนประชากรส่วนใหญ่ไปก่อน (ค่านี้ไม่มีหน้าจอ UI ให้ set ต้อง command line อย่างเดียว)

$ defaults write com.apple.mail NSPreferredMailCharset "CP874"

Related link from Roti

Thursday, December 27, 2007

กลับด้าน

ปกติเวลาสร้างตึกเราจะสร้างจากล่างขึ้นบน
ส่วนเวลารื้อถอนก็ทำกลับกัน นั่นคือค่อยๆทุบจากข้างบนลงมา (ยกเว้นกรณีใช้ระเบิด)

คำถามก็คือ แล้วตึกที่เขาทุบจากล่างขึ้นบนหล่ะมีไหม
พบคำตอบได้ที่นี่
http://www.dailymail.co.uk/pages/live/articles/news/news.html?in_article_id=472602&in_page_id=1770

Related link from Roti

Tuesday, December 25, 2007

vm เอ๋ย vm

ช่วงนี้ได้ server ใหม่มาใช้ เป็นพวก duo-core ด้วย ก็เลยได้ฤกษ์ทดลองเอา Xen มาใช้เสียที
(เดิมใช้แต่ vmware-server ซึ่งก็ถือว่า work มากแล้ว)

ลงแล้ว ก็เจอปัญหาอยู่หลายอย่างเหมือนกันเช่น
  • จะ enable vt ได้ ต้อง set config ใน bios ด้วย (default เป็น off)
    ทำให้หลงคิดว่าเป็นปัญหาที่ Xen อยู่พักใหญ่
  • กรณีที่เป็น Intel, แผ่นติดตั้งใหม่ๆ พวก ubuntu, opensuse ไม่สามารถติดตั้งบน xen ได้เลย
    เพราะมันใช้ isolinux ที่แสดง Logo เป็นรูป graphic
    ซึ่งจะทำให้ xen มัน hang และนิ่งไป
    ทางแก้ไข ก็คือต้องแตก แผ่นออกมา แล้วเข้าไปแก้ option ของ isolinux ไม่ให้แสดง Logo แบบ graphic
    อ่านเพิ่มเติมใน https://bugs.launchpad.net/ubuntu/+bug/83642
  • Windows 98 ติดตั้งบน Xen ไม่ได้
    ตายตั้งแต่ขั้น boot แผ่น setup เลย, โดยมีปัญหาตอน load driver ของ cdrom
  • ใช้ Xen คู่กับ vmware ไม่ได้

Related link from Roti

Friday, December 21, 2007

protected with_scope

feature หนึ่งใน rails ที่มีคนใช้บ่อยก็คือ with_scope
ใน rails 2.0, with_scope ได้เปลี่ยน visibility จากเดิมที่เป็น
public method ไปเป็น protected method แล้ว
นั่นหมายความว่า เราจะใช้ with_scope ได้เฉพาะใน model เท่านั้น (ระวังประโยคนี้ด้วย ใน ruby เราสามารถหลายๆอย่างที่ไม่น่าเป็นไปได้ได้)

ที่เป็นดังนี้ ก็เพราะว่ามีคนเอา with_scope ไปใช้ที่ระดับ controller กันเยอะ
แล้วก็เจอผล ที่ไม่ตรงตามที่คาดหวัง
เช่น เอาไปใช้ใน filter แบบนี้
แล้วไปติดปัญหาตอน save ที่มันไม่ยอม save ข้อมูล default จาก with_scope ให้
class ApplicationController  < AC::Base 
def scope_story_by_account
Story.with_scope(:find =>
{:conditions => {:account_id => current_account.id},
:create => {:account_id => current_account.id}) {
yield }
end
around_filter :scope_by_account
end

class StoryController < ApplicationController
def create
@story = Story.new(params[:story])
# do some stuff to @story here...
@story.save #...@story.account_id won't be set
end
end


แน่นอน การเปลี่ยนแปลงแบบนี้ ก็ต้องมีคนบ่น
บางคนก็บ่นว่า
I think it's a poor choice to limit something that people find useful just because it doesn't fit your idea of useful. Why not let the coder make these
decisions?


คำตอบที่ได้น่าสนใจ (David Heinemeier คนเขียน rails เป็นคนตอบเองเลย)
You must be new here :). Rails is opinionated software. We make things that we consider good style easy to do and bad style hard. Or rather, we make good style beautiful and bad style ugly. So even though with_scope is going protected, you can still use it in filters if you really, really want to. Just use send(:with_scope) -- that'll side-step the access control. Yes, that'll be ugly and it's intended to be.


อ่านตัวอย่างการใช้ที่เหมาะสมจากที่นี่ “WITH_SCOPE WITH SCOPE”

Related link from Roti

Wednesday, December 19, 2007

Caching in AssetTagHelper

ใน rails 2.0 มี feature อยู่ตัวหนึ่งที่ช่วย combine javascript หรือ CSS หลายๆ file ให้รวมกันเป็น file เดียว
ซึ่งจะช่วยเพิ่มความเร็วของการ load web ของเราได้
อย่างเช่น สมมติว่าเรามีการ include javascript ดังนี้
javascript_include_tag "prototype", "cart"

ปกติมันจะ render เป็นแบบนี้
<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="/javascripts/cart.js"></script>

แต่ถ้า เราเปิด feature cache ด้วยการใส่ option แบบนี้
javascript_include_tag "prototype", "cart", :cache => "shop"

มันก็จะ render แบบนี้แทน
<script type="text/javascript" src="/javascripts/shop.js"></script>

โดยเจ้า shop.js เกิดจากการ concat prototype.js เข้ากับ cart.js
และ gzip ก่อนส่งให้ browser

เงื่อนไขสำคัญที่จะทำให้ cache ทำงาน ก็คือ ใน environment file ต้องมีการกำหนด
config.action_controller.perform_caching             = true

ซึ่งใน default environment ค่านี้จะถูกกำหนดเป็น true ใน production mode
และเป็น false ใน development mode

อ่านรายละเอียดใน

Related link from Roti

Tuesday, December 18, 2007

ศิษย์ - ครู

อ่านเรื่องนี้แล้วรู้สึกระทึกใจ

http://gotoknow.org/blog/phoenix-mirror/154417

อ่านแล้วนึกถึงเจ้าลูกชาย ว่าจะได้พบเจอครูแบบไหน

Related link from Roti

SimpleDB

SimpleDB เป็นบริการใหม่ของ Amazon
ประเด็นที่ผมสนใจก็คือ มีคนว่ากันว่า มัน implement ด้วย erlang
แต่บางคนก็บอกว่ามันมีพื้นฐานมาจาก Amazon 's Dynamo ต่างหาก
ซึ่งตัว Dynamo นี้ implement ด้วย Java

บน Erlang มีคนทำ database ทำนองเดียวกับที่ SimpleDB ทำเหมือนกัน
ชื่อว่า CounchDB ซึ่งออกมาก่อน SimpleDB ได้พักใหญ่แล้ว
ประเด็นที่น่าสนใจสำหรับ CouchDB ก็คือ
มัน implement ด้วย erlang + spidermonkey (javascript engine)
แต่ couchDB ก็มีปัญหาซึ่งยังแก้ไม่ตก
ซึ่งในมุมของ erlang ก็ถือว่าเป็นปัญหาที่น่าสนใจมาก (สำหรับคนที่ต้องการทำ fault tolerant)

Related link from Roti

Tuesday, December 11, 2007

native dependency with maven

วันนี้มีโจทย์ที่ต้อง config maven ให้เลือกใช้ dependency แบบที่ต้องขึ้นอยู่กับ platform
ต้นตอของเรื่องก็คือเจ้า swt library ที่ต้องเลือก jar ให้ถูกกับเครื่องที่จะไป run
เช่นถ้า build บน linux ก็ควรจะใช้ swt-linux-gtk-native.jar
หรือถ้าบน windows ก็ต้องเป็น swt-win32-native.jar

เริ่มต้นด้วยการ setup repository ก่อน
หน้าตาของ repository ก็ประมาณนี้

+ swt
+ swt-linux-gtk-native
+ 3.3.0
- swt-linux-gtk-native-3.3.0.jar
+ swt-win32-native
+ 3.3.0
- swt-win32-native-3.3.0.jar


จากนั้นก็ให้กำหนด profiles ใน pom.xml
สังเกตุว่าใน profile จะมี activate block ที่ใช้ข้อมูล os เป็น condition ในการ activate
<profiles>
<profile>
<id>unix</id>
<activation>
<os>
<family>unix</family>
</os>
</activation>
<properties>
<swt.os-dep>swt-linux-gtk-native</swt.os-dep>
<swt.version>3.3.0</swt.version>
</properties>
</profile>
<profile>
<id>win32</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<swt.os-dep>swt-win32-native</swt.os-dep>
<swt.version>3.3.0</swt.version>
</properties>
</profile>
</profiles>


จากนั้นใน dependency list ของเรา ก็ให้อ้างถึง jar โดยใช้ property name แทน

<dependency>
<groupId>swt</groupId>
<artifactId>${swt.os-dep}</artifactId>
<version>${swt.version}</version>
</dependency>

Related link from Roti

Tuesday, December 04, 2007

dinosaur บุกโรงเรียนลูก

เนื่องในช่วงวันพ่อปีนี้ โรงเรียนอนุบาลของลูกผมก็ส่งคำเชิญมาว่า ให้ไปร่วมกิจกรรมในวันพ่อกัน
แต่มีข้อบังคับมาด้วยว่า คุณพ่อต้องเตรียมกิจกรรมมาร่วมกับเด็กๆด้วย
เนื่องจากช่วงนี้ลูกชายผมกำลังบ้า dinosaur, ผมก็เลยเตรียมอุปกรณ์ กะว่าจะไปชวนเด็กๆ ทำฉาก dinosaur กัน

ดูรูปแล้วกันว่าสนุกแค่ไหน

เริ่มจากการบรรยายสรุปก่อน


จากนั้นก็วาดรูป dinosaur, ใครอยากได้พันธุ์ไหน ระบุได้
แล้วก็ให้เด็กๆไประบายสี
ระหว่างนั้น ผมก็เตรียมทำฉาก


สุดท้ายก็มาจัดฉากกัน
dinosaur เยอะไปหน่อย คับฉาก


Note:
เด็ก 14 คน, วาด dinosaur ไป 14 ตัว
ยิ่งวาดยิ่งมัน นึกในใจว่ามือกำลังเริ่มเข้าที่เลย น่าจะมีเด็กเยอะกว่านี้อีกหน่อย

Related link from Roti

Wednesday, November 28, 2007

git-stash

เมื่อวาน sirn comment ถึงเจ้า Mercurial Queues extension ของ Mercurial
ตัว mq ไม่เคยใช้ แต่อ่านดูถึง feature ที่สามารถทำ stack ของ patch ได้ก็รู้สึกเคลิ้ม

วันนี้อ่านเจอคำสั่ง git-stash ที่สามารถ pending สิ่งที่ทำอยู่ แล้วย้อนกลับไปที่ clean state ก่อนหน้าได้
อันนี้แหล่ะ use-case ที่เจอบ่อย

สมมติกำลังทำ feature A อยู่ ระหว่างที่กำลังเมามันกับการ refactor
ก็ดันเกิดมี bug ร้ายแรงที่ต้องรีบแก้
ถ้าเป็น svn ง่ายสุด ก็อาจจะ checkout อีก copy
แล้วตามไปแก้ที่นู่น
แต่ถ้าเป็น git หล่ะ
# while doing something
git stash
# fix bug
git commit -a -m "xxxxx"
git stash apply
# กลับมาเมามันต่อ

Related link from Roti

Tuesday, November 27, 2007

Git อีกทีน่า

วันนี้ลองมาดูประเด็นที่ทำให้คนที่เคยใช้ cvs หรือ svn เกิดอาการเหวอเมื่อใช้ git ได้

ขั้นตอนปกติในการใช้ git
  1. สร้าง working copy ด้วยคำสั่ง git clone url
  2. แก้ไข file ที่ต้องการ
  3. สั่ง git add yourfilename เพื่อ mark ว่าต้องการ commit file นี้ในอนาคต
  4. สั่ง git commit
ดูเผินๆแล้ว จะเห็นว่าไม่น่ามีอะไร
สำหรับ user ที่ใช้ svn มาก่อน ก็จะบอกว่า
ขั้นที่ 3 มันเป็นขั้นที่เกินมาเนี่ย ใน svn ไม่เห็นต้องสั่งอะไรแบบนั้นเลย

ถ้าไม่รู้ว่าจริงๆแล้ว git ทำอะไรในชั้นที่ 3 มันก็อาจจะทำให้เกิด error ขึ้นได้
ลองจำลองเหตุการณ์ใหม่
  1. user clone project
  2. user แก้ไข file x
  3. user สั่ง git add x เพื่อบอก git ว่าต้องการที่จะ commit file นี้
  4. user กลับไป edit file x อีกรอบ
  5. user สั่ง git commit
ผลก็คือ เฉพาะการแก้ไขในขั้นที่ 2 เท่านั้น ที่ถูก commit เข้าไป
การแก้ไขในขั้นที่ 4 ไม่รวมอยู่ในนั้นด้วย
ขั้นตอนที่ถูกก็คือ
  1. user clone project
  2. user แก้ไข file x
  3. user สั่ง git add x เพื่อบอก git ว่าต้องการที่จะ commit file นี้
  4. user กลับไป edit file x อีกรอบ
  5. user สั่ง git add x อีกรอบ
  6. user สั่ง git commit
จะเห็นว่า git มีความแตกต่างกับพวก svn หรือ cvs อยู่
ตัว git เองมองสิ่งที่ track คือ content ไม่ใช่ file ทั้ง file
การที่เราสั่ง git add เป็นการสั่ง add content ที่เปลี่ยนแปลง ณ ขณะนั้น
การเปลี่ยนแปลงที่ตามหลังมา ไม่นับว่าอยู่ในกลุ่มที่จะ commit

keyword "Content" ยังนำความแตกต่างมาสู่วิธีการ commit change ด้วย
เราสามารถจัดการ content ที่ต้องการให้้ git commit ได้ในระดับ fine-grained
สมมติว่ามีการเปลี่ยนแปลงใน file ที่เราแก้ไข 5 จุด
เราสามารถเลือกได้ว่า การ commit ครั้งนี้ เราต้องการที่จะ commit เฉพาะจุดที่ 2 กับ 3 เท่านั้นนะ

ในการที่จะจัดการกับ content ที่จะ commit
เราสามารถทำได้โดยการ switch เข้าสู่ mode interactive ด้วยคำสั่ง
git add -i
ลองดูตัวอย่างการใช้งาน
สมมติว่าผมมี file x อยู่ ใน file นี้ผมมีการแก้ไข 2 จุดก็คือ
มีการ insert บรรทัดแรกสุด กับ บรรทัดท้ายสุดลงไป

เริ่มด้วยคำสั่ง git add -i
git ก็จะแสดงให้เห็นว่า file x มีการเปลี่ยนแปลงเพิ่มบรรทัดอยู่ 2 จุดนะ

pphetra@pann:~/temp/s$ git add -i
staged unstaged path
1: unchanged +2/-0 x

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help

ลองสั่ง help ดู ก็จะได้คำอธิบายย่อๆดังนี้

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 8
status - show paths with changes
update - add working tree state to the staged set of changes
revert - revert staged set of changes back to the HEAD version
patch - pick hunks and update selectively
diff - view diff between HEAD and index
add untracked - add contents of untracked files to the staged set of changes

ลองเลือก menu patch
มันก็จะแสดง file ขึ้นมา ให้เราก็ระบุหมายเลข file ที่ต้องการ

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 5
staged unstaged path
1: unchanged +2/-0 x
Patch update> 1
diff --git a/x b/x
index abe1496..61916db 100644
--- a/x
+++ b/x
@@ -1,3 +1,5 @@
+hi bunny
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/s/?]? ?
y - stage this hunk
n - do not stage this hunk
a - stage this and all the remaining hunks
d - do not stage this hunk nor any of the remaining hunks
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks

กรณีนี้เราต้องการ ที่จะ commit เฉพาะ row บนสุดเท่านั้น
เราก็สั่ง split
จากนั้น git จะแสดงการเปลี่ยนแปลงให้เราตัดสินใจทีละอัน

@@ -1,3 +1,5 @@
+hi bunny
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/s/?]? s
Split into 2 hunks.
@@ -1,3 +1,4 @@
+hi bunny
hello
hi pok
hi pann
Stage this hunk [y/n/a/d/j/J/?]? y
@@ -1,3 +2,4 @@
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/K/?]? d
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 7
Bye.
จะเห็นได้ว่า การจัดการ patch ของ git เร้าใจกว่า svn เยอะ
แต่ก็อาจจะนำความเวียนหัวมาสู่ผู้ที่คุ้นเคยกับโลกของ svn อย่างเดียวได้

Related link from Roti

Monday, November 26, 2007

Dojo DateWidget in Thai [Dojo 1.0]

จากปีก่อน ที่เขียนถึง Dojo DateWidget in Thai สำหรับ version 0.4
วันนี้ได้ฤกษ์ลอง version 1.0 บ้าง



ใน version 1.0, widget สำหรับ date ใช้ชื่อว่า DateTextBox
ซึ่งประกอบด้วย widget 2 ตัวประกอบกัน ก็คือ TextBox กับ _Calendar
โดยตัว _Calendar จะเป็น popup ที่ทำหน้าที่แสดงปฎิทิน
(เครื่องหมาย _ นำหน้า หมายความว่า dojo ตั้งใจให้ widget นี้เป็น internal widget)

สิ่งที่เราต้องการ ก็คือ เราต้องการให้ internal state ของ DateTextBox
ยังเก็บค่า Date เป็น Date object ปกติของ javascript อยู่
แต่ต้องการให้เฉพาะส่วนการแสดงผลเท่านั้น ที่แสดงผลเป็นปี พ.ศ.
นอกจากนั้นยังต้องการให้ calendar ที่ popup ขึ้นมาแสดงผลเป็นภาษาไทยด้วย

ใน dojo 1.0, ตัว widget ซับซ้อนขึ้น
มีการ reuse เยอะขึ้น อย่างเช่น DateTextBox นั้นมี inherited structure ดังนี้

DateTextBox -> TimeTextBox -> RangeBoundTextBox ->
MappedTextBox -> ValidationTextBox -> TextBox

จากการไล่การทำงานของ DateTextBox เราจะพบจุดตัดที่เราสามารถ override
ส่วนการแสดงผลได้ที่ method format และ parse
สามารถนำมา declare class ตัวใหม่ที่ชื่อ ThaiDateTextBox ได้ดังนี้
dojo.declare("ThaiDateTextBox", [dijit.form.DateTextBox], {

// กำหนดตัว calendar ตัวใหม่ที่แสดงผลภาษาไทย
_popupClass: "ThaiCalendar",

// value ที่รับเข้ามา มี type เป็น date object
// เมื่อได้มาแล้วก็โยนไปให้ dojo.date.locale.format จัดการ format ให้ก่อน
// แล้วค่อยนำมาตัด ปี เพื่อ + 543 หรือ 43 เข้าไปอีกที (ขึ้นอยู่กับ format)
// note: มี bug กรณี format yy ที่จะมี overflow
format: function(value, constraints) {
if(!value || value.toString() == this._invalid){ return null; }
var sidx = constraints.datePattern.indexOf('yy');
var fvalue = dojo.date.locale.format(value, constraints);
var ycnt = 2;
var adj = 43;
if (/yyyy/.exec(constraints.datePattern)) {
ycnt = 4;
adj = 543;
}
return fvalue.substr(0,sidx) + (parseInt(fvalue.substr(sidx, ycnt)) + adj);
},

// value ที่รับเข้ามา จะอยู่ในรูป datePattern ที่เรากำหนดตอน declare widget
// เมื่อได้มา ก็ทำการ - 543 เพื่อแปลงเป็น ค.ศ. ก่อนส่งให้ dojo.date.locale.parse
parse: function(value, constraints) {
var sidx = constraints.datePattern.indexOf('yy');
if (/yyyy/.exec(constraints.datePattern)) {
nyear = parseInt(value.substr(sidx, 4)) - 543;
} else {
nyear = String(parseInt(value.substr(sidx, 2)) + 2500 - 543).substr(2,2);
}
var newValue = value.substr(0, sidx) + nyear;
return dojo.date.locale.parse(newValue, constraints);
}
});


ส่วนตัว Calendar ที่ popup ขึ้นมาให้ user เลือกนั้น
ถ้าไล่ดู code ภายใน จะเห็นได้ว่า เขามีกลไก i18n ในการแสดงผลพวกชื่อเดือน ชื่อวัน
แต่เนื่องจากเจ้า dojo 1.0 มันไม่ได้ bundle locale ของไทยมาให้ด้วย
เราจึงต้องสร้างขึ้นมาเอง

ในการที่จะสร้าง locale th
เจ้า dojo ได้เตรียมกลไกไว้เรียบร้อยแล้ว
โดย script ที่จะใช้สร้าง จะอยู่ใน directory util/buildscript/cldr
ซึ่ง dojo ได้นำข้อมูล xml จาก Common Locale Data Repository
และเตรียม xlst file ซึ่งใช้สำหรับ generate เป็น javascript code ให้เราแล้ว
แค่เราเข้าไปแก้ไข build.xml เพิ่ม "th-TH" เข้าไปในรายการที่ต้องการให้ generate

<!-- Arbitrary defaults. locales and currencies properties can be altered or eliminated to build the entire set -->
<property name="locales" value="en-us,th-th"/>


ประเด็นปัญหาถัดไปก็คือตัวปี พ.ศ.
ถึงแม้เราจะเลือกใช้ locale th-TH แล้วก็ตาม
มันก็ยังมีปัญหาว่า ปี ยังได้เป็นปี ค.ศ. อยู่ดี
ตรงส่วนนี้ เราต้องแอบเข้าไป override method ใน Calendar widget
ในใน dojo 0.4, จุดตัดสำหรับเรื่องนี้ค่อนข้างง่าย เพราะคนเขียนเขาแบ่ง method ออกเป็น method ย่อยๆ เพื่อให้อ่านง่าย
แต่ใน dojo 1.0 คนเขียน เขาเขียนส่วน render ยาวเป็นพรืด ทำให้เราเลือก override ยากหน่อย

สำหรับกรณีนี้ เราเลือก override method "_setText"
ซึ่งเป็นทางเลือกที่ไม่ค่อยดีนัก เพราะเป็นการ override แบบที่แอบไปใช้กลไกภายในของมัน
เพราะมันมีผลว่า ถ้ามันมีการ upgrade internal structure เมื่อไร
class ของเราก็จะมีโอกาส broken ได้โดยง่าย
dojo.declare("ThaiCalendar", [dijit._Calendar], {
_setText: function(node, text) {
if (/YearLabelNode/.exec(node.getAttribute("dojoattachpoint"))) {
arguments[1] = parseInt(text) + 543;
}
return this.inherited('_setText', arguments);
}
});


เวลานำไปใช้ ก็ใช้แบบนี้
<script type="text/javascript" src="./js/dojo/dojo.js"
djConfig="isDebug: true, parseOnLoad: true, locale: 'th'"></script>

...

<span id="d1" dojoType="ThaiDateTextBox"
constraints="{datePattern:'dd/MM/yy'}"/>

Related link from Roti

Thursday, November 22, 2007

ทำความรู้จัก LVS

งานของผมนอกเหนือจากพัฒนา application ที่เป็น web แล้ว
ก็ยังมีงานเก่าๆที่เป็นพวกจอเขียวๆ (พวก terminal) อยู่ด้วย

ช่วงนี้มีโจทย์ที่ต้อง solve ก็คือ ต้องทำให้ telnel service นั้นมีคุณสมบัติ HA (High Availability)
ตัวเครื่องมือที่เลือกใช้ก็คือ LVS (Linux Virtual Service)
ซึ่ง support service ที่ run อยู่บน TCP หรือ UDP
ตัว LVS จัดได้ว่าเป็น Layer 4 Switching

คำศัพท์เบื้องต้น

  • Director คือเครื่องที่ทำหน้าที่รับ packet จาก client และ forward ต่อให้ Real Server

  • Real Server คือเครื่องที่ทำหน้าที่ให้บริการจริง

  • Virtual IP Address (VIP) คือ IP ที่ client ใช้ติดต่อ service

  • Real IP Address (RIP) คือ IP ของ Real Server




รูปแบบการ config LVS แบ่งออกเป็น 3 ประเภทตามลักษณะการ forward packet คือ
  1. NAT (Network Address Translation)
    packet ที่ส่งมาจะถูกแปลงเบอร์ IP address แล้วส่งต่อให้ real server
    ส่วน packet ที่ real server ตอบ ก็จะต้องส่งผ่าน Director เพื่อแปลง IP Address กลับเป็นตัวเดิม
  2. DR (Direct Routing)
    packet ที่ส่งมาจะไม่ถูกแปลง แต่จะถูก forward ไปให้ real server เลย
    เมื่อ real server ตอบ, packet ก็จะส่งกลับไปที่ client โดยตรง
    Note: เพื่อให้ real server ยอมรับ packet ที่ forward มา (ซึ่งมี IP Address เป็น VIP)
    ก็ต้องมีสร้าง hidden dummy interface)
  3. IP-IP Encapsulation (Tunnelling)
    คล้ายๆ direct routing แต่ packet ที่ถูก forward จะถูกห่อก่อนจะส่งไปให้ real server
    เพื่อที่จะได้ส่งข้ามวง network ได้


concept ที่สำคัญอันหนึ่งของ LVS ก็คือ Scheduling
หรือการจัดสรร request แจกให้กับ real server
ซึ่งมี algorithm ให้เลือกเยอะเลย เช่น

  • Least-Connection (lc): Allocate connections to the real-server with the least number of connections.

  • Weighted Least-Connection (wlc):

  • Weighted version of Least-Connection.

  • Round-Robin (rr): Place the real-servers in a circular list and allocate connections to each real-server in turn.

  • Weighted Round-Robin (wrr): Weighted version of rount-robin.

  • Locality-Bassed Least-Connection (lblc): Try to assign connections addressed to the same IP address to the same real-server. This is frequently used in conjunction with transparent http proxy services.

  • Locality-Based Least-Connection with Replication (lblcr): Variation of Locality-Bassed Least-Connection that allows a pool of servers for a given destination IP address to be maintained in sutiations of high load.

  • Destination-Hashing (dh): Use a static hash of the destination IP address to allocate connections.

  • Source-Hashing (sh): Similar to Destination-Hashing, but the source IP address is hashed.

  • Shortest Expected Delay (sed): Allocate connections to the server that will service the request with the shortest expected delay.

  • Never Queue (nq): Allocate a connections to a idle real-servers if there are any, else use the Shortest Expected Delay altgorithm.


ที่ผมชอบก็คือ เราสามารถ config น้ำหนัก(weight) ให้กับแต่ละ server ได้
ทำให้เรา mix real server ระหว่างเครื่อง performance ดี กับเครื่องปกติได้

การใช้ LVS อย่างเดียว ไม่สามารถทำ HA ได้
เนื่องจากเราต้องเตรียมเครื่อง Director ไว้มากกว่า 1 เครื่อง
ทำให้เราต้องใช้ Heartbeat เข้ามาช่วย config ว่า
ถ้าเครื่อง director เครื่องหลักตาย เครื่องสำรองต้องเข้ามา take over งานไปทำ

นอกจากนั้นยังมีประเด็นที่ Director ยังต้องสามารถ monitor เครื่อง Real server ด้วย
ถ้า real server ตายไป จะได้จัดการปรับปรุง route table ของตัวเอง ไม่ให้แจกงานไปที่ server ตัวนั้นอีก
กรณีนี้ผมเลือกใช้ Ldirectord

เนื่องจาก service ที่ผมจะ implement คือ telnet service
ที่มีธรรมชาติที่เปิด connection ค้างไว้ตลอด
กรณีที่ Director down ไป จะทำให้ session ของ user หลุดหมด
เพื่อแก้ปัญหานี้ ก็ต้องเลือกใช้ Connection Synchronisation
โดยการระบุให้เครื่องหลักเป็น master และเครื่องสำรองเป็น backup

default synchronization model ของ LVS เป็นแบบ master-slave ซึ่งจะมีปัญหาตามมาว่า
ถ้าเครื่องหลักมัน up ขึ้นมาอีกครั้ง การส่งข้อมูล routing table กลับจะทำอย่างไร
สำหรับกรณีนี้มีคน implement model การ synchronization แบบ peer-to-peer ขึ้นมา
ซึ่งผมเห็นแต่ในเอกสาร แต่ยังหา source ไม่เจอ

Related link from Roti

Sunday, November 18, 2007

ความผิดพลาด

ผมชอบที่อาจารย์สกล เขียนไว้ในนี้ Link

ความผิดพลาดนั้นเป็นของควบคู่กับงาน แต่ความผิดพลาดของหมอกับของครูนั้น มีความรุนแรงเหมือนกันอยู่อย่างนึง ของหมอนั้น ความผิดพลาดมักจะไปลงเอยหลายที่ หน้าหนังสือพิมพ์ website ปาฐกถา บรรยาย small group, etc และเป็นเรื่องของความเป็น ความตาย หรือ พิการ ส่วนความผิดพลาดของครูนั้น อาจจะหมายถึงชาติกำเนิดและการพัฒนาจิตที่บิดเบี้ยว เสียรูปร่างอย่างแรง และกรอบการเติบโตอันคับแคบ อับจนปัญญา ไร้ความคิดคะนึงหา ติดกับดักวิญญาณ การใช้ชีวิตที่ไม่เต็มสมศักยภาพของตนเอง

Related link from Roti

Thursday, November 15, 2007

ย้อนทบทวน git

ในงาน codefest เมื่อสองอาทิตย์ก่อน ผมทดลองนำ git มาใช้
ซึ่งผลของเวลาที่จำกัด + การที่ไม่เคยใช้มันมาก่อน ก็ทำให้มีประสบการณ์ที่ไม่ค่อยดีกับมันนัก

เริ่มแรกสุดลองดู graph ของการ merge ที่เกิดขึ้นก่อน



ดูก็สวยดีนะ แต่จริงๆแล้วมันมั่วมาก
เนื่องจากมี คน develop อยู่ 4 คน
ประเด็นแรกก็คือ มันควรจะตั้งให้คนใดคนหนึ่งเป็น ศูนย์รวม
แล้วก็ให้แต่ละคนที่เหลือ push กับ pull ผ่านทางจุดที่เป็นศูนย์รวมนั้น
แต่ปรากฎว่าเวลา เรา start git-daemon
default mode ของมันก็คือ pull ไปได้อย่างเดียว ไม่รับ
การ push กลับ
เนื่องจากช่วงนั้นผม concentrate กับ erlang อย่างเดียว
เลยไม่มีสมาธิมาลองหาดูว่า ต้องใส่ flag อะไร เพื่อให้มันสามารถรับการ push ได้
ตอนนั้นเลยแก้ปัญหาด้วยการใช้ pull อย่างเดียว
ใครทำอะไรเสร็จ ก็ให้ start server จากนั้นก็ให้ทีเหลือ pull ออกมาจากคนนั้น
มารู้ทีหลังว่า ถ้าต้องการ start daemon ให้รับ push ได้ด้วยนั้น
ต้องไส่ flag ให้มัน enable service "receive-pack" ด้วย

git-daemon --reuseaddr --export-all --base-path=. --enable=receive-pack .


ประเด็นถัดไป ก็คือ
ปกติในการเก็บ version control เราจะ ignore file พวกที่เป็นผลลัพท์จากการ compile
ใน git เราสามารถระบุ file ที่ ignore ได้ใน file .git/info/exclude
แต่ปัญหาก็คือ ตอนที่มีคน clone code ออกจาก repository เรา
เจ้า file exclude มันไม่ได้ไปด้วย
ผลก็คือ น้องๆที่เอาไปพัฒนาต่อ commit file พวกนี้เข้าใน repository กันทุกคน
พอถึงเวลา pull กลับมา มันก็จะเกิด error ฟ้องว่า merge ไม่ได้
วันนี้มานั่งอ่านคู่มือไล่หาว่าเราควรกำหนด exclude file อย่างไรดี
กลายเป็นว่า เวลาเรา clone repository
file ที่อยู่ใต้ .git มันจะเอามาจาก /usr/share/git-core/templates

Related link from Roti

Friday, November 09, 2007

Ruby Business Commons

เมื่อวานไปนังฟัง Mr.Eihiro Saishu present หัวข้อเรื่อง Ruby Business Commons
ฟังหัวข้อตอนแรก นึกยังไงก็นึกไม่ออกว่ามันเป็นเรื่องเกี่ยวกับอะไร, ค้นใน net ก็พบว่ามี project อยู่ใน rubyforge
ตามไปดูก็ไม่พบข้อมูลเพิ่มเติม เห็นแต่ว่ามี code กองอยู่จำนวนหนึ่ง

ได้ไปฟังแล้วก็ประทับใจมาก เหมือนได้ฟังพวกคอเดียวกันพูด
present ของ Eihiro ทำออกมาได้แตกต่างจากคนอื่นในงานดี
ออกมาในแนวของ ใช้คำน้อย, สื่อสารด้วย keyword ไม่ไช่ bullet

Ruby Business Commons คือ Community ของคนที่มี passion ใน ruby
รวมกลุ่มกัน เรียนรู้, พัฒนา และที่สำคัญก็คือ สังสรรค์
แล้วก็มีกิจกรรม knock (ร่วมกลุ่มกัน implement project)
ตัวอย่าง knock ครั้งที่ 1 ของเขาที่เมือง Tenjin น่าสนใจมาก
ลองอ่านเพิ่มเติมใน slide
ส่วน web site ของตัว Community อยู่ที่นี่ Link
แต่เสียใจด้วยมันเป็นภาษาญี่ปุ่น (โชคดีที่บริษัทฯผมมีคนอ่านญี่ปุ่นได้)

อ่านกิจกรรม knock ของเชาแล้ว เริ่มฝันเห็นแนวทางรำไรของ Bangkok BarCamp แล้ว

อานิสงค์ของการไปฟังครั้งนี้ ก็เลยได้พบคนใช้ ruby ในไทยเพิ่มอีกหลายคน
เช่น น้องฝน(ที่เขียน wiki เรื่อง rails) และคุณชุมพล ครุฑแก้ว จาก nectec
แล้วก็น้องพีรพงษ์ จากบริษัท Thai Software Engineering
ที่กำลังใช้ rails พัฒนา product ให้กับบริษัทญี่ปุ่น(กล้องนิคอน)

Related link from Roti

Thursday, November 08, 2007

จบ Codefest ไปอีกครั้ง

คราวนี้โชคไม่ดีตรง กอบ เพื่อนร่วมทีมผม ที่รับผิดชอบงานด้าน
server side erlang เกิดไม่สบาย
ผมเลยต้องทำแทน
ด้วยความที่ไม่ได้เตรียมตัวเท่าที่ควร (เตรียมอยู่ 2 วัน)
อาการ panic ก็เลยเกิดขึ้น
ช่วงเช้าถึงบ่าย เป็นเวลาทีื่ชุลมุนที่สุด
เนื่องจากอยู่ในช่วง setup ปรับตัวกัน
ต้องแบ่งสมาธิไปทำหลายอย่าง ทำให้ไม่มีสมาธิที่จะทำโปรแกรม

ทืมผมได้กว้านเอาน้องๆแถวนั้นมาร่วมทืมอีก 3 คนคือ
น้องป้อ (iporsut.wordpress.com) จากสุรนารี
น้องซี (kobkrit.blogspot.com) กับน้อง boy จากธรรมศาสตร์
3 คนนั้นก็เลยได้ลิ้มรส erlang เป็นครั้งแรก
น้องป้อได้เปรียบหน่อยตรงที่คุ้นชินกับ haskell มาแล้ว

ตัว version control ที่ใช้ก็คือ Git
ซึ่งสร้างความปวดหัวเวียนเกล้าได้มาก เวลาที่มันเกิด exception case ขึ้นมา
(เวลาเราอ่าน tutorial เรามักจะทำไปตาม flow ที่รื่นไหล)
ร่ำๆจะเปลี่ยนไปใช้ svn แทน

การเขียน erlang คราวนี้ทำให้รู้จุดอ่อนอย่างหนึ่ง
(จุดอ่อนของคนเขียน ไม่ใช่ของภาษา)
เนื่องจาก erlang มี characteristic อย่างหนึ่งก็คือเรื่อง fail-fast
พอมี process หนึ่งตาย โปรเซสที่ link กันอยู่ ก็จะตายด้วย
ด้วยความที่เราทดสอบโปรแกรม จาก shell command เป็นหลัก
พอเราทำอะไรผิดนิดผิดหน่อย โปรเซส shell ก็จะตาย
ส่งผลให้ process ที่ start ด้วย generic behavior ทั้งหลาย
(พวกนี้จะ link กับ process แม่ที่ start มัน)
ตายตามเป็นแถว
ดังนั้นงาน routine ส่วนใหญ่ใน codefest ก็คือ restart process.
ตอนนั้นก็ได้แต่สงสัยว่า เอ๊ะเราไม่ได้ link ตัวนี้กับตัวนั้น
แต่ทำไมพอตัวนี้ตาย ตัวนั้นตายด้วยหว่า
มาร้องอ๋ออีกที ตอนสายๆของอีกวันหนึ่ง

จุดอ่อนการเขียน erlang อีกเรื่องก็คือการใช้ io:format
ที่เรามักใช้ในการ print output ออกมาดูที่ console ว่า โปรแกรมทำงานถูกต้องตามที่ต้องการไหม
ปรากฎว่า io:format มันมักจะรบกวนการทำงานของ process
ส่งผลให้ process มันมีพฤติกรรมแปลกๆไป
อย่างเช่น socket ที่เปิดอยู่ ถูกปิดไป, โดย process ที่ control ไม่ได้ตายไปด้วย
bug ตัวนี้ผมใช้เวลาหาอยู่ตั้งแต่ช่วงหกทุ่มถึง 7 โมงเช้า
ร่ำๆจะยอมแพ้ ไม่จบงาน ก็หลายที
แต่สุดท้ายมาปิ๊งว่าเป็นเรื่องนี้ ตอนที่ไปอาบน้ำ

งาน codefest คราวนี้มีประเด็นที่น่าสนใจอยู่เรื่องก็คือ
lead ในงานคราวนี้คือทืมจากญี่ปุ่น, ซึ่งเขามีแนวทางในการ lead project
ที่น่าสนใจมาก เช่นใช้ wiki เป็นตัวสื่อสารหลัก
การจัด schedule เวลา ฯลฯ
เสียดายมากที่ผมต้องทุ่มสมาธิไปที่ erlang อย่างเดียว
ก็เลยไม่ได้เข้าไปสังเกตและเรียนรู้วิธีการของเขา

ส่วนงานอีกเรื่องที่เกี่ยวกับ MapServer
อันนี้ผมเตรียมมาก่อนแล้ว น้องๆก็เลยทำกันเองได้ ไม่ต้องเข้าไปช่วย
ซึ่งผมคิดว่าคุ้มมากที่พาน้องๆที่บริษัทฯมา
เพราะ
1. การโปรแกรมในเวลาระยะสั้น ต้องการการสื่อสารที่ชัดเจน
ต้องการความเข้าใจใน tool, framwork เพื่อใช้ในการวินิจฉัยปัญหา
ปัญหาที่น้องๆเจอ น่าจะทำให้เขารู้ว่า จุดอ่อนของเขามันมีอยู่ที่ไหนบ้าง
เช่น น้องอั๋นหาวิธีการ response ajax request แบบไม่ได้ใช้ template render
อยู่หลายชั่วโมง, ซึ่งถ้าเขาเข้าใจ concept ของ framework ชัดเจน
เวลาในการ solve ก็จะเหลือไม่กี่นาทีแทน
2. การ present ผลงาน ทำให้เรารู้จักเตรียมตัว, การซักซ้อม,
การคัดเลือกเนื้อหาที่จะนำเสนอ
งานนี้ผมปล่อยให้น้องๆนำเสนอกันเอง
ซึ่งก็เกิดความผิดผลาดกันถ้วนหน้า
เช่นคุมเวลาไม่ได้, เนื้อหาไม่น่าสนใจ, program ไม่ work อย่างที่อยากจะแสดงให้ดู

Related link from Roti

Friday, November 02, 2007

XMLSocket(Flex) กับ Erlang

protocol ของ XMLSocket กำหนดไว้ว่า ข้อมูลที่ส่งแต่ละชุดจะปิดท้ายด้วย Zero Byte
นี่คือ code ง่ายๆของ Erlang ที่เปิดรับข้อมูลที่ส่งจาก XMLSocket
%% echo server แบบง่ายๆ
start() ->
{ok, Listen} = gen_tcp:listen(4114, [list,
{reuseaddr, true},
{active, true}]),
{ok, Socket} = gen_tcp:accept(Listen),
gen_tcp:close(Listen),
loop(Socket).

loop(Socket) ->
receive
{tcp, Socket, Data} ->
%% ก่อน parse ก็ให้ตัด byte สุดท้ายที่เป็น zero byte ออกก่อน
Txt = xmlutil:parse(strip(Data, right, $\0)),
%% ตอนส่งกลับก็ให้ต่อท้ายด้วย zero byte กลับไปด้วย
gen_tcp:send(Socket, concat(xmlutil:encode(concat(Txt, " pok")), [0])),
loop(Socket);
{tcp_closed, Socket} ->
io:format("Socket closed~n")
end.


ตัว XMLSocket ถึงจะตั้งชื่อให้มี XML นำหน้า แต่จริงๆแล้วมันก็เหมือน XMLHttpRequest ของ javascript
ที่ใช้ส่งข้อมูลอะไรก็ได้ ตามแต่ใจเรา
อย่างที่ผมเอามาใช้ แทนที่จะส่งข้อมูลมาให้ erlang ในรูปแบบ XML (ซึ่งเราขี้เกียจ parse)
ก็ให้ส่งมาเป็น erlang tuple เลย จะได้เอาไป execute ต่อได้ง่ายๆ

ใน erlang, มันมีวิธี eval String ง่ายๆดังนี้
eval(S,Environ) ->
{ok,Scanned,_} = erl_scan:string(S),
{ok,Parsed} = erl_parse:parse_exprs(Scanned),
erl_eval:exprs(Parsed,Environ).

ลอง run ดู
2> simple_server:eval("X=1+2.",[]).
{value,3,[{'X',3}]}
3> simple_server:eval("{plus,1,2}.",[]).
{value,{plus,1,2},[]}


เวลาเราเอาไปใช้กับ XMLSocket ก็จะเขียนประมาณนี้
loop(Socket) ->
receive
{tcp, Socket, Data} ->
Str = strip(Data,right,$\0),
{value, Cmd, _} = eval(Str,[]),
Result = execute(Cmd),
gen_tcp:send(Socket, concat(Result, [0])),
loop(Socket);
{tcp_closed, Socket} ->
io:format("Socket closed~n")
end.

execute({hello}) -> "world";
execute({who}) -> "pok";
execute({plus, X, Y}) -> integer_to_list(X+Y).


Update:
กรณีถ้าต้องการ parse จาก String เป็น Term
ใช้ code แค่นี้ก็พอ
eval(S) ->
{ok,Scanned,_} = erl_scan:string(S),
{ok,Term} = erl_parse:parse_term(Scanned),
Term.

Related link from Roti

Thursday, November 01, 2007

เตรียมการ codefest

เหลืออีก 4 วันก็ถึง codefest แล้ว
ช่วงนี้ก็เลยวุ่นอยู่กับการเตรียมการ
เนื่องจากหัวข้อที่ผมเลือกทำ codefest,
ผมเลือกจากสิ่งที่ไม่รู้หรือไม่เคยทำ
ตอนนี้ก็เลยสนุกกับการบุกเบิกในสิ่งที่ไม่รู้

โปรเจคแรก เป็นโปรเจคที่ present ข้อมูลผ่าน Map
ซึ่งต้องยุ่งกับ MapServer



ความยากจะอยู่ที่ jargon หรือ Term ต่างๆที่เป็นเรื่องทางภูมิศาสตร์
เช่นถ้าใช้ wms (web service ของพวก GIS)
ค่า SRS (Spatial Referential System) ของไทยต้องกำหนดเป็นค่าอะไร?
ความยุ่งที่สอง ก็คือ ตอน present map ด้วย javascript
ผมเลือกใช้ Ka-Map ซึ่งมี feature ทำนอง Google Map
แต่ Ka-Map มันใช้ server-side เป็น php
ซึ่งผมต้องการเปลี่ยนเป็น Rails แทน
โชคดีที่มี project Ruby In Space เขาใช้ rails เป็น server-side เช่นกัน
ก็เลยไปเอา code ของ project นั้นมาใช้
แต่ก็ไม่วายโชคร้าย ตรง code base มันเก่าแล้ว
มันก็เลยมี error จากการที่ api ของ mapserver มันเปลี่ยน signature
แต่ก็แก้ได้ไม่ยาก
ไปยากตรงงมว่ามันใช้อย่างไร
ส่วนของ embeded code, ผมทำแต่ส่วน high level ก็เลยไม่มีปัญหามากนัก
เจอ bug ตรง firmware หน่อยหนึ่ง

โปรเจคที่สองเป็นโปรเจคที่ทำด้วย erlang + flex
ความยากอยู่ที่ นานๆจะใช้มันที
มันก็เลยฝึดๆ
กว่าจะทำอะไรได้ที ต้องเปิดคู่มือวุ่นวาย

แต่ที่ต้องยกให้เป็นพระเอกของงานนี้ก็คือ
vmware server
เพราะแค่เตรียมสภาพแวดล้อมเป็น vertual machine
แล้วแจกจ่ายให้ทืม ก็ช่วยลดแรงเสียดทานไปได้เยอะแล้ว
(แรงเสียดทานของ version conflict, library ไม่ครบ, config ไม่ถูก, Windows vs Linux)

Related link from Roti

Friday, October 26, 2007

cherry-picking

ช่วงนี้กระแส git กำลังมาแรง
เวลาอ่าน feed ก็จะได้เห็นเรื่องเกี่ยวกับ dvcs เยอะหน่อย
อย่างวันนี้เจอศัพท์ว่า “cherry-picking”

cherry-picking เป็น feature หนึ่งของ darcs
ความหมายก็คือ เวลาเรา push(หรือ pull) patch ไปอีก repository หนึ่ง
เจ้าโปรแกรม darcs มันจะค่อยๆนำเสนอ patch ทั้งหมดที่เกิดขึ้นใน local repository ของเรา
จากนั้นก็ให้เราเลือกตัดสินใจว่า จะ push patch นั้นหรือไม่
@pann[~/projects/javascript/test]$ darcs push ../dojo-grid/

Fri Oct 26 11:07:32 ICT 2007 pphetraATgmail.com
* remove unused css
Shall I record this patch? (1/?) [ynWsfqadjkc], or ? for help: ?
How to use record...
y: record this patch
n: don't record it
w: wait and decide later, defaulting to no

s: don't record the rest of the changes to this file
f: record the rest of the changes to this file

d: record selected patches, skipping all the remaining patches
a: record all the remaining patches
q: cancel record

j: skip to next patch
k: back up to previous patch
c: calculate number of patches

Shall I push this patch? (1/1) [ynWvpxqadjk], or ? for help: y
Finished applying...


เกร็ด: เวลาศึกษา darcs แล้วจะงงนิดหน่อย
เพราะเรามักจะคุ้นกับ concept "versions"
เช่น checkout revision 3172, ทำ tag release 0.1
แต่ใน darcs, concept ของมันจะวางอยู่บนคำว่า "patchs"
คนเขียน darcs ก็คือ David Roundy ซึ่งเป็น physics professor ที่ Berkeley
เขาเขียนโดยอิงกับ “theory of patches” (พึ่งเคยได้ยินเหมือนกัน)

ข้อดีอย่างหนึ่งของการวาง concept อยู่บน patch
ก็คือ มันบังคับให้ developer ต้องเขียนคำอธิบาย patch ดีๆ
ไม่เหมือนกับพวก commit message ของ cvs, svn ที่ developer เขียนส่งเดชอย่างไรก็ได้
เพราะถ้าคำอธิบายไม่ดีแล้ว รับรองว่าการ sync ระหว่างกัน
มันจะมั่วและสับสนมาก

Note: git ก็มี cherry-picking เหมือนกัน
แต่มันใช้ concept คนละอย่างกับ darcs
(ซึ่งส่งผลให้แนวการจัดการแตกต่างกัน)
อ่านเพิ่มเติมเรื่องนี้ Link

อืมม์ งาน codefest คราวนี้จะใช้ git หรือว่า darcs หรือ hg ดีหว่า

Related link from Roti

Friday, October 19, 2007

ลองใช้ Ruby call Webservice ของสรรพากร

คุณ krunaporn เขียน แนะนำบริการ Web Service ของสรรพากร ใน blognone
ก็เลยจะลองใช้ ruby เรียกใช้ดูสักหน่อย
ดูสิว่าจะมีปัญหาหรืออุปสรรคอะไรบ้าง

เริ่มจาก Library ที่จะใช้ก่อน
ตรงนี้เราจะเลือกใช้ soap4r

จากนั้นก็เลือกบริการของสรรพากรที่เราจะทดสอบ
โดยเราจะเลือกบริการตรวจสอบเลขที่บัตรประชาชน ซึ่งมี wsdl อยู่ที่
https://rdws.rd.go.th/ServiceRD/CheckTINPINService.asmx?wsdl

ขั้นถัดไป ก็คือการ generate stub code จาก wsdl
โดยใช้ script ของ soap4r ที่ชื่อ wsdl2ruby.rb
ลองสั่ง

wsdl2ruby.rb --wsdl https://rdws.rd.go.th/ServiceRD/CheckTINPINService.asmx?wsdl --type client

จะได้ Error ตัวแรกออกมาก็คือ

at depth 0 - 20: unable to get local issuer certificate
F, [2007-10-19T14:41:50.023698 #12255] FATAL -- app:
Detected an exception. Stopping ... certificate verify failed (OpenSSL::SSL::SSLError)
/usr/lib/ruby/gems/1.8/gems/httpclient-2.1.2/lib/httpclient.rb:1039:in `connect'
/usr/lib/ruby/gems/1.8/gems/httpclient-2.1.2/lib/httpclient.rb:1039:in `ssl_connect'

ดูจาก error ก็จะเดาได้ว่า server certificate เป็นแบบ self sign ทำให้ OpenSSL มันฟ้อง error ออกมา
ตามไปแกะดูใน soap4r จะเห็นว่า soap4r มันใช้ library ที่ชื่อ httpclient
ซึ่งถ้าเราไปอ่านดูคู่มือ ก็จะเห็นว่ามันมี option ให้เลือกได้
ว่าจะ bypass การตรวจสอบ server certificate หรือไม่
แต่ปัญหาของเราก็คือ เราจะกำหนด option นั้นอย่างไร (เนื่องจาก script wsdl2ruby ไม่มีช่องทางให้ pass parameter ตัวนี้เลย)
การแก้ปัญหาที่ง่ายที่สุด ก็คือ
download เอา wsdl มาเก็บไว้ก่อน
จากนั้นก็ค่อยสั่ง
 
wsdl2ruby.rb --wsdl rdservice.wsdl --type client


ผลลัพท์จากการสั่ง wsdl2ruby.rb, เราจะได้ file ออกมา 4 file คือ
  • CheckTINPINServiceClient.rb
    file นี้เป็นโครงตัวอย่างการใช้งาน service
  • defaultDriver.rb
    file นี้เป็น Class ที่ represent service
  • defaultMappingRegistry.rb
    file นี้กำหนด mapping ของ parameter
  • default.rb
    file นี้เป็น class ที่ใช้ represent parameter กับ return value ของ service


ลองมาทดสอบเขียนโปรแกรมกัน
# เริ่มด้วยการ require soap4r
require 'rubygems'
gem 'soap4r'

# include generated library
require 'defaultDriver'

# initialize service
service = CheckTINPINServiceSoap.new()

# parameter
parameters = ServicePIN.new('anonymous', 'anonymous', '3100905022221')

# invoke service
resp = service.servicePIN(parameters)
puts resp

ลอง run ดู, จะได้ error ตัวเดิม
at depth 0 - 20: unable to get local issuer certificate
/usr/lib/ruby/gems/1.8/gems/httpclient-2.1.2/lib/httpclient.rb:1039:in `connect': certificate verify failed (OpenSSL::SSL::SSLError)
from /usr/lib/ruby/gems/1.8/gems/httpclient-2.1.2/lib/httpclient.rb:1039:in `ssl_connect'
from /usr/lib/ruby/gems/1.8/gems/httpclient-2.1.2/lib/httpclient.rb:1466:in `connect'

ทำไงดีหล่ะ จะ set option ไม่ให้ check server certificate อย่างไรดี
ค้นใน google ก็หาไม่เจอ
สุดท้ายต้องแกะเอาจาก source code ของ soap4r
ได้ความว่า ต้องใช้คำสั่งนี้
service.options['protocol.http.ssl_config.verify_mode'] = 
OpenSSL::SSL::VERIFY_NONE


อุปสรรคถัดไป ก็คือ มัน run ได้แล้วหล่ะ
แต่ผลลัพท์ที่ได้มันคืออะไร
ลองใช้ pretty printer สั่งพิมพ์ดู ได้ผลลัพท์นี้ออกมา

pp resp # =>
#<ServicePINResponse:0xb77b11e8
@servicePINResult=
#<ServicePINResponse::ServicePINResult:0xb77b10f8
@diffgram=
#<SOAP::Mapping::Object:0x..fdbbd53ca {}NewDataSet=#<SOAP::Mapping::Object:0x..fdbbd50b4
{}Message=#<SOAP::Mapping::Object:0x..fdbbd503c {}Code="E00008" {}Description="\340\271\200\340\270\245\340\270\202
PIN \340\271\204\340\270\241\340\271\210\340\270\226\340\270\271\340\270\201\340\270\225\340\271\211\340\270\255\340
\270\207 \340\271\200\340\270\231\340\270\267\340\271\210\340\270\255\340\270\207\340\270\210\340\270\262\340\270\201
\340\270\253\340\270\241\340\270\262\340\270\242\340\271\200\340\270\245\340\270\202PIN\340\271\200\340\270\233\340
\271\207\340\270\231\340\270\225\340\270\261\340\270\247\340\270\255\340\270\261\340\270\201\340\270\251\340\270\243
\340\270\253\340\270\243\340\270\267\340\270\255\340\271\200\340\270\245\340\270\202PIN\340\271\200\340\270\233\340
\271\207\340\270\231\340\270\225\340\270\261\340\270\247\340\270\255\340\270\261\340\270\201\340\270\251\340\270\243
\340\270\253\340\270\243\340\270\267\340\270\255\340\270\210\340\270\263\340\270\231\340\270\247\340\270\231\340\270
\253\340\270\245\340\270\261\340\270\201\340\271\200\340\270\227\340\271\210\340\270\262\340\270\201\340\270\261\340
\270\23213\340\270\253\340\270\245\340\270\261\340\270\201\340\270\253\340\270\243\340\270\267\340\270\255\340\270\243
\340\270\271\340\270\233\340\271\201\340\270\232\340\270\232 PIN\340\271\204\340\270\241\340\271\210\340\270\226\340
\270\271\340\270\201\340\270\225\340\271\211\340\270\255\340\270\207 <br> PIN incorrect <br> => \"3100905017911x\"">>>,
@schema=
#<SOAP::Mapping::Object:0x..fdbbd87f0 {http://www.w3.org/2001/XMLSchema}element=#<SOAP::Mapping::Object:0x..fdbbd8782
{http://www.w3.org/2001/XMLSchema}complexType=#<SOAP::Mapping::Object:0x..fdbbd870a {http://www.w3.org/2001/XMLSchema}
choice=#<SOAP::Mapping::Object:0x..fdbbd8692 {http://www.w3.org/2001/XMLSchema}element=#<SOAP::Mapping::Object:0x..fdbbd861a
{http://www.w3.org/2001/XMLSchema}complexType=#<SOAP::Mapping::Object:0x..fdbbd85a2 {http://www.w3.org/2001/XMLSchema}
sequence=#<SOAP::Mapping::Object:0x..fdbbd852a {http://www.w3.org/2001/XMLSchema}element=["", ""]>>>>>>>>>

xml หนอ xml
แกะจากตรงนี้ มันปวดตา ไปหน่อย มี ruby class ปนขึ้นมาด้วย
เปลี่ยนไป แกะจาก message จริงๆที่ส่งกลับมาดีกว่า

เพื่อจะดู message จริง ก็ให้ใส่ debug flag ตัวนี้ลงไป

service.wiredump_dev = STDERR if $DEBUG

จากนั้นเวลา run ก็ให้ใส่ flag -d ให้ ruby ด้วย (ruby -d)
ซึ่งมันจะแสดง message จริงออกมาดังนี้
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body><ServicePINResponse xmlns="https://rdws.rd.go.th/ServiceRD/CheckTINPINService">
<ServicePINResult><xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"><xs:element name="NewDataSet"
msdata:IsDataSet="true"><xs:complexType><xs:choice maxOccurs="unbounded">
<xs:element name="Message"><xs:complexType><xs:sequence><xs:element name="Code"
type="xs:string" minOccurs="0" /><xs:element name="Description" type="xs:string" minOccurs="0" />
</xs:sequence></xs:complexType></xs:element></xs:choice></xs:complexType></xs:element></xs:schema>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<NewDataSet xmlns=""><Message diffgr:id="Message1" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<Code>E00008</Code><Description>เลข PIN ไม่ถูกต้อง เนื่องจาก
หมายเลขPINเป็นตัวอักษรหรือเลขPINเป็นตัวอักษรหรือจำนวนหลักเท่ากับ13หลักหรือรูปแบบ PINไม่ถูกต้อง
&lt;br&gt; PIN incorrect &lt;br&gt; =&gt; "3100905017911x"</Description></Message></NewDataSet>
</diffgr:diffgram></ServicePINResult></ServicePINResponse></soap:Body></soap:Envelope>

ดูแล้วปวดตามาก แต่วิธีไล่ที่ง่ายที่สุด ก็คือ
ดูจากหลังไปหน้า เริ่มที่บรรทัดท้ายสุดเลย
จะเห็นว่า response มันมีโครงประมาณนี้

+ ServicePINResponse
+ ServicePINResult
+ diffgram
+ NewDataSet
+ Message
+ Code
+ Description

ลองสั่งพิมพ์ผลลัพท์ด้วยคำสั่งนี้

puts resp.servicePINResult.diffgram.newDataSet.Message.Description

ผลลัพท์คือ No Method Found.
หลังจากแงะดู response อยู่อีกพักใหญ่ (เข้าใจว่านานพอควร)
ก็ได้ข้อสรุปว่า
ต้องเรียกอย่างนี้ ถึงจะถูก

puts resp.servicePINResult.diffgram.newDataSet['Message']['Code']
puts resp.servicePINResult.diffgram.newDataSet['Message']['Description']

ทำไมข้อมูลใน newDataSet, soap4r มันแปลงเป็น hash ให้หว่า?

ข้อคิดที่ได้
+ ความตั้งใจดี แต่ design response คิดสั้นไปนิด

Related link from Roti

Thursday, October 18, 2007

ไป Codefest กันดีกว่า

หลังจากเข้าร่วมงาน Codefest ครั้งแรกเมื่อปีก่อนมาแล้ว
โดยปีก่อนนู้นทดลองเอา Erlang มา implement Vehicle tracking Server
ตอนนี้ก็ได้ฤกษ์หาโครงการเข้าร่วมอีกครั้ง
แต่ด้วยความโลภ ส่งโครงการเดียวไม่สนุก
ปีนี้เลยเอาเสียสองโครงการ

  • โครงการแรกไม่ได้ฉีกไปจากแนวเก่านัก
    โดยจะทำเป็น server ที่ aggregate ข้อมูลจาก Embedded Device ผ่านทาง GPRS
    แล้วก็ present ข้อมูลร่วมกับ MapServer
    แต่จะเน้นไปที่อุปกรณ์จริงมากขึ้น
    โดยปีนี้ได้ พี่ปู่ จาก Embeddedj มาช่วย Implement Embedded Device ให้ทดลองเล่นกัน

    ส่วนตัว server เนื่องจากไม่มีประเด็นเรื่องการรับ load แบบคราวก่อน
    ปีนี้จึงเปลี่ยนมาใช้ Rails แทน

    ส่วนการ Present ผ่าน Map
    เพื่อลดการพึ่งพา Google Map ก็เลยจะลองใช้
    MapServer แทน

  • โครงการที่สอง เป็นการเชื่อมระหว่าง Adobe Air กับ Mozart Oz
    ทำเป็นโปรแกรมจัดตารางห้องประชุม (ที่ใช้ constraint programming จากฝั่ง Oz ช่วย solve)


ใครว่างเชิญไปร่วมสนุกกันได้นะครับ Codefest 2007

Update: เพื่อความท้าทาย, โครงการที่สอง เปลี่ยนใจไปใช้ Air + Erlang ทำ โปรแกรมประมูล แทน

Related link from Roti

Monday, October 15, 2007

javascript interval

น้อง sand ถามปัญหามาเรื่อง จะใช้ dojo ดึงข้อมูลจาก server ทุกๆ 30 วินาที

ใน javascript มันมี function ที่ชื่อ interval ให้ใช้
window.onLoad=function() {
window.setInterval(myrefresh, 30000);
}

function myrefresh() {
... do something
}

ถ้าเราอยากดึงข้อมูลจาก server ก็แค่ใส่ logic ให้ไปใช้ function dojo.xhrGet
เพื่อดึงข้อมูลแบบ ajax มา update content บนหน้าจอ

ข้างบนเป็นแบบง่ายสุด
ถ้าต้องการ feature มากขึ้นเช่น
start, stop interval ได้
dojo ก็มี object ที่ชื่อ dojox.timing.Timer ให้ใช้
วิธีใช้ ก็ตามนี้
dojo.require("dojox.timing._base");

dojo.addOnLoad(function() {
var timer = new dojox.timing.Timer(30 * 1000);
timer.onTick=function() {
... do something
}
timer.start();
});


หรือถ้าเรามีเหตุการณ์ที่ต้องการให้เกิดตาม sequence ที่กำหนด
ก็ใช้ object dojox.timing.Sequence ก็ได้
ลองดูตัวอย่าง วิธีการกำหนด sequence
var seq = [
{func: [showMessage, window,
"i am first"], pauseAfter: 1000},
{func: [showMessage, window,
"after 1000ms pause this should be seen"], pauseAfter: 2000},
{func: [showMessage, window,
"another 2000ms pause and 1000ms pause before"], pauseAfter: 1000},
{func: [showMessage, window,
"repeat 10 times and pause 100ms after"], repeat: 10, pauseAfter: 100},
{func: returnWhenDone} // no array, just a function to call
];

Related link from Roti

Thursday, October 11, 2007

Up Function (revise)

จาก Post ก่อน Link
ด้วยความรีบร้อน มุ่งแต่ function เลยไม่ได้สนใจ form
ให้ดีแล้วแล้วควรจะเขียนแบบนี้
ดูเป็นกลุ่มก้อนกว่า
dojo.provide("orangegears.dom.Util");

orangegears.dom.Util = {

up: function(node, expression) {
var ancestors = this.ancestors(node);
for (var i = 0; i < ancestors.length; i++) {
var tmp = dojo.query(expression, ancestors[i]);
if (tmp.length > 0) {
return tmp[0];
}
}
return [];
},

ancestors: function(node) {
return this.recursivelyCollect(node, "parentNode");
},

recursivelyCollect: function(element, property) {
var elements = [];
while (element = element[property])
if (element.nodeType == 1)
elements.push(element);
return elements;
}

}

Related link from Roti

Up Function

ใน Prototype (javascript library) มันมี function อยู่อันหนึ่งที่ผมชอบ ก็คือ "up"
function นี้ช่วยให้เรา search หา parent element ได้ง่ายขึ้น
ช่วยให้ลดความซับซ้อนของบาง feature ลง

ลองดูตัวอย่างการใช้แบบง่ายๆ
  <ul>
<li>xxx</li>
<li>yyy</li>
<li class="item">
zzz
<!-- กดแล้วจะ remove item ที่เป็นพ่อของปุ่มนี้ออก -->
<input type='button' value="remove"
onclick='this.up(".item").remove()'/>
</li>
</ul>

ตัวอย่างนี้ simple มากจนไม่จำเป็นต้องใช้ function up ช่วยก็ได้
แต่ใน app ที่เป็นพวก Rich client ตัว DOM จะซ้อนกันสลับซับซ้อนกว่ากว่าตัวอย่างนี้มาก
มี up ก็ช่วยให้เราทำอะไรหลายอย่างได้ง่ายขึ้น

แต่ถึงจะชอบอย่างไร, project ปัจจุบันผม ก็ไม่ได้ใช้ Prototype แต่ใช้ Dojo,
ซึ่งลองความหาใน code แล้วยังหา function แบบนี้ไม่เจอ (code base มันใหญ่, อาจจะมีอยู่แล้วแต่หาไม่เจอ)
ดังนั้นปฏิบัติการลอกเลียนแบบ ก็เลยเกิดขึ้นดังนี้
dojo.provide("orangegears.dom.Util");

orangegears.dom.Util.up = function(node, expression) {
var ancestors = orangegears.dom.Util.ancestors(node);
for (var i = 0; i < ancestors.length; i++) {
var tmp = dojo.query(expression, ancestors[i]);
if (tmp.length > 0) {
return tmp[0];
}
}
return [];
}

orangegears.dom.Util.ancestors = function(node) {
return orangegears.dom.Util.recursivelyCollect(node, "parentNode");
}

orangegears.dom.Util.recursivelyCollect = function(element, property) {
var elements = [];
while (element = element[property])
if (element.nodeType == 1)
elements.push(element);
return elements;
}

namespace ยาวไปนิด แต่ไว้ไปสร้าง shortcut ทีหลังได้
ตัวอย่างเวลานำไปใช้จริง
dijit.byId(orangegears.dom.Util.up(this, '.dojoxGrid').id).setAll(this.checked);

Related link from Roti

Tuesday, October 09, 2007

Shadow




They see only their own shadows, or the shadows of one another, which the fire throws on the opposite wall of the cave?
Socrates
The Allegory of the Cave (The Republic , Book VII)


งานของ Tim Noble and Sue Webster, Dirty White Trash [With Gulls] 1998

Note: รูปจาก Freeze Frame Screen, the Shadow, Hot Heads Under Silent Wigs

Related link from Roti

Friday, October 05, 2007

java กับ erlang

วันก่อนเห็น java guy บ่นเรื่อง Erlang
แต่ก็ไม่ได้สนใจอ่าน
(คนอะไร ยังไม่ทันมีประสบการณ์เลย บ่นถึงข้อเสียได้เป็นฉากๆ)

วันนี้เห็น Aristotle Pagaltzis เขาเขียนล้อเลียนสั้นๆ
Plus ça change
มีอารมณ์ชัน และเสียดสีดี

Related link from Roti

Thursday, October 04, 2007

ประกันสังคม

อาจารย์ผมเขียนเกี่ยวกับประกันสังคมไว้ใน mail ของบริษัทฯ
เนื้อหามันไม่ได้จำกัดอยู่กับคนในบริษัทฯ ก็เลยคัดลอกมาให้อ่านกัน

ผู้ใช้แรงงาน(รวมมนุษย์เงินเดือนบางพวก) เป็นผู้มีฐานะทางสังคมระดับล่าง มีจำนวนมาก รวมกันไม่ติด อ่อนแอ ไม่มีอำนาจต่อรอง

ระบบประกันสังคม เป็นระบบลงขันตามสัดส่วนรายได้ แล้วจ่ายคืนในรูปสวัสติการพื้นฐาน เช่น รักษาพยาบาล

หลอกให้เชื่อว่า ให้หลักประกันชราภาพ (แต่สะสมเงินสมทบแบบจำกัด จึงจ่ายคืนแบบจำกัด) ทั้ง ๆ ที่ไม่สามารถดำรงชีวิตอยู่ได้จากเงินรับกรณีชราภาพ แล้วถ้าคำนวณเป็น จะรู้ว่า เงินกองทุนชราภาพจะไม่พอจ่าย เพราะ (สมทบเดือนละ 1.5% + อีกเท่าตัวจากนายจ้าง แล้วได้รับบำนาญต่อเดือน 15% ของเฉลี่ย 60 เดือนสุดท้าย สูงสุด 2,250 บาท) ส่งสมทบ 1 ปี จ่ายคืนได้แค่ 2 เดือนกว่าก็หมด ทำงานมา 30 ปี จ่ายคืนได้ 72 เดือน(6 ปี) ส่วนที่สมทบไว้หมดแล้ว ดอกผลไม่มากพอ แต่คนอายุยืนกว่า 61 ปี (สิทธิชราภาพ เริ่มได้เมื่อไม่ทำงานหลังอายุครบ 55 ปี)

เรื่องประกันสังคม ต่อไปจะเป็นปัญหาสังคม เมื่อกองทุนไม่มั่นคง แต่ตอนนี้ปัญหายังซุกตัวอยู่ใน ภูเขาน้ำแข็ง คนส่วนใหญ่เห็นเม็ดเงินสะสมมูลค่ามาก เพราะ สิทธิประโยชน์ชราภาพยังไม่เกิดเต็มที่ คนที่เกษียณตอนนี้ กรณีสมทบไม่เกิน 12 เดือน จะได้รับบำเน็จเอาเงินก้อนที่สมทบไว้ คืนกลับไปบวกดอกเบี้ยนิดหน่อย (ถ้าสมทบเกิน 12 เดือน แต่ไม่ถึง 180 เดือน ได้คืนส่วนของนายจ้างด้วย) ปลายปี 2556 จึงจะเริ่มได้สิทธิบำนาญ เมื่อถึงเวลานั้น เงินกองทุนชราภาพ จะเริ่มหดหาย ซึ่งอาจจะยังไม่รู้สึก เพราะ เงินเข้ายังมากกว่าเงินออก ต้องผ่านไป 10 ปี 20 ปี เมื่อคนเกษียณมากขึ้น แล้วไม่ตายง่าย จนหมุนเงินเข้าไม่ทันเงินออก จึงจะเริ่มเห็นอาการเงินกองทุนหด แต่เพราะเวลายังอีกนาน จะยังไม่มีใครร้อนตัวตอนนี้ พวกลูกจ้างจะเรียกร้องสิทธิประโยชน์เพิ่ม โรงพยาบาลจะเก็บเงินเพิ่ม พวกเหลือบจะมาดูดเงินจากกองทุน

ผมอายุยืนไม่ถึง ที่จะเห็นปัญหาปูดขึ้นมา

การประกันสังคม เป็นลักษณะการออมแบบหนึ่ง (กองทุนสำรองเลี้ยงชีพ ก็เช่นเดียวกัน) แต่การออมแบบนี้ บรรเทาความวิกฤตทางสังคมได้หน่อยหนึ่ง ไม่พอเลี้ยงชีพเมื่อยามแก่เฒ่า

ถ้าผู้ใช้แรงงานเป็นคนแก่ ไม่มีลูกหลานเลี้ยงดู ก็อยู่ไม่ได้ ถ้าลูกหลาน ยังไม่เปลี่ยนฐานะจากผู้ใช้แรงงาน เป็นฐานะอื่นที่ดีกว่า ก็อาจดูแลได้จำกัด เพราะ ลำพังตัวผู้ใช้แรงงาน ดูแลตัวเอง ก็ลำบาก

ยุคอุตสาหกรรม ผู้ใช้แรงงานในโรงงาน เป็นผู้หนุนความมั่งคั่งให้นายทุน

Knowledge worker คือ ผู้มีการศึกษา ต่างจากผู้ใช้แรงงานในโรงงาน พวกนี้มีพัฒนาการสูงกว่า (แต่สำหรับประเทศไทย เราขยับตัวด้านการศึกษาช้าเกินไป เหมือนกับเป็นคนหลับ ถึงตอนนี้ ก็ยังไม่ตื่น สะลึมสะลือ งัวเงีย เราหลงผิดว่าเราทำดีที่สุดแล้ว เพราะ ประเทศเราอุดมสมบูรณ์ เราจึงไม่ค่อยกระตือรือร้น )

ในส่วนที่เกี่ยวกับประกันสังคม บริษัทของเราจ่ายเงินสมทบ มากกว่ารับประโยชน์คืน พนักงานส่วนใหญ่เป็น Knowledge worker แต่พวกเราบางคนอาจจะยังไม่ตระหนักว่า เราจะต้องรู้จักออมมากกว่าที่เป็นอยู่ สังคมรอบข้าง มีวัตถุหลอกล่อให้เราหลงไหลง่าย เราต้องรู้จักอดกลั้นต่อสิ่งยั่วกิเลส ยั่วความฟุ้งเฟ้อ เราต้องนึกถึงวันข้างหน้าที่อาจไม่แน่นอน ต้องเผื่อเหนียว กลัวอด เราจะไปหวังพึ่งเงินบำนาญชราภาพจากประกันสังคมไม่ได้ อนาคตอยู่ในมือเราเอง แต่เราร่วมมือกันหนักแน่น จะแข็งแรงกว่าคนเดียวหรือจับมือแบบหลวม ๆ

ดร. สุริยัน ติษยาฐิคม

Related link from Roti

Wednesday, October 03, 2007

Dojo Tree

การใช้ dijit.Tree
ใน dojo0.9, มีการ re-design Tree Widget ใหม่
โดย copy code เดิมมาจาก TreeV3 ใน dojo 0.4
แล้วปรับเปลี่ยนวิธี define item ใน tree เสียใหม่
โดยเปลี่ยนไปใช้ abstraction ที่ชื่อ Store
เป็นผู้ทำหน้าที่ provide data ให้กับ Tree

ลองดู use case แบบง่ายสุดก่อน
เริ่มด้วยการ define Data ที่จะใช้เป็น model ในการสร้าง Tree
      var treeData = {
label: "name",
identifier: "id",
items: [
{ id: 1, name: "Micro-X", children: [
{ id: 3, name: "Developement" },
{ id: 4, name: "R&D" }
] },
{ id: 2, name: "Ingres", children: [
{ id: 5, name: "Marketing" }
] }
]
}

var store = new dojo.data.ItemFileReadStore({ data: treeData });

Attributes หลักๆที่ Store ต้องการก็คือ
  • label จะใช้กำหนดว่า เวลา render Tree Node แล้ว
    ค่าที่แสดงเป็น label จะนำมาจาก attribute ที่ชื่ออะไร
  • identifier ใช้กำหนดว่า attribute name ไหน represent unique key ใน dataset
  • items ตัวเนื้อ data ที่ represent tree node

เมื่อ define Store แล้ว ก็สามารถนำไป feed ให้ Tree Widget ของเราได้
    <div dojoType="dijit.Tree" store="store" id="tree" label="Company">
</div>


แค่นี้ก็ได้ Tree หน้าตาแบบนี้ออกมา



ตัว Tree Widget นี้ มันมี extension point ให้เรา extend functional หลายตัว เช่น
ถ้าเราต้องการ ดัก click event
เราสามารถ override หรือ connect onClick ได้ดังนี้
    <div dojoType="dijit.Tree" store="store" id="tree" label="Company">
<script type="dojo/connect" event="onClick" args="item">
console.debug(item.name);
var name = store.getValue(item, "name");
console.debug(name);
</script>
</div>

ปัญหาก็คือ ตัว Store มันเปลี่ยนรูป Data ที่เรา feed เข้าไป (นัยว่าเพื่อ optimize อะไรบางอย่าง)
ถ้าเราใช้ item.name เพื่อดึงค่าขึ้นมา, ค่าที่ได้จะได้ datatype เป็น array แทนที่จะเป็น string ธรรมดา (ตามที่เรา declare ไว้)
ดังนั้นก่อนที่จะนำค่าจาก item ไปใช้ ก็เลยต้องมี wrapper code นี้พอกลงแบบนี้
store.getValue(item, "name")
Note: อันนี้นับว่าเป็นข้อเสียของ Tree api นี้อย่างมาก, implementation ควรจะซ่อนให้พ้นสายตาจากคนใช้

จุด extension อื่นๆ ก็มีอย่างเช่น
ถ้าต้องการ customize Label
    <div dojoType="dijit.Tree" store="store2" id="tree2" label="Company">
<script type="dojo/method" event="getLabel" args="item">
return store2.getValue(item, "name") + "_hi";
</script>
</div>

ข้อจำกัดอีกอย่างของ Tree Label ก็คือ มันแสดงได้เฉพาะ Plain Text เท่านั้น
ถ้าต้องการอะไรที่ดูดีขึ้น เช่นเปลี่ยนสี
ก็ให้ไปใช้ css class ช่วยแทน โดยทำการ override method getLabelClass
    <div dojoType="dijit.Tree" store="store4" id="tree4" label="Company" childrenAttr="children,staffs">
<script type="dojo/method" event="getLabelClass" args="item">
if (item) {
if (this.store.getValue(item, "type") == 'employee') {
return "employee";
}
}
return "";
</script>
</div>

Note: ใครที่จะ test ตรงนี้ ให้ระวังว่า ช่วงนี้มีการ refactor อยู่
ทำให้ code ในส่วนนี้หายไปจาก trunk, ผมกำลังรอ submit patch อยู่

ประเด็นที่น่าสนใจของ tree model ก็คือ เราสามารถ share node ได้
โดยการใช้ _reference attribute
      var treeData2 = {
label: "name",
identifier: "id",
items: [
{ id: 1, name: "Micro-X", children: [
{ id: 3, name: "Developement" },
{ id: 4, name: "R&D" }
] },
{ id: 2, name: "Ingres", children: [
{ _reference: { id: 3 }},
{ id: 5, name: "Marketing" }
] }
]
}

node "Development" ก็จะถูก share ใช้ร่วมกัน
แต่ข้อสงสัยที่ตามมาก็คือ use case ไหน? ที่มันจำเป็นต้องทำแบบนี้

นอกจากนี้ยังมี feature แปลกๆอีกเช่น
เราสามารถกำหนด attribute name ของ child element ได้มากกว่า 1 (default คือ "children")
เช่น data ชุดนี้ มี child element อยู่ 2 แบบ (children และ staffs)
เข้าใจว่า feature นี้มีไว้ สำหรับกรณี Tree ที่ mix content จากหลายๆ class
      var treeData4 = {
label: "name",
identifier: "id",
items: [
{ type: "company", id: 1, name: "Micro-X", children: [
{ type: "department",
id: 3, name: "Developement",
staffs: [
{type: "employee", id: 100, name: "pok"}
]
},
{ type: "department",
id: 4, name: "R&D",
staffs: [
{type: "employee", id: 101, name: "kob"}
]
}
] },
{ type: "company", id: 2, name: "Ingres", children: [
{ _reference: { id: 3 }},
{ type: "department", id: 5, name: "Marketing" }
] }
]
}

เวลาใช้ เราก็ต้องกำหนด childrenAttr ให้ Tree ด้วย
    <div dojoType="dijit.Tree" store="store3" id="tree3" label="Company" childrenAttr="children,staffs">
</div>

Related link from Roti

Monday, October 01, 2007

Dojo ???

ท่ามกลางทะเล framework และ library ที่มากมาย
ในยุค information overload นี้
สิ่งที่มักจะเกิดในใจของผมก็คือ
"เราเลือกใช้ถูกตัวหรือเปล่า"
"มีตัวอื่นที่ดีกว่านี้หรือเปล่า"

ตอนที่ใช้ Dojo ก็เช่นกัน
เลือกเพราะอ่าน code แล้วมันติดใจ, เขียน code แล้วสนุก
ใช้ไปนานๆเข้า
framework ตัวอื่นๆก็เริ่ม เตะตา (เตะบ่อยด้วย)
JQuery ก็มีคนชมเยอะ
Ext ก็สวยเสียเหลือเกิน
...

แต่ความรู้สึกพวกนี้ ก็ลดลงได้
เวลาเจอคนอื่นที่ใช้เหมือนเรา
อย่างเดือนที่ผ่านมา
อย่างน้อยก็เจอ site ใหญ่ที่ใช้ dojo เพิ่มขึ้น
  • beta.bloglines.com -> ใช้ dojo0.9 แต่โทษที UI ไม่สวยเลย
  • store.apple.com -> ใช้ dojo รุ่นโบราณมาก 0.4.0
    แถมยัง load library อะไรมาเยอะแยะไปหมดเลย ซึ่งแต่ละหน้าใช้จริงๆไม่เท่าไร
    คนเขียนเขาไม่ได้ใช้ custom build ซึ่งจะช่วยลดจำนวน file ที่ต้อง load ลง

Related link from Roti

Thursday, September 27, 2007

Tapestry กับ Wicket

หลายคนคงสงสัยว่า เจ้าสองตัวนี้มันมีอะไรที่ต่างกัน
Keng Tong (ซึ่งเป็น committer ของ Tapestry, และเป็นคนเขียนหนังสือ Enjoy Tapestry)
เขาเฉลยให้ฟังแล้ว

My thoughts on the differences between Tapestry and Wicket

I've never seen any code that is as good quality as the Tapestry code

เห็นด้วยว่า code ของ Tapestry นี่มันดีจริงๆ

Wicket tend to have more working functions than Tapestry (tree component, more ajax functions, modal dialog, dynamic images, wizard. role based authorization, captcha, breadcrumb, ...).

ใช่เลย อย่าลืมตามไปอ่านเหตุผลด้วยว่า ทำไม...

Related link from Roti

Wednesday, September 26, 2007

ประสบการณ์ Dojo 0.9

ช่วงนี้กำลังนั่งทำ Orangegears กับน้อง sand เสียเป็นส่วนใหญ่
สิ่งที่ผมต้องการสร้างความแตกต่างระหว่าง Ofbiz กับ Orangegears ก็คือ UI
ช่วงนี้ก็เลยนั่งลอง prototype ความเป็นไปได้ในการใช้ Dojo มาเป็น layer ในส่วน UI
ซึ่งก็พบความแตกต่างระหว่าง 0.9 และ 0.4 จำนวนหนึ่ง
ก็เลยมา blog ไว้

เริ่มด้วยความแตกต่างระหว่าง 0.4 กับ 0.9 ที่สัมผัสได้
  1. ความเร็วในการ parse
    เดิมใน Dojo 0.4 ปัญหาที่เราพบก็คือ dojo.parser ทำงานช้า
    ในกรณีที่เรามี DOM tree ที่ใหญ่มากๆ, dojo.parser จะทำงานช้าในแบบที่รับไม่ได้
    (ซึ่งมันมีวิธีการแก้ไข โดยการ scope ส่วนที่ dojo.parser ทำงานให้มีขนาดเล็กลง)
    ใน Dojo 0.9 ความเร็วในการ parse เพิ่มขึ้นอย่างมาก
    เข้าใจว่าได้แรงบันดาลใจมาจาก JQuery ซึ่งสามารถ scan DOM ได้เร็วมากๆ

    ผลพลอยได้จากการที่ parser มันเร็ว ก็คือ style การเขียนที่เปลี่ยนไป
    แนวทางการเขียนแบบ declarative ถูก encourage มากขึ้น
    ส่งผลให้ code ดูสวยและอ่านง่ายขึ้น (สำหรับ developer ที่สายตาคุ้นกับ xml สไตล์ flex, xul แล้ว)

    ข้างล่างคือตัวอย่าง code ที่ผมลองเขียนใน Orangegears ดู
    เป็นหน้าจอที่แสดง Tree ของผังบัญชี
    style การเขียนเป็นแบบ declarative
    <!-- Store น่าจะเปรียบได้กับพวก DataProvider ใน flex-->
    <div dojoType="dojo.data.ItemFileReadStore"
    jsId="financial.accountCharts"
    url = "getAccountTreeJson">
    </div>

    <!-- Tree Widget -->
    <div id="accountCharts"
    dojoType="dijit.Tree"
    store="financial.accountCharts" <!-- link เข้าหา DataProvider ข้างบน -->
    label="Accounts">

    <!-- เชื่อม script นี้เข้ากับ event onClick เข้า Tree widget นี้
    สังเกตว่า type มีค่าเป็น dojo/connect
    -->
    <script type="dojo/connect" event="onClick" args="item">
    var glAccountId = financial.accountCharts.getValue(item, "glAccountId");
    dojo.xhrGet({
    url: "getGlAccountJson",
    handleAs: "json",
    content: {glAccountId: glAccountId},
    load: function(obj, ioArgs) {
    dijit.byId("glAccountTypeId").setValue("");
    dijit.byId("glAccountClassId").setValue("");
    dijit.byId("glAccountForm").setValues(obj);
    }
    });
    </script>

    </div>

  2. Layout Widget
    ใน Dojo 0.9 มันมี Layout Widget เพิ่มขึ้นมา
    ทำให้การสร้าง App สะดวกขึ้นอย่างเห็นได้ชัด
    ทำให้เราเป็นอิสระจาก CSS มากขึ้น (ไม่งั้นก็มัวไปนั่ง control CSS ให้ได้รูปแบบตามที่เราต้องการ)


ในเรื่องของ source code ของ Dojo
style การเขียนส่วนใหญ่ยังคล้ายคลึงกับ 0.4 อยู่
ถ้าไล่ code 0.4 เป็น ก็สามารถใช้ 0.9 ได้เลย
โดยใน 0.9 นี้, dojo rewrite widget system ใหม่ในชื่อ package ว่า dijit
ผลจากการ rewrite ก็ทำให้จำนวน object, function ในส่วน Widget มันลดลง เป็นระบบระเบียบมากขึ้น
การไล่ code ที่เกี่ยวกับ UI ก็เลยทำได้ง่ายขึ้น
(บ้างอย่างก็ง่ายขึ้น, บางอย่างก็งงๆ เพราะมีความเป็น generic มากไปหน่อย)

Bug ใน Dojo 0.9
เท่าที่ใช้มาพึ่งเจอแค่ bug ตัวเดียวซึ่งก็ร้ายแรงอยู่
เป็นเรื่องเกี่ยวกับ การ reload content ใน ContentPane
ซึ่งแก้ไขโดยเปลี่ยนไปใช้ code จาก SVN โดยตรง
(หนีเสือ แต่อาจจะไปเจอจระเข้ เพราะมีความเสี่ยงมากขึ้นกับ bug ตัวใหม่ๆแทน)

Related link from Roti

Thursday, September 20, 2007

Eclipse oAW

กลับจากงาน NJUG4 ด้วยความค้างคาใจในเรื่อง MDA
ด้วยความที่ Robert ไม่ยอมแสดงตัวอย่างเจ๋งๆใน MagicDraw ให้ดู

เมื่อวันก่อนอ่านพบใน Eclipse Planet ว่ามี project 's annoucement ที่ชื่อ OpenArchitecture Ware (oAW)
ซึ่งเป็น Framework สำหรับ MDA ที่ integrate เข้ากับพวก MDD projects ของ Eclipse (เช่น EMF)
ก็เลยได้ฤกษ์ทดลองเสียเลย

มาลองดู step ของการใช้ oAW
ตั้งแต่ define MetaModel ไปจนถึง การ generate Source code

  • step แรกเริ่มต้นกันที่ การสร้าง MetaModel ก่อน (M2)
    ที่มาของ MetaModel ใน oAW มีได้ 2 แบบหลักๆคือ
    1. จาก EMF
    เมื่อก่อนเคยสงสัยมานานแล้วว่า EMF ไว้ทำอะไร
    หลังจากไปฟัง Robert พูดมา และกลับมาอ่านเอกสารของ EMF
    ทำให้เริ่มมองภาพ EMF ออกมากขึ้น
    ตัว EMF จริงๆแล้วก็คือ Implementation ตัวหนึ่งของ MOF (Meta Object Facility)
    แต่ implement แค่ subset บางส่วน, และเพื่อไม่ให้สับสนกับ MOF
    ส่วน subset model ที่ EMF implement เขาก็เลยตั้งชื่อว่า Ecore
    (ปัจจุบัน MOF 2.0, subset ที่ EMF support เขาตั้งชื่อว่า EMOF)

    วิธีสร้าง MetaModel ก็มีหลายแบบ เช่น
    • define มันตรงๆเลย ด้วย Editor ของ EMF
    • export มาจากพวก UML Tool ที่ support XMI format
    • model ด้วย Java Code + Annotation
    • ใช้ XSD ในการ define


    2. สร้างจาก xText
    อันนี้น่าสนกว่าวิธีที่ 1 เยอะ
    ถ้าอ่าน overview ของ xText จะเห็นว่า
    เขาประกาศตัวเป็น Textual DSL development framework
    การสร้าง MetaModel ใน xText ก็จะใช้การเขียน Grammar rules แทน

    ตัวอย่างของ MetaModel ที่ oAW ใช้ใน tutorial ก็มีหน้าตาแบบนี้
    เป็นตัวอย่างของการ define MetaModel ของ Simple Class Diagram



  • ขั้นตอนถัดไป ก็คือการสร้าง Model (ที่เป็น instance ของ MetaModel ที่ได้จากขั้นตอนแรกของเรา)
    ถ้าเราใช้ MetaModel จาก EMF ในขั้นนี้ เราเลือกทางเลือกได้หลายทาง
    เช่น ให้ EMF generate Editor ให้เรา เพื่อที่เราจะได้ใช้ Editor ตัวนั้นสร้าง Model
    หรือจะไปพวกตระกูล GMF สร้าง Graphic Editor มาเพื่อที่จะมาสร้าง Model ก็ได้

    แต่ถ้าเลือก choice ของ xText
    ในขั้นนี้ เราก็จะให้ xText generate Text Editor ให้เรา
    แล้วเราก็ใช้ Text Editor นั้น เขียน DSL ของเราได้เลย

  • ขั้นถัดไป ก็คือการ กำหนด build script ที่ใช้ในการ generate
    oAW เรียก script พวกนี้ว่า Workflow
    ตัวนี้ดูแล้ว ได้อารมณ์เหมือนเขียน Ant เลย
    <workflow>
    <property file="workflow.properties"/>

    <!-- ให้ parser อ่าน Model ของเรา โดยอ้างอิง MetaModel ด้วย -->
    <component id="xmiParser"
    class="org.openarchitectureware.emf.XmiReader">
    <modelFile value="${modelFile}"/>
    <metaModelPackage value="data.DataPackage"/>
    <outputSlot value="model"/>
    <firstElementOnly value="true"/>
    </component>

    <!-- validate model โดยใช้พวก OCL script -->
    <component
    class="org.openarchitectureware.check.CheckComponent">
    <metaModel id="mm" class="org.openarchitectureware.type.emf.EmfMetaModel">
    <metaModelPackage value="data.DataPackage"/>
    </metaModel>
    <checkFile value="checks"/>
    <emfAllChildrenSlot value="model"/>
    </component>

    <!-- ลบ source file ที่เคยถูก generate ไว้ -->
    <component id="dirCleaner"
    class="org.openarchitectureware.workflow.common.DirectoryCleaner" >
    <directories value="${srcGenPath}"/>
    </component>

    <!-- generate file โดยใช้ template ที่ระบุ -->
    <component id="generator"
    class="org.openarchitectureware.xpand2.Generator">

    <metaModel id="mm"
    class="org.openarchitectureware.type.emf.EmfMetaModel">
    <metaModelPackage value="data.DataPackage"/>
    </metaModel>

    <expand value="templates::Root::Root FOR model"/>

    <outlet path="${srcGenPath}/">
    <postprocessor
    class="org.openarchitectureware.xpand2.output.JavaBeautifier"/>
    </outlet>

    </component>
    </workflow>


  • ในการ generate source code สิ่งที่ต้องมีก็คือ Template Engine
    หน้าตาของ oAW template มีหน้าตาแบบนี้
    ให้ความรู้สึกเหมือน XSL
    «DEFINE Root FOR data::DataModel»
    «EXPAND Entity FOREACH entity»
    «ENDDEFINE»

    «DEFINE Entity FOR data::Entity»
    «FILE name + ".java"»
    public class «name» {
    «EXPAND Attribute FOREACH attribute»
    }
    «ENDFILE»
    «ENDDEFINE»

    «DEFINE Attribute FOR data::Attribute»
    private «type» «name»;
    «ENDDEFINE»


แค่นี้ก็ได้แนวทางการ Generate code แบบง่ายแล้ว
(รายละเอียดมันเยอะกว่านี้
มีทั้งเรื่อง การ validate model โดยใช้ OCL,
การสร้าง helper code ที่ template สามารถใช้ช่วยในการ generate source code)

อืมม์ นี่แค่เริ่มต้น
เนื่องจากเห็นมองเห็นภาพรวมแล้ว
ทำให้รู้สึกสนุก และอยากหัดใช้ project ในตระกูล Eclipse MDD มากขึ้น
ตัว xText ที่เป็น Textual DSL ก็น่าใช้
พวกรุ่นหลังของ GMF ก็ออกมาง่ายมากขึ้นทุกที
ทำให้เราสร้าง Graphice Editor ได้ง่ายขึ้นเรื่อยๆ

์์Note: ต้องขอบคุณงาน NJUG มาก
เพราะถ้าไม่ได้ไปฟัง Robert พูด
ตอนที่เห็น oAW ก็คงไม่ได้ปิ๊งขนาดนี้หรอก

Related link from Roti