Thursday, August 24, 2006

Functional test with Selenium RC

ลองใช้ Selenium มาทั้ง 3 แบบแล้ว
core, firefox extension, remote-control
สุดท้ายก็ชอบแบบ remote-control มากที่สุด
โดยเฉพาะการใช้ร่วมกับ ruby

ภาพรวมการทำงานของ Selenium remote-control ก็คือ
เราจะ start selenium server ขึ้นมาตัวหนึ่ง
เวลาจะ test เราก็จะเขียน program เพื่อส่ง command ที่ต้องการ test
ไปยัง server, โดย server จะสื่อสารและสั่งงานให้ browser ทำตาม command ที่เราสั่ง



ที่นี้ลองมาดูการทำงานภายในของ Selenium ดูบ้าง
วิธีการที่ selenium ใช้ควบคุม browser ก็คือการใช้ javascript
เช่นสมมติว่าเราต้องการให้เกิดการ click ที่ปุ่มๆหนึ่ง

<a href="xxxx" id="myButton">click me</a>

ในแง่ของ test script เราก็จะส่งคำสั่งต่อไปนี้

cmd = "click"
param1 = "id=myButton"

ทีนี้ selenium จะทำอย่างไร ปุ่มนี้จึงจะ click ได้
ขั้นที่ 1 ก็คือหา element ที่ต้องการ click ให้เจอก่อน
โดยวิธีการหา จะขึ้นอยู่กับ prefix ที่เรากำหนดไปใน parameter
เช่นถ้ากำหนด id= นำหน้า, selenium ก็จะใช้วิธี getElementById
แต่ถ้าเป็นพวก xpath=//img[@alt='The image alt text'] ก็จะใช้ xpath
หรือถ้านำหน้าด้วย css= ก็จะใช้ css selector ในการหา

ขั้นที่ 2 ก็คือเมื่อเจอ element ที่ต้องการแล้ว ก็ต้องทำให้เกิดการ click ขึ้นมา
ลองดูตัวอย่างของการ simulate mouse click ใน Firefox ดู
MozillaPageBot.prototype.clickElement = function(element, clientX, clientY) {

triggerEvent(element, 'focus', false);

// Add an event listener that detects if the default action has been prevented.
// (This is caused by a javascript onclick handler returning false)
var preventDefault = false;

...

// Trigger the click event.
triggerMouseEvent(element, 'click', true, clientX, clientY);

...

if (this.windowClosed()) {
return;
}

triggerEvent(element, 'blur', false);
};

จะเห็นว่า มันทำการ simulate event focus จากนั้นก็ทำการสร้าง event mouse click
สุดท้ายก็ส่ง event blur
ส่วนการทำงานภายในของ function triggerMouseEvent
ถ้าเป็น mozilla ดูเหมือนจะใช้วิธีนี้
var evt = document.createEvent('MouseEvents');
.. // init event
element.dispatchEvent(evt);

จะเห็นได้ว่า วิธีการทำงานนั้นน่าสนใจทีเดียว

ประเด็นที่ตามมาของการใช้ javascript control ก็คือ
javascript นั้นจะ run ใน domain name ที่เราต้องการ test อย่างไร
เนื่องจาก javascript จะทำงานได้กับ domain name ที่เรา load javascript นั้นมาเท่ากัน
Selenium เลยใช้วิธีการ start proxy server ขึ้นมา
เพื่อบังคับให้ request ของ browser วิ่งผ่านมันก่อน
เพื่อที่มันจะได้ inject javascript ที่ต้องการลงไปได้



หันไปดูด้านผู้ใช้ดูบ้าง
ลองดูตัวอย่าง ruby code ที่ผมเขียน test

def setup
@domain = 'http://wcfweb.test:8080'
@sel = Selenium::SeleneseInterpreter.new("selenium.server.test", 4444,
"*firefox", @domain, 15000)
@sel.start
@home = "#{@domain}/fin/app?page=wcf2000/Wcf2110_1&service=page"
end

def submit_cheque(no, date, bank, amount)
to_page2
@sel.click("link=0.00")
type_cheque(no, date, bank, amount)
calc_charge
@sel.click("save_btn")
@sel.wait_for_page_to_load(15000)
end

def t_cheque_amount_entry_but_no_other_info
submit_cheque("", "", "", 2000)
assert(@sel.is_text_present("กรุณาระบุเลขที่เช็ค"))
assert(@sel.is_text_present("กรุณาระบุวันที่เช็ค"))
assert(@sel.is_text_present("กรุณาระบุรหัสธนาคาร"))
end

def t_cheque_entry_wrong_date
submit_cheque("1234567", nextDay(2), "", 2000)
assert(@sel.is_text_present("วันที่เช็ครับลงล่วงหน้ามากกว่า 1 วัน"))
end

def t_cheque_entry_wrong_bank_code
submit_cheque("1234567", nextDay(1), 1234, 2000)
assert(! @sel.is_text_present("วันที่เช็ครับลงล่วงหน้ามากกว่า 1 วัน"))
assert(@sel.is_text_present("รหัสธนาคารไม่ถูกต้อง"))
end


เวลา run, Selenium ก็จะเปิด browser ทำงาน
โดย browser มีหน้าตาแบบนี้

Related link from Roti

Wednesday, August 23, 2006

Dojo Render Time

ช่วงแรกๆที่ใช้ Dojo จะยังไม่เจอปัญหาเรื่อง Render Time.
แต่พอในหน้าจอเริ่มมี element มากขึ้น
เวลาที่ใช้ในการ render หน้าจอ จะเริ่มช้าลงอย่างเห็นได้ชัด

ปัญหาก็คือ
เวลาเราใช้ dojo เรามักจะใช้ในรูปแบบนี้
<span dojoType="tooltip" 
connectId="entryField"
caption="xxxxxx"/>

ประเด็นก็คือ แล้ว dojo จะรู้ได้ไงว่าต้องเปลี่ยน dom element นี้ให้เป็น dojo component
Dojo ก็เลยต้อง scan document ทั้งอัน เพื่อที่จะหาว่ามี attribute dojotype อยู่ที่ไหนบ้าง
ยิ่งหน้าจอเรามี dom element เยอะขึ้นเท่าไร ก็ยิ่งเสียเวลามากขึ้นเท่านั้น

การแก้ไขก็คือ จำกัดวงไม่ให้ dojo ต้อง scan เอกสารทั้งหมด
เริ่มด้วยการ set djconfig
<script> 
djConfig = {
..
parseWidgets: false,
searchIds: [],
..
};
</script>

จากนั้นในแต่ละ element ที่ต้องการให้เป็น dojo component
ก็ให้ใส่ script นี้ลงไป
<span dojoType="tooltip" 
connectId="entryField"
caption="xxxxxx"
id="this_is_my_dojo"/>
<script>djConfig.searchIds.push("this_is_my_dojo");</script>

Related link from Roti

Label กับ Form

Label Placement in Forms
บทความนี้เขา track ลูกตา กับ form ที่ align ในแบบต่างๆ
แล้วสรุปให้เราดูว่า ข้อดีข้อเสียเป็นอย่างไร

Related link from Roti

Monday, August 21, 2006

Closures for Java

มี proposol แบบนี้ออกมา ค่อยรู้สึกมีความหวังกับภาษานี้หน่อย
(หลังจากปล่อยให้ C# นำหน้าไปนาน)

ดูตัวอย่างแรกสุดก่อน
public static void main(String[] args) { 
int plus2(int x) { return x+2; }
int(int) plus2b = plus2;
System.out.println(plus2b(2));
}

ในที่สุด Kingdom of Nouns อย่าง java ก็มี verb citizen โผล่เข้ามาแล้ว
ในบรรทัดแรก plus2 ก็คือ Type ชนิด function
ส่วนบรรทัดที่ 2 ก็คือการกำหนดให้ plus2b เป็นตัวแปรที่ reference ไปยัง function plus2

บรรทัดที่ 1 และ 2 จากตัวอย่างข้างบน สามารถยุบเขียนในรูป anonymous function ได้แบบนี้
int(int) plus2b = (int x) {return x+2; };


แถมด้วย syntactic sugar
แทนที่จะต้องเขียนแบบนี้
void sayHello(java.util.concurrent.Executor ex) { 
ex.execute((){ System.out.println("hello"); });
}

ก็สามารถเขียนแบบนี้ได้
void sayHello(java.util.concurrent.Executor ex) { 
ex.execute { System.out.println("hello"); }
}

Related link from Roti

Sunday, August 20, 2006

Emacs & xft

ตั้งแต่ใช้ Emacs มา มีเรื่องขัดใจอยู่อย่างเดียว
ก็คือ text ของ emacs ใน linux มันไม่เป็น anti-aliased
วันนี้ก็ลอง build emacs จาก branch ที่เขา implement feature xft ดู

ขั้นตอนก็คือ
checkout จาก trunk ก่อน

cvs -z3 -d:pserver:anonymous@cvs.savannah.gnu.org:/cvsroot/emacs co emacs

จากนั้นก็สั่ง update จาก branch

cd emacs
cvs up -Pd -r XFT_JHD_BRANCH

สั่ง configure
(ตรงนี้ distro แต่ละ distro คงไม่เหมือนกันนะ
ถ้าดูเอกสารที่คุณ poonlap แนะนำ จะไม่ต้องมี --x-includes กับ --x-libraries
กรณีของผมเป็น fedora core 5, ถ้าไม่มีจะ compile ไม่ผ่าน)

./configure --with-xft --with-gtk --x-includes=/usr/include/X11 --x-libraries=/usr/lib/X11

จากนั้นก็สั่ง

make bootstrap
make install


เท่านี้ก็ได้ emacs ฉบับสวยงาม
แต่ข้อเสียก็คือ มันแสดงภาษาไทยไม่ได้
เศร้า
(poonlap, ถ้าอ่านเจอ ก็ช่วยหาทางแก้ให้หน่อยนะ)

เครื่องผมก็เลยมี emacs มัน 2 แบบเลย
ถ้าดู code ภาษาอังกฤษก็ใช้ build with xft
ถ้าภาษาไทย ก็ใช้แบบธรรมดาไป

Update: มีปัญหา Segment fault เกิดขึ้นบ่อยมาก
ถ้า switch ไปใช้ workspace อื่น

Related link from Roti

Friday, August 18, 2006

Ruby กับ Ingres

หลังจอเฝ้ารอว่าจะมีใครเขียน adapter ให้ ruby สามารถคุยกับ ingres ได้
ในที่สุด ก็รอไม่ไหว หันไปหาทางเลือกอื่น
นั่นคือ ช่องทาง ODBC

solution ก็คือ
  • unixODBC
    อันนี้ไม่มีอะไรพิเศษ ติดตั้งได้จากพวก Package Manager ของ Distro ได้เลย
    ประเด็นถัดมา ก็คือการสร้าง datasource
    ถ้าสร้างด้วยมือ ดูจะโหดร้ายไปหน่อย
    ingres ก็เลยเตรียม utility ที่ชื่อ iiodbcadmn



  • Ruby ODBC
    ตัวนี้มีปัญหานิดหน่อย
    ตรงวิธีที่เขาเขียน
    เนื่องจากเขาเขียนโดยอิง mysql เป็นหลัก
    พอมาใช้กับ database ตัวอื่น ก็อาจจะเป็นปัญหาบ้าง
    ในกรณีของ ingres ถ้าลอง build แล้ว run testcase ดูจะพบ error นี้
    [pphetra@fedora ruby-odbc-0.9991]$ ruby test.rb sso
    connect.............ok
    create_table........ok
    insert.............../test/20insert.rb:6:in `call': 37000 (2913)
    [unixODBC][Ingres][Ingres ODBC Driver][Ingres]line 1, You cannot assign
    a value of type 'varchar' to a column of type 'integer'.
    Explicitly convert the value to the required type. (ODBC::Error)
    from ./test/20insert.rb:6
    from test.rb:20
    from test.rb:16

    ถ้าเข้าไปดูใน testcase จะเห็น code ที่มีปัญหา หน้าตาเป็นแบบนี้
    $p = $c.proc("insert into test (id, str) values (?, ?)") {}
    $p.call(3, 'FOO')

    ประเด็นที่เป็นปัญหา ก็คือวิธีที่ใช้ในการ binding parameter เข้ากับ statement
    หลังจากไล่ source code ของ ruby-odbc ซักพัก ผมก็เจอจุด quickfix
    ก็เลยแก้ง่ายๆลงไป
    @@ -6265,6 +6266,7 @@
    if (coldef == 0) {
    switch (ctype) {
    case SQL_C_LONG:
    + stype = SQL_NUMERIC;
    coldef = 10;
    break;
    case SQL_C_DOUBLE:

  • Ruby DBI
    ตัวนี้ไม่มีอะไรพิเศษ
    ผมเลือกลงแต่ dbd ที่ต่อเข้า ODBC อย่างเดียว


ขั้นตอนถัดไป ระหว่างรอให้มี ODBC Adapter(สำหรับ rails) ดีๆออกมา
ก็จะทดลองเขียน rails adapter ที่ต่อเข้ากับ ruby dbi ไปพลางๆก่อน
(ใช้ตัวอย่างจาก sqlserver adapter ที่มี mode ที่ใช้ odbc ด้วย)

Related link from Roti

Monday, August 14, 2006

เรื่องเล่าจากทางเหนือ

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

จบมาแล้ว เวลาเห็นข่าวที่เกี่ยวกับชาวเขา
ก็รู้สึกว่าเป็นความผูกพันที่ต้องติดตามอ่าน

วันนี้อ่านเจอ blog ของ คุณจตุพร วิศิษฎ์โชติอังกูร
ที่ทำงานอยู่ในพื้นที่
เขามีเรื่องเล่าหลายเรื่องที่น่าสนใจทีเดียว

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

Related link from Roti

หนุ่มญี่ปุ่นกับเมืองปาย

ลองอ่านบทสัมภาษณ์ของ หนุ่มญี่ปุ่นคนหนึ่ง
ที่เรียกเมืองปายว่า "บ้าน"
Innerview : Toron

Related link from Roti

Hivemind #2

ลองดูเรื่อง Configuration บ้าง
concept ของ Configuration ใน Hivemind ก็คือ
เวลาเรานำ service ไปใช้, service ย่อมต้องการ parameter บางอย่าง
เพื่อที่จะได้กำหนดพฤติกรรมให้เหมาะกับสภาพแวดล้อมที่ service run อยู่

ใน Hivemind เราแบ่งส่วนของ configuration ออกเป็น 2 ส่วนคือ
  • ส่วนที่ difine นิยาม และการนำไปใช้
    เรียกตรงนี้ว่า Configuration Point
  • ส่วนที่ provide configuration data
    มีศัพท์เรียกว่า Contribution


ดูตัวอย่างดีกว่า
สมมติเรามี service ตัวหนึ่งชื่อ FooService
public interface FooService {

public void connect();

}


public class FooServiceImpl implements FooService {

private List<ServerInfo> serverInfos;

/**
*
@param serverInfo the serverInfo to set
*/

public void setServerInfos(List<ServerInfo> infos) {
this.serverInfos = infos;
}

/* (non-Javadoc)
* @see test.FooService#connect()
*/

public void connect() {
System.out.println("connect to " + serverInfos.get(0).getHost());
}
}


public class ServerInfo {
private String host;
private String port;
private String uri;

// setter & getter ..
}

ในตัวอย่างข้างบนนี้ serverInfos ก็คือ configuration point
ที่เราอยากให้สามารถปรับเปลี่ยนได้ตามบริบทที่ service นี้ไป run อยู่
ดังนั้นใน descriptor file ของเรา เราก็จะกำหนดดังนี้

เริ่มด้วยการกำหนด configuration point ก่อน
<configuration-point id="serverInfo" >
<schema>
<element name="serverInfo">
<attribute name="host" required="true"/>
<attribute name="port" required="false"/>
<attribute name="uri" required="true"/>
<conversion class="test.ServerInfo"/>
</element>
</schema>
</configuration-point>

ให้สังเกตว่าเรามีการ define schema ของรูปแบบของข้อมูลด้วย

จากนั้นก็กำหนดบอกว่า FooService เราต้องการ information จาก configuration-point นี้นะ
<service-point id="foo" interface="FooService">
<invoke-factory>
<construct class="impl.FooServiceImpl">
<set-configuration property="serverInfos" configuration-id="serverInfo"/>
</construct>
</invoke-factory>
</service-point>

สุดท้าย เวลาที่เราต้องการ customize กำหนดค่า serverInfo
เราจะทำผ่าน contribution
โดยรูปแบบข้อมูล ต้องตรงตามที่กำหนดเป็น schema ไว้ใน configuration-point
<contribution configuration-id="serverInfo">
<serverInfo host="128.1.1.10" uri="/app/service"/>
</contribution>


ลองเปรียบเทียบกับ spring บ้าง
ตามตัวอย่างข้างบนนี้ spring จะเขียนเป็นแบบนี้
<bean id="fooService" class="test.impl.FooServiceImpl">
<property name="serverInfos">
<list>
<bean class="test.ServerInfo">
<property name="host" value="128.1.1.10"/>
<property name="uri" value="/app/service"/>
</bean>
</list>
</property>
</bean>


ดูแค่นี้ ก็จะเห็นว่า spring มี configuration ที่ดูเข้าใจง่ายกว่า (จริงๆแล้วขึ้นกับว่า ผู้ดูเป็นใคร?)

แต่ hivemind มีข้อได้เปรียบกว่า spring ตรงที่
contribution ของ hivemind มัน auto-discovery ได้
คือเราสามารถแยก component ออกเป็น module ย่อยๆ
โดยแต่ละ module ก็มี descriptor file ของตัวเอง
ดังนั้นเราอาจจะ package service ไว้ใน module หนึ่ง
แล้วอาจจะ pack contribution ไว้ในอีก module หนึ่ง
เมื่อ registry ของ hivemind load ขึ้นมา มันจะมองหา
descriptor file ที่อยู่ใน classpath ทั้งหมด แล้วประกอบขึ้นมาเป็น registry ให้โดยอัติโนมัติ

Related link from Roti

Sunday, August 13, 2006

Hivemind #1

ช่วงสองสามเดือนมานี่ ชีวิตขลุกอยู่กับ Tapestry
แน่นอนเมื่อโปรเจคใหญ่ขึ้น ซับซ้อนขึ้น
ก็หลีกเลี่ยงไม่ได้ที่จะต้องลงไปแตะ Architecture ที่เป็นฐานรากของ Tapestry
ซึ่งก็คือ Hivemind

Hivemind เรียกตัวเองว่า Service and Configuration MicroKernel
โดย Service ก็คือ POJO ที่สามารถร้อยเข้าหากันด้วยวิธี Dependency Injection เช่นเดียวกับ Spring
ส่วน Configuration ก็คือ การ define data ที่ service ต้องการใช้

ลองดูตัวอย่างการ configuration ของ hivemind

เริ่มด้วยการ define service

<service-point id="foo" interface="Foo">
...
</service-point>


เทียบกับ Spring ที่ใช้ concept เป็น Bean
<bean id="foo" class="test.impl.FooImpl">
...
</bean>

ความแตกต่างในเบื้องต้น จะเห็นได้ว่า bean ใน spring เราจะระบุชื่อที่รวม package name ด้วย
ส่วน hivemind ชื่อ package จะถูก define ไว้ที่ระดับ document root ของ xml

ส่วนประเด็นที่สอง hivemine นิยมประกาศให้ชัดไปเลยว่า
service ที่ expose นั้นมี interface เป็นอย่างไร
ซึ่ง client ที่ต้องการใช้ service สามารถ query ตามชื่อของ service ได้
IPreferenceService svr = (IPreferenceService) registry
.getService(IPreferenceService.class);


ทีนี้ลองไปดูวิธีร้อย service เข้าหากันบ้าง
เอาวิธีร้อยแบบ declarative (ไม่ได้ใช้ autowire)
ในส่วนของ Hivemind
<service-point id="dao" interface="IPreferenceDao">
<create-instance class="impl.PreferenceDapImpl"/>
</service-point>

<service-point id="preferenceService" interface="IPreferenceService">
<invoke-factory>
<construct class="impl.PreferenceServiceImpl">
<set-service property="dao" service-id="dao"/>
</construct>
</invoke-factory>
</service-point>

เทียบกับ spring
<bean id="dao" class="test.impl.PreferenceDaoImpl"/>

<bean id="preferenceService" class="test.impl.PreferenceServiceImpl">
<property name="">
<ref bean="dao"/>
</property>
</bean>


จะเห็นว่าถึงตอนนี้ hivemind กับ spring ก็ดูเหมือนๆกัน
ต่างกันตรงที่ใช้ wording กันคนละแบบ
แต่จริงๆแล้วพอถึงจุดนี้ มันเริ่มเกิดความแตกต่างในวิธี implement แล้ว
ใน Hivemind ถ้าเราลอง get service ขึ้นมา
object ที่เราได้จะมี class เป้น SingletonProxy
ส่วนของ Spring ถ้า get bean ขึ้นมา object ที่ได้
จะเป็น class ตรงตามที่ define ไว้
// hivemind
System.out.println(registry.getService(PreferenceServiceImpl.class);
// => <SingletonProxy for test.preferenceService(test.IPreferenceService)>

// spring
System.out.println(factory.getBean("preferenceService"));
// => test.impl.PreferenceServiceImpl

ดูอย่างนี้แล้ว Hivemind จะออกไปในแนว Lazy initialize มากกว่า
คือถ้า proxy ยังไม่ถูกใช้งาน object จริงๆ ก็จะยังไม่ถูกสร้างขึ้น
แต่ในกรณี spring bean ลูกๆที่ถูกอ้างถึง ต้องมีการ initialize สร้างเป็น object
ก่อนที่จะถูก set ให้กับ bean แม่

พักอยู่ที่จุดนี้ก่อน
ไว้ถ้ามีเวลา จะลงไปลึกถึงเรื่อง interceptor,
configuration point, service management

Related link from Roti

Saturday, August 12, 2006

Derived Folder (File) ใน Eclipse

ใครที่ใช้ eclipse กับ project ที่มีขั้นตอนการ build ที่ซับซ้อน
อาจจะพบปัญหาว่าเวลาเราสั่ง open resources
แล้ว eclipse มันแสดง file ที่เราไม่ต้องการเห็น

เราสามารถ disable directory ที่เราไม่ต้องการ ให้ "open resources" ค้นหา
ได้โดยการ click ขวา เพื่อเลือก properties ของ folder นั้น
จากนั้นก็ tick ที่ Derived checkbox

Related link from Roti

Thursday, August 10, 2006

ใครๆก็เปลี่ยนไปใช้ OSGi

Websphere version 6.1 เปลี่ยนมาใช้ OSGi เป็น fundamental architectural layer
ใครที่เคย change directory เข้าไปใต้ lib path ของ websphere แล้วเห็น jar file
เป็นร้อย (พูดให้เวอร์ไว้ จำนวนจริงเท่าไร ไม่เคยนับ) ก็จะไม่เห็นแล้ว
เพราะมันจะย้ายไปอยู่ใต้ plugins แทน (ใน format แบบเดียวกับที่เราเห็นใน eclipse)

ส่วน JOnAS version 5 ก็มี proposol ว่าจะเขียนบน OSGi เหมือนกัน

สำหรับ Geronimo ก็เห็นมีการ push กันใน mailing list มานานแล้ว

Tuscany ก็มีการเปิด isuue เรื่อง initial OSGi support effort เหมือนกัน

Related link from Roti

Wednesday, August 09, 2006

Hit and Run


... hit and run micromanagement. That’s where you micromanage one developer in a spurt of activity and then suddenly disappear from that developer’s life for a couple of weeks while you run around micromanaging other developers.

ฟังคุ้นๆ

จาก "The Command and Control Management Method"

Related link from Roti

Tuesday, August 08, 2006

Dirvish

เห็นมีคนพูดถึง Dirvish ว่ามี concept คล้ายๆ feature Time Machine ใน Leopard

With dirvish you can maintain a set of complete images of your filesystems with unattended creation and expiration. A dirvish backup vault is like a time machine for your data.

Related link from Roti

Ruby-like syntax

เห็น code_poet เขาเขียนเรื่อง FinderTemplate
ไม่รู้ว่าเป็นพฤติกรรมร่วมกันหรือเปล่า
สำหรับคนที่ใช้ ruby แล้ว
พอกลับมาเขียน java ก็มักเลียนแบบ syntax บางอย่างของ ruby
คือที่คิดอย่างนี้ ก็เพราะว่าผมก็มีอาการอย่างนี้เหมือนกัน

Related link from Roti

Friday, August 04, 2006

Tapestry Asset

Asset ในความหมายที่ Tapestry ใช้ ก็คือ binary หรือ text file ที่ browser ต้องการใช้
ไม่ว่าจะเป็น image file, javascript file, css file ฯลฯ

การที่ Tapestry define Asset ออกมาเป็นเรื่องเป็นราวแบบนี้
อย่างน้อยก็ช่วยให้เราจัดการกับ Resource File ได้ง่ายขึ้น
เช่นแทนที่จะยุ่งกับ physical file โดยตรง ก็ไปจัดการผ่าน Logical file แทน
ทำให้จัดการเรื่องพวก internationalize ได้ง่ายขึ้น
(พวกเปลี่ยนรูปที่แสดง ตาม locale ของ user)
รวมทั้งทำเรื่อง dynamic asset ณ runtime ได้ง่ายขึ้น

Tapestry แบ่งที่มาของ Asset ออกเป็น 2 ประเภทคือ
  • Context Asset
    asset ที่อยู่ใน Context Directory ของ Web application
  • Classpath Asset
    asset ที่อยู่ใน classpath ไม่ว่าจะเป็นใน WEB-INF/classes หรือใน jar ต่างๆ

asset ประเภท Context Asset ถือเป็นเรื่องปกติธรรมดาอยู่แล้วไม่มีประเด็นให้พูดถึง
แต่ asset ประเภท classpath นี่สิ มีประโยชน์มากในแง่ของการ reuse
เพราะช่วยให้เราเขียน component เป็น module (ภายในมีทั้ง html, gif, javascript, ฯลฯ)
ที่นำมาใช้ใหม่ได้่ง่าย

การเข้าถึง Classpath Asset จะทำผ่าน AssetService
โดย url ที่ใช้เข้าถึงจะมีหน้าตาแปลกๆแบบนี้

http://localhost:8080/fin/app?digest=ff8e3332b16d745764befd7c6956ad9a&path=%2Fwcf%2Fweb%2Fjs%2Fwcfutil.js&service=asset

สังเกตว่ามี parameter ที่ชื่อ digest อยู่ด้วย
ทำไมต้องมีค่า digest ด้วย?

มูลเหตุที่ต้องมี digest แปะมา ก็เพื่อป้องกันไม่ให้ผู้ไม่หวังดี
ใช้ service นี้ไปดึงเอาพวก class file ออกมาตามใจชอบ
(เนื่องจาก service นี้สามารถดึงเอาอะไรก็ได้ที่อยู่ใน classpath)

ประเด็นหนึ่งของการมี checksum แปะแบบนี้ก็คือ
มันจะกิน cpu time ทุกครั้งที่มีการ request asset นี้
Tapestry ก็เลยออกแบบให้มี cache ในส่วน generate digest

ผลพลอยได้ที่มาโดยไม่ตั้งใจ ก็คือ เมื่อเนื้อหา file เปลี่ยน
ค่า digest ก็จะเปลี่ยนไปได้วย (แต่ต้อง clear cache ก่อน)
ทำให้เราไม่เจอปัญหาเรื่อง browser cache
(เหมือนกับที่ rails generate timestamp แปะท้าย url
ที่ request ขอ javascript ก็เพื่อป้องกันปัญหานี้เหมือนกัน)

ปัญหาของการมี digest อีกอย่างก็คือ
link ทุก link ต้องเกิดจากการ generate ของ server
(เพราะ server จะต้องแปะค่า digest ให้)
ทำให้เราไม่สามารถใช้ javascript load ข้อมูลแบบ dynamic ได้
(dojo ใช้กลไกการ load แบบ dynamic)

เพื่อแก้ปัญหานี้ Tapestry4.1 ก็เลยมีการเพิ่ม feature UnprotectedAssets
เพื่อให้เราเลือก config ได้ว่า Asset กลุ่มไหนต้องมี Digest
กลุ่มไหนที่ไม่ต้องมี Digest

Related link from Roti

Wednesday, August 02, 2006

Tapestry4.1

Tapestry 4.1 release ออกมาแล้ว
version นี้มีจุดที่น่าสนใจก็คือ
  • integrate dojo toolkit เข้าไปใน framework เลย
    ลองดูตัวอย่างในเอกสารเขา

    สมมติเรามี html นี้อยู่
    <div id="foo">Hello</div>

    ถ้าเราอยากดัก event onMouseOver
    โดยถ้า event เกิดแล้วให้ไป trig listener ที่อยู่บน server side
    @EventListener(elements = "foo", events = "onMouseOver")
    public void watchText() {
    ...
    }

    แค่นี้เอง

    กลไกภายในที่เกิดขึ้นข้างใน
    ก็ประมาณว่า จะมีการร้อย event ที่เกิดใน DOM
    เข้ากับ dojoEvent ซึ่งจะทำการ trig ไปยัง tapestry service
    ซึ่งจะ call เข้าหา method ที่ต้องการให้
  • สามารถ render ผลลัพท์เป็น JSON ได้
  • bundle dojo อยู่ใน tapestry.jar เรียบร้อย
    ไม่ต้อง download ติดตั้ง
  • กลไกในส่วน client validation เปลี่ยนไป implement ด้วย dojo
  • มีการ map dojo widget เข้าเป็น tapestry component จำนวนหนึ่ง
    ทำให้สะดวกในการใช้มากยิ่งขึ้น

ฮ่วย ออกช้าไปสองเดือน
เมื่อสองเดือนที่แล้ว ผมนั่งปวดหัวกับการ integrate dojo + tapestry4.0 + JSON

Related link from Roti

Tuesday, August 01, 2006

Programming by Coincidence

เห็นที่ bact' comment ไว้ใน Prototype กับ Legacy javascript project
ก็เลยนึกถึงประโยคนี้ "Programming by Coincidence"
(อ่านเจอในหนังสือ The Pragmatic Programmer)

Style วิธีการเขียนแบบนี้ เป็นแบบที่่ผมใช้มาตลอดชีวิตเลยก็ว่าได้
นั่นคือ ทดลองเขียน, run ดูผลลัพท์, ปรับแต่งแล้ว run ใหม่
ถ้า run ได้ก็จบ ถัายังไม่ได้ ก็เข้าวงจรเดิม คือทดลองใส่ไอ้นั่นหรือไอ้นู่นเข้าไป
loop อย่างนี้ไปเรื่อยๆ จนกว่าจะทำงานได้

ปัญหาก็คือ บางทีเราไม่รู้ว่า ทำไมมันถึง run ได้
อันนี้เป็นที่มาของคำว่า Coincidence (บังเอิญ, ประจวบเหมาะ)
ที่แย่กว่านั้น ก็คือด้วยความเร่งรีบ หรือความชุ่ยก็แล้วแต่
แทนที่จะทำความเข้าใจว่าทำไมมันถึงทำงานได้
กลับถือโอกาสข้ามมันไปเสีย
ที่สนุกกว่านั้น ก็คือคนใหม่ๆที่ตามมา เห็นพี่ๆเขียนอย่างนั้น
ก็ลอกๆกันต่อ

มาช่วงนี้ ผมมีงานที่ต้อง port โปรแกรมเก่า ขึ้นมาสู่ technology ใหม่
ทำให้ได้มีโอกาสกลับไปนั่งแกะโปรแกรมที่ตัวเองและน้องๆเขียนไว้เมื่อ 7-8 ปีก่อน,
คราวนี้แหล่ะเห็นชัดเลย
"programming by coincidence" เต็มๆ
บอกได้คำเดียวว่า "น่ากลัวมาก"

ปล. ว่าจะเอาพวก JML มาแก้ทางพวกน้องๆที่บริษัทฯ
ที่มีแนวไปในทาง Programming by Coincidence

Related link from Roti

นิ้ว

ช่วงนี้โปรเจคที่ผมทำมา 2 เดือน เริ่มเกิด Momentum แล้ว
วันนี้นั่งเขียนโปรแกรมจนเมื่อยนิ้วเลย
(ถ้าเป็นช่วงแรกๆ ต้องเสียเวลาไปนั่งไตร่ตรอง
กับนั่งเปิดเอกสารเยอะหน่อย)

ตอนนี้เส้นเอ็นที่ต่อจาก forearm ไปนิ้วก้อย เริ่มเกิดอาการตืงๆแล้ว
นี่ขนาดย้ายปุ่ม ctrl ขึ้นมาอยู่ที่ caps lock แล้วนะ

พอปวดนิ้วที ก็เริ่มเปิด net ดู keyboard Kinesis ที
แต่ด้วยราคาระดับ $300 ก็ทำให้ยังตัดใจซื้อไม่ลง

เฮ้อ แก่แล้วก็ยังนี้แหล่ะ
ใช้ linux ก็เมื่อยนิ้วก้อย (ctrl)
ใช้ mac ก็เมื่อยนิ้วโป้ง (cmd)

Related link from Roti

เบรุต



ก่อนและหลังจากโดนยิวโจมตี

ภาพนี้ได้จาก workshop.evolutionzone.com
ตามไปอ่านได้ที่นี่ Geopolitics: Beirut before & after

Related link from Roti

Saturday, July 29, 2006

Prototype กับ Legacy javascript project

อ่านเจอประเด็น Prototype conflict กับ Legacy project
แต่ก็ไม่ได้สนใจอะไร เพราะนึกไม่ออกว่ามันจะ conflict กันตรงไหน
(ตอนแรกนึกว่าเป็นเรื่อง name conflict)

วันนี้อ่านเจอ case ตัวอย่างใน How Prototype Can Break Your Site
ของ code_poet
เขายกตัวอย่างง่ายๆว่า
var a = new Array();
a[0] = 1;
a[1] = 2;
for (var i in a) {
print(i + " = "+ a[i]);
}

code ข้างบนก็ดูเรียบๆตรงไปตรงมา ไม่น่าผิดปกติตรงไหน
แต่ถ้าเมื่อไรก็ตามที่เรา include Prototype เข้ามา
code ข้างบนจะทำงานผิดไปจากเดิม
โดยผลลัพท์ที่ได้จะหน้าตาแบบนี้แทน
0 = 1 
1 = 2
each = function() { }
iterate = function() { }


สาเหตุเป็นเพราะ
  • Prototype มีการ extends Array class ของ javascript
    เพื่อเพิ่ม feature เกี่ยวกับการ iterate
  • loop code ใช้วิธี loop array แบบพิสดารไปหน่อย
    คือแทนที่จะใช้ for (var i = 0; i < a.length; i++)
    กลับไปใช้ for (var i in a) แทน

Related link from Roti

He ni zai yi qi

เมื่อวันก่อน ก่อนออกไปทำงาน เผลอไปเปิด cable tv ดูเข้า
ได้เรื่องเลย เจอเรื่องแนวที่ชอบ
ผลก็คือไปทำงานสาย กับ เสียน้ำตาไปหลาย cc.

กลับมาจากที่ทำงาน ก็เลยนั่งค้นชื่อเรื่อง
กะว่าจะให้แฟนดูสักหน่อย
ชื่อเรื่องภาษาอังกฤษคือ Together

A 13-year-old violin prodigy has his life changed when his father takes him to Beijing to audition for a prestigious music school.

หนังเรื่องนี้เพลงเพราะมาก



ดูแล้วก็นึกในใจว่า นี่ถ้ายังไม่มีลูก
เราจะรู้สึกซาบซื้งได้อย่างนี้หรือเปล่า

Related link from Roti

Tuesday, July 25, 2006

เสียภาษีที่ดิน

แฟนผมได้มรดกที่ดินมาแปลงหนึ่ง
ด้วยความที่เป็นพลเมืองดี ก็เลยต้องไปเสียภาษี
ก่อนไปก็วิตกนิดหน่อยว่า
ไม่ได้เสียภาษีมา 10 ปีแล้ว
ค่าภาษีจะโดนไปเท่าไร

ตอนยื่นเสียภาษี เจ้าหน้าที่ถามว่า
ที่ดินมีการทำอะไรบ้างหรือเปล่า
เพราะถ้าไม่ได้ทำอะไร จะแพงกว่านะ

แฟนก็ยืนยันว่า ไม่ได้ทำอะไร
หลังจากเคาะเครื่องคิดเลขอยู่ครู่หนึ่ง
ก็ได้ข้อสรุปออกมาว่า
แปลงที่หนึ่ง 95 ตารางวา เสีย 7 บาท ค่าปรับ 5 บาท
แปลงที่สอง 13 ตารางวา เสีย 0.91 บาท ค่าปรับ 5 บาท

รวมทั้งสิ้น 18 บาท

Related link from Roti

javascript, new function(..)

สงสัยมานานแล้ว ว่าประโยค new แบบนี้ จริงๆแล้วมันหมายความว่าอะไร
function foo(x) {
...
}

var test = new foo(2);

Paul Crowley เขาอธิบายไว้ใน "Subclassing in JavaScript, part 1" ว่า
step ที่เกิดใน new foo(2) จริงๆแล้วก็คือ
  • create new blank object (call it "res")
  • res.__proto__ = foo.prototype
  • foo.apply(res, [2])
    res จะกลายเป็น this ภายใน function foo
  • return res

เห็นอย่างนี้ ก็ไม่งงแล้ว
function foo(x) {
this.x = x;
}
var test = new foo(2);
test.x; // -> 2


แต่ตอนนี้ไปงงต่อตรง
__proto__ คืออะไร และต่างกับ prototype ต่างกันอย่างไร
ใน blog ของ Paul เขาอธิบายว่า
เวลาเราหาค่า property ใน object กรณีที่ object นั้นไม่มี key นั้นอยู่
และ __proto__ ใน object นั้นถูก set ไว้
javascript ก็จะมองหา key จาก object ที่ __proto__ ชี้อยู่

แล้วถ้าลองพิมพ์ค่า foo.__proto__ หล่ะ จะได้อะไร

foo.__proto__; // => function foo(x) { this.x = x; }
foo; // => function foo(x) { this.x = x; }

? ชี้เข้าหาตัวเองเหรอ ชีวิตสับสน
แล้วถ้าอย่างนี้หล่ะ
foo.__proto__.__proto__ // => function() {}

งงต่อไป

Related link from Roti

Sunday, July 23, 2006

Custom Dojo Package

โดยปกติ เมื่อเราสั่งคำสั่ง
dojo.require("dojo.lang");

สิ่งที่เกิดขึ้น ก็คือ dojo จะค้นหาว่า package dojo.lang
ถูก include ไว้ใน file dojo.js แล้วหรือยัง
ถ้ามีแล้ว ก็จะไม่ทำอะไร แต่ถ้ายังไม่มี
ก็จะทำการ load มาให้

ประเด็นคำถามก็คือ แล้วมันจะ load package dojo.lang จาก file อะไร
คำตอบก็คือ dojo จะลอง load file ตามลำดับนี้ (relative จาก dojo root directory)

- src/lang.js
- src.js
- __package__.js

ถ้าเจอใน file ไหนก่อน ก็จะหยุดที่ตรงนั้น
แต่ถ้าไม่เจอเลย ก็จะ raise error

ที่นี้ถ้าเรามี custom component ของเราเอง
แต่ไม่อยากเก็บไว้ใต้ path เดียวกับ dojo
เราจะต้องทำอย่างไร

ทดลองสร้าง package pok.test ขึ้นมา
กำหนดว่า source code ของ package นี้จะเก็บอยู่ใต้ directory pok
ที่อยู่ระดับเดียวกับ directory src ของ dojo
จากนั้น สร้าง file test.js ไว้ใต้ directory pok

+ src
+ pok
- test.js

คำสั่งที่สำคัญที่ต้องมีใน file test.js ก็คือ
dojo.provide("pok.test");

ซึ่งเป็นการบอกให้กลไกการ load ของ dojo หยุดการค้นหา package

เวลาจะใช้งาน custom package
เราก็ต้องมีการระบุ path ในการ search โดยใช้คำสั่ง

dojo.setModulePrefix("pok", "pok");

parameter ตัวแรก คือชื่อ prefix ของ package
ส่วน parameter ตัวที่ 2 ก็คือ relative path ของ package
ซึ่ง relative กับ root directory ของ dojo

เมื่อเราสั่ง dojo.require("pok.test");
dojo ก็จะดูว่า prefix ของ package ที่ชื่อ pok
จะต้องไปหาที่ directory ไหน
กลไกการหา ก็เป็นตามปกติ นั่นคือ

- pok/test.js
- pok.js
- __package__.js

Related link from Roti

กอริลลา

ลองอ่าน "นักวิจัย เหมือน กอริลลา ในหมอก มืดมิด"
blog ของ คนไร้กรอบ
) ให้ดูพฤติกรรมสัตว์ ดังเช่น ที่ บริษัท Royal Dutch /shell ศึกษาวิชา เกี่ยวกับสัตว์ เพื่อมาดูองค์กรมีชีวิต
ผมก็เลย อุปมา นักวิจัย ก็เหมือน กอริลลา ในกรงที่ สวนสัตว์ (อย่าโกรธ นะ ที่อุปมาแบบสัตว์)
ธรรมชาติ ของลิงเขาต้องอยู่ในป่า มีอิสระเสรี มีที่อยู่เป็นอาณาเขต ฯลฯ พอเรา เอาพวกเขามาขัง เขาเขย่ากรง ก็เพื่อ คลายเครียด แสดงความเป็นใหญ่ ฯลฯ มันเป็นธรรมชาติ อย่าไปโกรธเขา
ในทำนองเดียวกับนักวิจัย ที่ได้ทุนไปเรียนเอกกลับมา โทกลับมา เราเอาเขา ที่อัตตาเยอะ(ประมาณ กอริลลา) มาขัง เงินเดือนนิดเดียว พวกเขา ก็เลย เอา อัตตามาชนกัน เขย่ากรง ตีอกใส่กัน ฯลฯ
ผู้บริหาร บริหารนักวิจัยแบบผิดธรรมชาติ เอา นักวิจัยมาใส่กรง

Related link from Roti

Saturday, July 22, 2006

EasyMock

ผมผัดผ่อนกับเรื่อง Unit Testing แบบจริงๆจังมานานแล้ว
ส่วนใหญ่จะทำนิดทำหน่อย ตรงจุดที่ไม่แน่ใจใน algorithm
มา project ตัวปัจจุบันนี้ก็เลยตั้งใจว่า
จะบังคับตัวเองให้เขียน testcase ดีๆให้ได้

ตัว tool ที่เลือกใช้ ก็คือ TestNG
ซึ่งหลังจากเขียนไปได้จำนวนหนึ่ง ก็รู้สึกไม่ค่อยมี feedback มากระตุ้นตัวเองมากพอ
ก็เลยเอา JCoverage มาใช้อีกตัว

ใช้ไปได้สักพัก ก็ยังรู้สึกว่ามีแรงจูงใจยังไม่พออยู่ดี
เนื่องจากเรารู้ตัวเองว่า เรายังหลีกเลี่ยงไม่แตะ source code ส่วนที่สำคัญจำนวนหนึ่งอยู่
เนื่องจากมันเป็น code ส่วนที่ไกล้ชีิดกับ Database
ซึ่งเมื่อทำเสร็จมันต้อง rollback ข้อมูลที่ insert, update ลงไป
เพื่อให้ database อยู่ในสถานะก่อนหน้าที่จะ test
(ซึ่งเดิมที่เคยทำ ผมใช้ DBUnit ช่วยทำในส่วนนี้)

ปัญหาหลักๆก็คงอยู่ตรงความขี้เกียจนี่แหล่ะ ขี้เกียจเตรียมข้อมูล

EasyMock เข้้ามาเป็นพระเอกในจุดนี้
EasyMock ช่วยให้ผมสามารถ test business logic โดยไม่ต้องยุ่งกับ database ได้

เริ่มแรก สมมติเรามี logic การ Login อยู่
public class AuthenService {

IUserDao userDao;

public boolean authenticate(String login, String password)
throws DisableAccountException{

User user = userDao.getByLoginId(login);
if (user.isEnable()) {
if (user.getPassword().equals(password)) {
return true;
}
} else {
throw new DisableAccountException();
}
if (userDao.increaseFailCount() > 3) {
userDao.disableLogin(login);
}
return false;
}

}

โดยเราจะ test logic "ในกรณีที่ login ไม่สำเร็จ 3 ครั้งให้ disable account ของ user"
คัว class ที่เราจะสร้าง Mock ก็คือ IUserDao
public interface IUserDao {
/**
*
@param login
*
@return
*/

public User getByLoginId(String login) ;

/**
*
@return number of fail count
*/

public int increaseFailCount();

/**
*
@param login
*/

public void disableLogin(String login);

}


เริ่มด้วยการเขียน testcase
public class TestLogin {

private User createUser(boolean flag) {
User user = new User();
user.setLogin("pok");
user.setPassword("xxx");
user.setEnable(flag);
return user;
}

@Test
public void testDisableAccount() {

IUserDao dao = createStrictMock(IUserDao.class);

expect(dao.getByLoginId("pok")).andReturn(createUser(true));
expect(dao.increaseFailCount()).andReturn(4);
dao.disableLogin("pok");
expect(dao.getByLoginId("pok")).andReturn(createUser(false));

replay(dao);

AuthenService service = new AuthenService();
service.setUserDao(dao);
try {
service.authenticate("pok", "yyy");
} catch (DisableAccountException de) {
fail("early throw exception");
}
try {
service.authenticate("pok", "yyy");
fail("should not be here.");
} catch (DisableAccountException de) {

}
verify(dao);
}

}

ใน code เราเริ่มด้วยการ สร้าง Mock ของ​ IUserDao
IUserDao dao = createStrictMock(IUserDao.class);

จากนั้นก็ train Mock ว่า เดี๋ยวในตอน test จะมีการเรียกใช้แบบนี้เกิดขึ้นนะ
expect(dao.getByLoginId("pok")).andReturn(createUser(true));
expect(dao.increaseFailCount()).andReturn(4);
dao.disableLogin("pok");
expect(dao.getByLoginId("pok")).andReturn(createUser(false));

จบการ train ด้วยคำสั่ง replay
replay(dao);

ที่เหลือก็เป็นการจำลองการเรียกใช้งาน AuthenService

สุดท้ายจบ testcase ด้วยคำสั่ง
verify(dao);

ซึ่งเป็นการตรวจว่าถ้า dao เราถูกเรียกไม่เรียงลำดับ หรือไม่ครบตามที่เรากำหนดไว้
testcase นั้นก็จะ error

ลองดูผลลัพท์จาก JCoverage หลังจาก test แล้ว



สถานะปัจจุบัน ตอนนี้ผมได้ feel ของการเขียน test กลับมาแล้ว
คือรู้สึกว่าติดและสนุกกับการเขียน Test

Related link from Roti

Friday, July 21, 2006

blog ภาษาอังกฤษ

เมื่อวันก่อนมีชาวต่างประเทศคนหนึ่งเขา comment ใน
http://pphetra.blogspot.com/2006/03/rails-s-date-widget-plugin.html#comments
อยากให้แปลเรื่องที่ถูกถึง Date picker ใน Rails
ให้เป็นภาษาอังกฤษหน่อย

ส่วนพรุ่งนี้ บริษัทแฟนอยากให้เข้าไปช่วย
brief เรื่อง java ให้กับ programmer ชาวจีินให้หน่อย

ได้เวลาฝึกพูดกับฝึกเขียนแล้ว

Related link from Roti

Tuesday, July 18, 2006

เดินทาง

ได้ link มาจาก BioLawcom
เรื่องนี้ไม่อ่านไม่ได้
chittawiwat.pdf

ผมใช้เวลาเดินทางจากเชียงใหม่เมื่อวันที่ 16 พฤษจิกายน 2548 ไปถึงเกาะสมุย เมื่อวันที่ 24 มกราคม 2549 ใช้เวลาทั้งหมด 66 วัน
การเดินทางในครั้งนี้ผมใช้เงื่อนไขหนึ่งคือ จะไม่เดินไปหาคนที่รู้จัก หรือถ้าคนรู้จักอยู่ที่ไหนก็จะหลีกไปใช้ทางอื่น
สองคือ จะไม่มีสตางค์ซึ่งเป็นสัญลักษณ์ของอำนาจติดตัวไปด้วย

Related link from Roti

Sunday, July 16, 2006

Dojo DateWidget in Thai

Dojo Widget ออกแบบมาดี
มีความเป็น object อยู่สูง
แถมยังมีลักษณะเป็น MVC ด้วย
ทำให้เราสามารถ customize ส่วน Presentation ได้โดยไม่ต้องยุ่งกับ Model

ในการทำ ThaiDateWidget
เราสนใจแค่การ override
function ที่ทำหน้าที่แสดงผล เดือน กับ ปี
dojo.provide("dojo.widget.html.ThaiDatePicker");
dojo.require("dojo.widget.html.DatePicker");


dojo.widget.defineWidget(
"dojo.widget.html.ThaiDatePicker",
dojo.widget.html.DatePicker,
{
thaimonths : ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน',
'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม',
'กันยายน', 'ตุลาคม', 'พฤษจิกายน', 'ธันวาคม'],

setYearLabels: function(year) {
this.previousYearLabelNode.innerHTML = year - 1+543;
this.currentYearLabelNode.innerHTML = year+543;
this.nextYearLabelNode.innerHTML = year + 1+543;
},
setMonthLabel: function(monthIndex) {
this.monthLabelNode.innerHTML = this.thaimonths[monthIndex];
}
}

);

เวลาใช้งาน
<html>
<head>
<script type="text/javascript" src="./js/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.html.DatePicker");
dojo.require("dojo.widget.html.ThaiDatePicker");
</script>
</head>
<body>

<span dojoType="DatePicker" widgetId='test'></span>
<br/>
<span dojoType="ThaiDatePicker" widgetId='test2'</span>
</body>
</html>

ผลลัพท์ออกมาหน้าตาดังนี้

Related link from Roti

Better Builds with Maven

Better Builds with Maven
คือ Ebook ที่ผมใช้เป็นคู่มือในการใช้ Maven2
(เคยให้น้องที่บริษัทอ่าน ตอนให้อ่านก็บอกว่าอ่านสบายๆ
น้องเขาบอกว่า 200 กว่าหน้านี่นะ อ่านสบาย)

หลังจากใช้ maven2 มา 6 อาทิตย์
ได้ประสบการณ์รวมๆ มาดังนี้

  • ถ้าใช้ในองค์กร ก็ควรจะใช้ร่วมกับ Maven proxy
  • maven2 ยังไม่เหมาะกับ Project ที่ใช้ Cutting Edge library เท่าไร
    เพราะว่ามันจะไม่มี repository ให้ใช้
    ทำให้เราต้องเสียเวลาสร้าง repository เอง
  • กรณีที่ Project เรามีหลาย Module
    แต่วิธีการ assign งานของเรา เป็นแบบ End-to-End
    คือทำตั้งแต่ UI ยัน Model
    การจัดการ Project ใน IDE ที่เราใช้ จะซับซ้อนขึ้นทันตาเห็น
  • บาง Package ที่ pack อยู่ใน repository ยังไม่ได้มาตรฐาน
    เช่น สมมติเราใช้ Tapestry4.02
    แล้วเราต้องการใช้ Library X ซึ่ง dependency กับ Tapestry อยู่
    แต่ใน packaging ของมันกลับระบุให้มัน dependency กับ Tapestry4.0
    ผลลัพท์ก็คือ เราก็จะ dependency กับทั้ง 4.0 และ 4.02
    (ถ้า Library X pack ไว้ถูกต้อง มันควรจะต้องระบุว่ามัน depend กับ 4.0+)
  • ใช้งานร่วมกับ CruiseControl ได้โดยไม่มีปัญหา
  • feature Transitive Dependencies ช่วยให้ชีวิตง่ายขึ้นเยอะ

Related link from Roti

Saturday, July 15, 2006

Block Scope

ลองดูตัวอย่างนี้
def bar()
"outer bar"
end

class Foo
def bar()
"my bar"
end

def test(&block)
block.call
end

def test2()
yield
end

def test3(&block)
instance_eval(&block)
end
end

f = Foo.new
f.test { bar }
f.test2 { bar }
f.test3 { bar }


คำถามก็คือ

f.test { bar }
f.test2 { bar }
f.test3 { bar }

ได้ผลลัพท์เป็นอะไร

เฉลย

f.test { bar } # => "outer bar"
f.test2 { bar } # => "outer bar"
f.test3 { bar } # => "my bar"

Related link from Roti

Lisp 1.5 Programmer 's Manual

นั่งดู manual ที่เขียนขึ้นเมื่อปี 1962
เจอตัวอย่างนี้เข้าไป
DEFINE (( 
(MEMBER (LAMBDA (A X) (COND ((NULL X) F)
( (EQ A (CAR X) ) T) (T (MEMBER A (CDR X))) )))
(UNION (LAMBDA (X Y) (COND ((NULL X) Y) ((MEMBER
(CAR X) Y) (UNION (CDR X) Y)) (T (CONS (CAR X)
(UNION (CDR X) Y))) )))
(INTERSECTION (LAMBDA (X Y) (COND ((NULL X) NIL)
( (MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION
(CDR X) Y))) (T (INTERSECTION (CDR X) Y)) )))
))

มึนเลย

ถ้าเขียนแบบ syntax ในปัจจุบัน
อย่าง function member ก็จะเขียนแบบนี้
(defun member (a x)
(cond ((null x) NIL)
((equal a (car x)) t)
(t (member a (cdr x)))))


สำหรับผม มันอ่านง่ายขึ้นเยอะเลยนะ

Related link from Roti

Thursday, July 13, 2006

George Seurat

ดูแล้วน่าสนุกดี
ที่ Beloit, Wisconsin เขาจัดกิจกรรมถ่ายรูป
เลียนแบบภาพเขียนของ George Seurat
โดยเลือกเลียนแบบภาพที่ชื่อ
Sunday Afternoon on the Island of La Grande Jatte



ชอบตรงหมา เหมือนเชียว

Link

Related link from Roti

Sunday, July 09, 2006

บวรศักดิ์

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

ได้ฟังเรื่องที่เขาเล่าแล้วไม่ชอบอย่างหนึ่ง
ก็คือเรื่องที่ บวรศักดิ์ เป็นคนที่ไม่แคร์กฎเรื่องห้ามสูบบุหรี่เท่าไร
นึกอยากจะสูบก็สูบเลย ไม่ว่าจะอยู่ที่ไหน

เรื่องที่ไม่ชอบใจ ไม่ใช่เรื่องนั้นหรอก
แต่เป็นเรื่องที่ว่า คนที่ไปด้วย มันไม่รู้จักรักษาสิทธิของตัวเองต่างหาก
วัฒนธรรมเชิงอำนาจ ทำให้เราไม่กล้วท้วงติงคนที่เราคิดว่าสูงหรือเก่งกว่าเรา
หึๆ แต่พอจะเข้าใจ คนในระบบราชการทั้งนั้น

รับน้องนี่ก็ตัวดีเลย (ไปเข้าเรื่องนี้ได้ไง)
ประเพณีที่ช่วยเสริมวัฒนธรรมเชิงอำนาจ

Related link from Roti

วันครอบครัว

หลังจากหมกมุ่นกับ project มา 6 อาทิตย์
ผมก็โดยยื่นคำขาดว่า ต้องจัดให้มีวันสำหรับครอบครัว 1 วัน
วันนี้ก็เลยพาลูกและภรรยาไปเที่ยวพิพิธภัณฑืเด็กกัน

ความรู้สึกแรกที่เกิดหลังจากเดินเข้าไป ก็คือ
เฮ้ นี่มันเป็นสถานที่ในฝันสำหรับเด็กๆเลย
มี ทราย, น้ำ, รถ, ฟองสบู่, ของเล่นสารพัดแบบ

เฮ้อ ในที่สุดเมืองไทย ก็มีสถานที่ดีๆให้เด็กสักที
เสียอย่างเดียวคือมันมีจำนวนน้อยเกินไป
แถมยังจำกัดเฉพาะกลุ่มคนที่มีฐานะหรือเวลาพอที่จะพาเด็กมา

ระหว่างที่อยู่ในนั้น ผมก็แอบสังเกตว่าพ่อแม่แต่ละคนเป็นอย่างไรบ้าง
มีที่น่ารำคาญอย่างหนึ่งก็คือ
พ่อแม่ที่ไม่เคารพกฎ
เช่นบริเวณที่ห้ามใส่รองเท้า
ไม่ว่าจะมีป้ายใหญ่แค่ไหน ก็ห้ามพ่อแม่ขี้เกียจถอดรองเท้าไม่ได้
เฮ้อ เราจะคาดหวังอะไรจากเด็กได้หล่ะ
ในเมื่อพ่อปูกับแม่ปูเป็นกันเอง

ส่วนเด็กๆนี่ มีพลังเหลือเฟือจริงๆ
พ่อแม่ทีี่มาด้วย ส่วนใหญ่จะแพ้แรงเด็กๆทั้งนั้น
ภาพที่เห็นก็คือ พ่อแม่นั่งตาลอยมองดูลูกเล่น
หรือไม่ถ้ามากันทั้งพ่อและแม่
ก็จะมีภาพพ่อแอบนั่งหลับ
(เข้าใจนะ นอนน้อยแล้วมาเล่นกับลูกนี่มันง่วงสุดๆเลย)

Related link from Roti

Dragon Sakura

มีอยู่วันหนึ่ง ผมไปนั่งบ่นให้รุ่นพี่ที่ทำงานฟังว่า
ช่วงนี้ผมหมกมุ่นกับโปรเจคมากไป
จนทำให้แฟนน้อยใจว่าไม่มีเวลาให้ครอบครัวแล้ว
พี่เขาก็เลยคิดแผนการให้
โดยการให้ยืม DVD หนังชุดมา
พอแฟนว่างปุ๊บส่ง DVD ให้ดู
รับรองว่าดูเมื่อไร ติดแน่นอน
ที่นี้ก็ไม่มีเวลามานั่งน้อยใจ่ว่าไม่มีเวลาให้แล้ว

วันนี้ได้ฤกษ์เปิด DVD เรื่อง Dragon Sakura
ให้แฟนดู ได้ผลเลย ติดหนึบทันที
หนังมันดีน่ะ
เป็นเรื่องเกี่ยวกับโรงเรียน โรงเรียนหนึ่ง
ที่รวมเด็กเหลือขอไว้
อัตราสอบเข้ามหาวิทยาลับคือ 36 %
เด็กๆที่เรียนที่นี่จะถูกตราหน้า ว่าเป็นเด็กโง่
แถมโรงเรียนก็กำลังจะล้มละลาย
แต่ทนายความที่ได้รับมอบหมายมาทำเรื่องนี้
ก็เกิด Idea ที่จะกอบกู้โรงเรียน
ด้วยการตั้งเป้าว่าจะปีนี้จะมีนักเรียนสอบเข้ามหาวิทยาลับโทได 5 คน
ความสนุกมันอยู่ตรงนี้แหล่ะ



แต่รู้สึก DVD นี้จะได้ผลมากเกินต้องการ
คือแทนที่แฟนผมจะติดคนเดียว
กลับกลายเป็นว่า ผมก็ติดไปด้วยอีกคน

ปล. จากการค้นใน net
ก็พบว่าเรื่องนี้เคยฉายช่อง ITV แล้ว
มีชื่อไทยด้วย "นายซ่าท้าเด็กแนว"
เฮ้อ ชื่อไม่ได้ความเลย

Related link from Roti

Friday, July 07, 2006

Sawfish

ช่วงนี้ใช้ notebook ที่ลง Linux ไว้บ่อยมาก
เวลาใช้ ก็ใช้เป็น routine เลย
เปิด terminal ที่ workspace 1
เปิด eclipse ที่ workspace 2
เปิด emacs ที่ workspace 2
เปิด firefox ที่ workspace 3

ทุกวันตอนเปิดเครื่อง ก็ต้องไล่ start
แล้วก็ไล่วางให้ถูก workspace
ก็เลยเกิดความคิดจะลอง customize ให้มัน start ให้เอง

โปรแกรมที่ลองใช้ก็คือ Sawfish
ซึ่งเป็น Window manager ที่เขียนด้วย Lisp
ทำให้ได้หัดเขียน lisp ไปในตัว

เริ่มด้วยความต้องการอย่างง่ายสุดก่อน
กดครั้งเดียวได้โปรแกรมทุกตัวที่ต้องการ
(defun run-all ()
(start-emacs)
(start-eclipse)
(start-firefox)
(start-term))

(defun start-emacs ()
(system "emacs &"))

...

start มาได้อย่างนี้แล้ว แต่ทุกตัวก็ยังกองกันอยู่ที่ workspace เดียว
ก็เลยเขียน function ขึ้นมาจัด workspace
(defun arrange-ws ()
(move-window-to-workspace (emacs-win) 0 1)
(move-window-to-workspace (eclipse-win) 0 1)
(move-window-to-workspace (firefox-win) 0 2))

(defun emacs-win ()
(get-window-by-name-re "^emacs"))

(defun eclipse-win ()
(get-window-by-name-re ".Eclipse"))

...


จัดการ bind function เข้ากับ short-cut key
ก็เป็นอันเรียบร้อย
(bind-keys global-keymap
"H-a"
`(run-all))


ขั้นถัดไป ก็คือทำให้โปรแกรมมันฉลาดกว่านี้
กดครั้งเดียวพอ ไม่ต้องกด 2 ครั้ง
(เนื่องจากเวลาสั่ง system เราระบุว่าให้ start process
แต่ความที่มันเป็น asyncronous ทำให้เราไม่รู้ว่า process มัน start เสร็จหรือยัง
ทำให้ไม่รู้ว่าควรจะใช้คำสั่ง switch workspace ตอนไหนดี)

Link
Sawfish code library - แหล่งรวม code ให้ลอก

Related link from Roti

Rock Balancing

เคยไปเดินเที่่ยวถ้ำแล้วเจอคนเอาก้อนหินมาวางซ้อนเรียงกัน
ดูเป็นปฏิมากรรมง่ายๆดี

แต่เรื่องวางให้ดู amazing ก็ต้องอันนี้
Rock Balancing Gallery

(ได้ link จาก Signal vs. Noise)

Related link from Roti

Thursday, July 06, 2006

dynamically add methods

เห็นวิธี add dynamic method ของ Prototype แล้วชอบใจดี
var Iterators = function() {
var tags = "div p span ul ol li span form input select textarea h1 h2 h3 h4 h5 h6 dl dt em strong";
var methods = {};
$A(tags.split(' ')).each(function(tag) {
methods["each" + tag.charAt(0).toUpperCase() + tag.substring(1)] = function(element, iterator) {
element = $(element);
element.cleanWhitespace();
$A(element.getElementsByTagName(tag)).each(iterator);
}
});

Element.addMethods(methods);
}();


Writing Custom Iterators For Prototype

Related link from Roti

Wednesday, July 05, 2006

Dojo Custom Widget

ช่วงนี้กำลังลุ่มหลงกับการหาวิธีใช้ Dojo ให้เกิดประโยชน์สูงสุดอยู่
วันนี้เลยทดลองสร้าง widget ง่ายๆเองดู
แน่นอนเริ่มต้น ก็ต้องเริ่มที่ HelloWorld

ขั้นแรก เริ่มด้วยการ define widget ก่อน
dojo.widget.defineWidget(
"pok.Test",
dojo.widget.HtmlWidget,
{ ... }
);

parameter แรกสุด ก็คือชื่อ widget ของเรา
parameter ตัวที่สอง ก็คือ widget ที่เรา extend
ส่วน parameter ตัวที่สาม เป็น object ที่เป็น properties, methods ของ widget เรา
(จริงๆมี parameter อีก 2 ตัว แต่เรา ignore ก่อน)

แน่นอนว่า เมื่อเป็น html widget
widget ของเราก็ต้อง generate html ออกมาด้วย
เราสามารถเลือกวิธี generate html ออกเป็น 2 วิธี (คงมีมากกว่านี้ แต่ตอนนี้รู้แค่นี้)
  • generate จาก template file
  • generate จาก template string


ทดลองทำวิธี template string
โดยต้องการ output แบบนี้ <input type='text' value=''/>
dojo.widget.defineWidget(
"pok.Test",
dojo.widget.HtmlWidget,
{
templateString: "<input type='text' value=''/>"
}
);

เวลานำไปใช้ก็แค่
<body>
<div dojoType="Test"/>
</body>


ทีนี้ ถ้าเราอยากเพิ่ม parameter ให้เรา customize widget เราได้
ก็สามารถทำได้ดังนี้
กรณีนี้เราจะยกตัวอย่างการ customize textbox value
dojo.widget.defineWidget(
"pok.Test",
dojo.widget.HtmlWidget,
{
templateString: "<input type='text' value='${title}'/>",
// default value
title: "hello world",
postMixInProperties: function() {
this.strings = {
title: this.title
}
}
}
);

เห็นได้ว่า เราทำการ map title ที่แสดงใน template ผ่านทาง
method postMixInProperties

ที่นี้เวลานำไปใช้ เราก็สามารถ customize title ได้ดังนี้
<div dojoType="Test" title='hi'/>


concept อันถัดไปก็คือ dojoAttachPoint
ลองเปลี่ยน template เราให้เป็นหน้าตาแบบนี้แทน
    templateString: "<span> <input type='text' value='${title}'/> " +
" <span dojoAttachPoint='labelNode'>xx</span></span>",

dojoAttachPoint ก็คือจุดที่เราสามารถ manipulate DOM ในระหว่างที่ render widget ได้
โดย implement ผ่านทาง hook method ที่ชื่อ fillInTemplate
dojo.widget.defineWidget(
"pok.Test",
dojo.widget.HtmlWidget,
{
templateString: "<span> <input type='text' value='${title}'/> " +
" <span dojoAttachPoint='labelNode'>xx</span></span>",
title: "hello world",
label: "your name here",
postMixInProperties: function() {
this.strings = {
title: this.title
}
},
fillInTemplate: function() {
this.labelNode.innerHTML = this.label;
}

});


ไม่เลยเลย พอเริ่มเข้าใจก็ดูไม่ค่อยยากแล้ว

Related link from Roti

Tuesday, July 04, 2006

Dojo ScriptSrcIO

สืบเนื่องมาจากงาน codefest ที่ติดใจว่ายังทำ Cross Domain javascript ไม่สำเร็จ

ผมเคยเขียนเรื่อง cross domain javascript ไปทีแล้ว
Yahoo Web Services + JSON + Cross domain javascript

วิธีที่เคยเขียนถึงนั้น จริงๆ ก็ไม่ยากเย็นอะไร
แต่มีวิธีที่คิดว่าดีกว่า นั่นก็คือใช้ Dojo
Dojo เตรียม feature dojo.io.ScriptSrcIO ให้เราเรียบร้อยแล้ว
(ซึ่งเดาว่า คงใช้วิธีแบบเดียวกับที่เคยเขียนไปนั่นแหล่ะ เพียงแต่ฉลาดกว่า และทำได้หลายหลายแบบกว่า)
เหลือแต่ว่าหาวิธีใช้มันให้ได้เท่านั้นเอง
วันนี้ผมก็เลยนั่ง งม หาวิธีใช้

หลังจากมั่วอยู่นาน ไม่เกิดผลสักที จนเกือบยอมแพ้ไปแล้ว
google ก็เป็นใจ ส่งตัวอย่างที่ชัดๆมาให้ดูจนได้
หลังจากทดลอง run ตัวอย่าง ก็เลยรู้ว่าท่ีทดสอบไม่สำเร็จ
เป็นเพราะผมใช้ browser ไปเปิด html file จาก filesystem ตรงๆ
โดยไม่ผ่าน web server,
การเปิด file ตรงๆ จะทำให้ dojo ไม่สามารถ solve หา src ที่มันต้องการได้
(เนื่องจากเวลา dojo solve หา src มันจะใช้ xmlhttprequest ในการค้นหา)
UPDATE:
ผิดที่วิธี config ของผมเอง ไม่ได้ผิดที่การเรียกใช้โดยตรงจาก filesystem


ลองดูตัวอย่างเต็มๆ ได้ที่ site ของหนุ่มญี่ปุ่นคนนี้
http://d.hatena.ne.jp/shinichitomita/20060522/1148276164


ที่นี้ลองมาทำความเข้าใจดูบ้าง
เริ่มแรกสุด ก็คือวิธีที่ใช้ dojo.io.bind
dojo.io.bind({
url: 'http://del.icio.us/feeds/json/'+deliciousId,
transport: "ScriptSrcTransport",
jsonParamName: "callback",
load: function(type, data, event, kwArgs) {
renderPosts(data);
}
});

จะเห็นว่า เราเลือกใช้ transport เป็น "ScriptSrcTransport"
เจ้า dojo.io.bind ออกแบบมาให้เราสามารถ switch วิธีการที่ใช้
ติดต่อกับ server ได้
โดยค่า default ของมันก็คือ XMLHTTPTransport
ซึ่งก็คือการใช้ XmlHttpRequest นั่นเอง

ประเด็นถัดมา ก็คือค่า result ที่ได้
พอได้มาแล้ว จะนำมาแสดงอย่างไร
ลองทดสอบนำ url "http://del.icio.us/feeds/json/pokpok" มา run ดู
จะพบว่ามัน return code มาหน้าตาแบบนี้

if(typeof(Delicious) == 'undefined')
Delicious = {};
Delicious.posts = [{"u":"http://sawmill.sourceforge.net/","d":"sawfish: an extensib .... }];

จะเห็นว่าถ้า call ได้สำเร็จ เราจะได้ ตัวแปร Delicious.posts มาใช้
แต่ปัญหาก็คือ เนื่องจาก nature ของการ call แบบนี้ มันเป็น Asyncronous
เราจะรู้ได้อย่างไรว่า Delicious.posts ถูก assign เรียบร้อยแล้ว

พวก json service เห็นปัญหานี้ ก็เลยออกแบบ service ของตนให้ pass ค่า callback ได้ด้วย
ทดลองเรียก
"http://del.icio.us/feeds/json/pokpok?callback=show"
ผลลัพท์ที่ได้จะมีหน้าตาแบบนี้

show([{"u":"http://sawmill.sourceforge.net/","d":"sawfish: an extensib ...."}];);

Note: มีศัพท์ที่เขาใช้เรียกวิธีนี้ว่า JSONP
อ่านรายละเอียดได้ที่นี่ Remote JSON - JSONP


ในการใช้ Dojo ScriptSrcIo
เราสามารถเลือกวิธี callback ได้เหมือนกัน
โดยการใส่ parameter jsonParamName: "callback"
ค่า callback ที่ใส่นี้เป็นชื่อ param ที่ remote service รับนะ
ไม่ใช่ชื่อของ javascript function ที่ต้องการให้ call
ผลลัพท์ที่ได้จาก remote service ก็จะถูกส่งต่อให้
function ที่ระบุไว้ที่ parameter load: function(type, data, event, kwArgs) ทำงาน
โดย data ก็คือ javascript object ที่ service ตอบกลับมา

เพื่อให้แม่นยิ่งขึ้น ผมเลยทดลองเขียน service hello world ด้วย rails ดู
ตัว view หน้าตาแบบนี้
​​
<%= params[:callback] %>({result: "helloworld"})

ส่วน client ที่ call ก็หน้าตาแบบนี้
function test() {
dojo.io.bind({
url: 'http://erp:3000/test',
transport: "ScriptSrcTransport",
jsonParamName: "callback",
load: function(type, data, event, kwArgs) {
alert(data.result);
}
});
}

Related link from Roti

Monday, July 03, 2006

เริ่มเห็นภาพ Monads

วันนี้อ่านเจอ Tutorial ที่พูดถึง IO ของ haskell (ที่เต็มไปด้วย Monads)
แม้จะทนอ่านจนจบไม่ได้ แต่ก็ทำให้เริ่มเห็นภาพ Monads มากยิ่งขึ้น
เริ่มรู้แล้วว่า เหตุผลที่ต้องมี monad ก็เพราะ
  • Haskell is a pure language, which means that the result of any function call is fully determined by its arguments.
    ตามกฎนี้ เราไม่สามารถเขียน function พวก rand() หรือ getchar() ได้
    เพราะกฎของ haskell จะบังคับไว้ว่า ถ้า argument เหมือนเดิม (กรณีนี้คือ void)
    return ที่ได้ ต้องเหมือนกันทุกครั้ง
  • Haskell functions can't have side effects, which means that they can't effect any changes to the "real world"
    ถ้าตามกฎนี้ haskell จะไม่สามารถเปลี่ยนแปลง file content ภายนอก
    หรือไม่สามารถ writing to screen, ฯลฯ

Related link from Roti

ActiveResources

ActiveResources คือ
Feature ใน rails 1.2 ที่ผมชอบมากเป็นพิเศษ
เพราะมันเปิดโอกาศให้ผมใช้ Rails กับ Legacy project ปัจจุบันของผมได้

โดย ActiveResource ก็คือ rest client ที่ช่วยให้เราต่อ
เข้ากับ rest service ที่อยู่บน server ตัวอื่นๆได้ง่ายๆ


Person = ActiveResource::Struct.new do |p|
p.uri "http://www.myhost.com/people"
p.credentials :username => "xxx", :password => "pass"
end
person = Person.find(1)
person.name = "pphetra"
person.save!


เห็นทางสว่าง สำหรับโปรเจค ที่จะ deploy ปลายปีนี้แล้ว
บางส่วนเป็น rails บางส่วนเป็น java

Related link from Roti

จบงาน codefest

เหนื่อยมาก 2 วันกับ project Vehicle Tracking System
ในงาน codefest ครั้งที่ 1
แต่ก็ชื่นใจที่ implement ได้ทััน แม้จะไม่ได้ feature ครบตามที่ตั้งใจไว้
กลับมาบ้าน แทนที่จะได้นอนเร็ว ปรากฎว่าแม่เด็ก ดันติดธุระ
ต้องนั่งถ่างตาเล่านิทาน, ระบายสี, เล่นกับลูก
พอแม่เด็กกลับมา ก็สลบไปเลย

ในส่วนของ erlang, ยิ่งใช้ก็ยิ่งชอบใจ
syntax ที่ simple
บวกกับ standard library ที่มีครบครัน
ทำให้เขียนแล้วรู้สึกสนุก
(จริงๆ ผมไม่ได้เขียนหรอก
เป็น mentor ให้รุ่นน้องเขียน เสียมากกว่า)

จำนวน line of code ที่ได้ ก็น่าสนใจมาก
(อย่าไปนับทั้งหมดที่อยู่ใน cvs นะครับ
เพราะมีพวก experimental อยู่หลายแบบ)
source code ของ server =~ 100 บรรทัด
source code ของ controller (web app) =~ 110 บรรทัด
ตัว html+javascript =~ 300 บรรทัด
test code กับพวกที่ run ครั้งเดียว(setup database) ไม่นับ

Performance ก็ไม่เลยเลย
ลองใช้ jmeter ทำ load testing ดู
โดยใช้ Notebook pentium4 2.4 GHz เป็น server
ยิ่งเข้าไป 500 thread แต่ละ thread ส่งข้อมูล 5 request
ทั้งหมดส่งไปภายใน 1 วินาที โดยกระจายตัวแบบ Gaussian random
ตังค่า assert ไว้ 500 ms.
ปรากฎว่า มีจำนวน request ที่ใช้เวลาเกิน 500 ms.
ทั้งหมด 189 request
คิดเป็น 0.075 %

บรรยากาศในงาน ก็สนุกดี
มีกล้องค่อยถ่ายไปเรื่อยๆ
ได้บรรยากาศเหมือนอยู่ใน reality show

ช่วงแรกๆที่เริ่ทำงานยังรู้สึกเสียวอยู่ กลัวทำไม่ทัน
คิดในใจ "รู้งี้แอบเตรียมเขียน code มาก่อนดีกว่า"
แต่พอตกเย็น ส่วน server เริ่มลงตัว
ก็รู้สึกผ่อนคลาย เพราะรู้แล้วว่าเสร็จทันแน่

Related link from Roti

Wednesday, June 28, 2006

Rails, Enhance Symbol

ใน revision 4455 มีการเพิ่ม method to_proc ใน class Symbol
ส่งผลให้เราสามารถทำแบบนี้
orders.collect(&:total)

ซึ่งถ้าเป็นแบบเดิมๆ เราจะต้องเขียนแบบนี้
orders.collect {|o| o.total}


เอาไป apply ได้อื้อเลย
orders.sum(&:total)

people.select(&:manager?).collect(&:salary)


ชอบ ruby+rails ตรงนี้แหล่ะ มันเปิดให้ใช้ความคิดสร้้างสรรค์ได้เต็มที่

Related link from Roti

Charles Reid

ผมชอบรูป Nude ที่ใช้สีน้ำนะ
Charles Reid @ Stremmel Gallery



Related link from Roti

Standpedia


Standpedia
ไม่รู้เห็นกันหรือยัง

เหมาะแก่การใช้ประกอบการถกเถียงเป็นอย่างยิ่ง
เส้นยึกยัก คือ คำค้าน (challenging Reason)
เส้นตรง คือ ความเห็น หรือข้อความสนับสนุน (Supporting Reason)

GotoKnow น่าเอาไปทำบ้างนะ

Related link from Roti

Erlang, ลองใช้ Mnesia

Mnesia คือ database ที่เขียนด้วย erlang เพื่อ erlang
เนื่องจากเขียนด้วย erlang ,ประเด็นปัญหาเรื่อง impedance mismatch
ระหว่าง data format ของ DBMS กับ data format ของ programming language
ก็เลยไม่มี (impedance mismatch ก็คือที่มาของ Oิbject relation mapping)
feature ที่ชอบก็คือ
  • Tables can be moved or replicated to several nodes to improve fault tolerance.
  • Table locations are transparent to the programmer.


โปรแกรม Mnesia นั้น bundle มาพร้อมกับ erlang เลย
สามารถใช้งานได้ทันที ไม่ต้องติดตั้งเพิ่มเติม

การสร้าง table ใน Mnesia เริ่มต้นด้วยการ define Record Datatype
ที่จะใช้เก็บข้อมูล
-record(employee,{emp_no, 
name,
salary,
sex,
phone,
room_no}).


การสร้าง table ก็ใช้คำสั่ง
init() ->
mnesia:create_table(employee,
[{attributes, record_info(fields, employee)}]).

เวลาจะ insert ก็ใช้คำสั่ง
insert(Emp_no, Name, Salary, Sex, Phone, Room_no) ->
insert(#employee{emp_no = Emp_no,
name = Name,
salary = Salary,
sex = Sex,
phone = Phone,
room_no = Room_no}).

insert(Emp) ->
Fun = fun() ->
mnesia:write(Emp)
end,
mnesia:transaction(Fun).

Note: #employee{key = value,...} เทียบได้กับ constructor ของ Record

เวลา select ก็มีอยู่ 2 แบบ
พวก select กับ read
ลองแบบ read ก่อน (read ทั้ง record)
get(Emp) ->
Fun = fun() ->
mnesia:read({employee, Emp})
end,
mnesia:transaction(Fun).

การใช้งาน ก็เช่น
30> dbtest:get(111).
{atomic,[{employee,111,pok,2000.00,male,xxxx,412}]}

Note: read คือการดึงข้อมูลโดยใช้ key, โดยมี assumption ว่า element แรกใน record เป็น key

ส่วน select ก็ใช้ดังนี้
all_males() ->
Fun = fun() ->
Male = #employee{sex = male, name = '$1', _ = '_'},
mnesia:select(employee, [{Male, [], ['$1']}])
end,
mnesia:transaction(Fun).

Note: $1 ใช้เป็นตัวเชื่อมระหว่าง record member ที่ต้องการนำมาแสดงผลใน result list

ส่วนการ update นั้นยุ่งขึ้นมานิดหนึ่ง
เพราะ variable ใน erlang assign แล้วห้ามแก้ (immutable)
raise(Eno, Raise) -> 
F = fun() ->
[E] = mnesia:read(employee, Eno, write),
Salary = E#employee.salary + Raise,
New = E#employee{salary = Salary},
mnesia:write(New)
end,
mnesia:transaction(F).

Related link from Roti

Tuesday, June 27, 2006

Kenichi Koshine


Kenichi Koshine

Related link from Roti

Monday, June 26, 2006

เก็บตก Rails

By our best estimates, over 90% of the 550 people at RailsConf 2006 are using Apple laptops

เขาเลยมีการแจก certificate ให้กับคนที่ไม่ใช้ apple (stand against the norm)


Ruby/Rails works for Kiosks Our kiosk project really impressed a lot of people. We designed it from top to bottom with all Ruby software on Gentoo Linux. Working in Ruby really helped the integration with USB devices such as bill validator and coin readers. The fancy UI was done entirely with Scriptaculous, running on a fullscreen-mode instance of Firefox.

Related link from Roti

playboy centerfold

ลองดูวิวัฒนาการของ photographs บน centerfold ใน playboy magazine
ตั้งแต่ปี 1960 - 1990
PlayboyDecades

Related link from Roti

Sunday, June 25, 2006

GotoKnow version ใหม่ implement ด้วย rails

ไม่ได้เข้า GotoKnow นานแล้ว
เห็นแว็บๆว่าเปลี่ยน version แต่ไม่ได้สังเกตอะไรเพิ่มเติม

วันนี้พึ่งรู้ว่าเปลี่ยนไปเป็น implement ด้วย Rails แล้ว
(ของเดิมใช้ python)
source code อยู่ที่ KnowledgeVolution.org
เลยถือโอกาสเข้าไป scan ดู code หน่อย
(อาจารย์ธวัชชัย ที่เป็นคนเขียน เขาเก่งอยู่ เพื่อจะได้ trick อะไรติดมือกลับมา)

  • plugin ที่ใช้ เห็นมี Exception Notifier Plugin
    ใช้สำหรับส่ง mail เตือน กรณีที่เกิด error ขึ้นใน app
  • deployment เห็นว่าใช้ capistrano ด้วย
  • ส่วน Localization ใช้ GetText ทำ
  • มี patch เรื่องเกี่่ยวกับ Date ใน http://knowledgevolution.org/browser/trunk/lib/overrides.rb
    ถ้าเราดูใน source file ของ ruby
    # === Doesn't depend on strftime
    #
    # This library doesn't use +strftime+. Especially #rfc2822 doesn't depend
    # on +strftime+ because:
    #
    # * %a and %b are locale sensitive
    #
    # Since they are locale sensitive, they may be replaced to
    # invalid weekday/month name in some locales.

    เห็นใน code ใช้วิธีแปะเดือน กับวัน จาก Constants Array ใน Date ทับลงไป
    โดย string ชุดนี้ถูก replace จาก application.rb (controller) บรรทัดที่ 33 ซึ่งใช้ gettext แปลงตาม locale อีกที
  • มีการใช้ rmagick ช่วย generate background image ตาม theme ด้วย
    generate_images.rb
  • ใช้ Rmagick generate รูปที่ใช้ถามเวลา comment เพื่อป้องกัน spam
    generate.rb
    โดย generate ไว้ตั้งแต่ต้นเลย เพื่อไม่ให้เสียเวลา generate ตอนที่ request วิ่งเข้ามา
  • changeset ล่าสุด เห็นมีการใช้ thread มา expire_fragment
  • ส่วนเรื่อง Testing เห็นมี unit test ที่ user model กับ mailer test
    (ผมก็เหมือนกัน ถ้า app มันตรงไปตรงมา บางที ก็ขี้เกียจ test
    แต่ก็พยายามหัดอยู่)

Note: source code เขาใช้ tab = 8 space ดูสะอาดตาดี

Related link from Roti

Erlang 101 ตอน 3

วันนี้จะลองเขียน Server Process ดูบ้าง
เริ่มต้นง่ายๆที่ Echo Server

เริ่มต้นด้วยการประกาศ module
-module(echo).
-export([start/0,loop/1]).

function start จะทำหน้าที่ spawn process ขึ้นมาใหม่
แล้วทำการ register process เข้ากับชื่อ "echo"
(การ register จะช่วยให้ client สามารถส่ง message มาให้ server ได้ง่ายขึ้น
แทนที่จะต้องรู้ว่า server มี process id เป็นอะไร
ก็ให้ระบุชื่อ server แทน)
start() ->
register(echo,
spawn(echo, loop, [nil])).

ในส่วนของ function loop ก็คือ loop ที่เราจะรอรับ message จาก client
ให้สังเกตว่ากรณี message ประเภท echo พอตอบ message กลับไปแล้ว
จะมีการ recursive เรียกใช้ loop อีกครั้ง
ส่วนกรณี stop จะไม่มีการเรียก loop ซ้ำ ซึ่งก็หมายความ process จะ terminate
loop(State) ->
receive
{From, {echo, Msg}} ->
From ! {ok, Msg},
loop(State);
{From, {stop}} ->
true
end.

ใน echo server เราสนใจ message อยู่ 2 แบบคือ
  • {From, {echo, Msg}}
    From ก็คือ process id ของ client
    จะได้ส่งข้อมูลกลับไปให้ client ได้
    echo ขึ้นต้นด้วยตัวเล็ก แสดงว่ามันเป็น atom หรือ Constants แบบหนึ่ง
    Msg คือ ข้อความที่ user ส่งมา
  • {From, {stop}}


เวลาใช้งาน ก็จะทำดังนี้
Erlang (BEAM) emulator version 5.4.13 [source] [hipe]

Eshell V5.4.13 (abort with ^G)
1> c("/Users/pphetra/projects/erlang/echo2/src/echo", [{outdir, "/Users/pphetra/projects/erlang/echo2/src/"}]).
/Users/pphetra/projects/erlang/echo2/src/echo.erl:13: Warning: variable 'From' is unused
{ok,echo}
2> echo:start().
true
3> echo ! {self(), {echo, "helloworld"}}.
{<0.30.0>,{echo,"helloworld"}}
4> echo ! {self(), {stop}}.
{<0.30.0>,{stop}}
5>

  • <0.30.0> คือเลข pid ของ echo server
  • self() คือ function ที่ return pid ของ process ที่เรียกคำสั่งนี้


ใน erlang มี concept ที่เรียกว่า Behavior
ซึ่งก็คือการดึงเอา pattern ที่ซ้ำๆกันออกมาเป็น code ต่างหาก
แล้วเปิดให้เราเขียน code ที่ implement behavior เหล่านี้ได้

erlang เตรียม behavior ของ server ไว้แล้ว
โดยมีชื่อว่า gen_server
ลองดูว่า echo server implement behavior นี้แล้ว code จะเปลี่ยนโฉมไปยังไงบ้าง

เริ่มต้นด้วย การประกาศ behavior(gen_server)
-module(echo).
-behavior(gen_server).
-export([handle_call/3,start/0,init/1,terminate/2]).

gen_server บังคับให้เรา implement callback function ที่ชื่อ init
ซึ่งกรณีของเรา ไม่ได้มีการ allocate resources อะไร
ก็เลยแค่ return ok พร้อม state [] ว่างๆกลับไป

start() ->
gen_server:start_link({local, echo}, echo, [], []).

init(_Arg) ->
{ok, []}.

ตัว handle_call คือ callback ที่ใช้รองรับ syncronous call (พวกต้องการรับค่ากลับ)
จะเห็นว่าเรา return กลับเป็น {reply, Msg}
ซึ่ง gen_server จะส่ง message กลับไปให้เอง
ส่วน function terminate ก็เป็น callback ที่ gen_server call
เมื่อพบ message {stop, normal, ..}
ซึ่งในการทำงานจริง ตรงนี้จะเป็นการคืนค่าทรัพยากรที่จองไว้ กลับคืนสู่ระบบ
handle_call(Msg, _From, State) ->
case Msg of
{echo, Text} ->
{reply, {ok, Text}, State};
{stop} ->
{stop, normal, State}
end.

terminate(normal, _) ->
ok.


ดูแล้ว มันเทอะทะขึ้นอีกนิด แต่ว่าผลประโยชน์ที่ได้จากการ implement gen_server behavior ก็คือ
เราสามารถใช้ pattern supervisor มาช่วย monitor server เรา
ถ้ามีการตาย หรือ terminate แบบผิดธรรมชาติ เช่นมี bug หรือ IO มีปัญหา
supervisor สามารถ restart server ให้เราอัติโนมัติได้
ซึ่งช่วยเพิ่ม reliability ให้กับระบบเรา

Related link from Roti