Tuesday, May 09, 2006

Hibernate, Spring, open session in view

ถ้าใครใช้ Hibernate ใน web application มักจะพบเจอปัญหา LazyInitialize ทุกราย
ซึ่ง case classic ก็คือ
ใน data access layer มีการเปิด session และทำการ query ข้อมูล
จากนั้นก็ปิด session แล้ว return ข้อมูลกลับไปยัง UI
ซึ่งพอ jsp ทำการ render ข้อมูล
และเกิดไปแตะข้อมูลที่ยังไม่ได้ initialize
ก็จะเกิด LazyInitializeException ขึ้น
(แต่ถ้าเกิดการแตะข้อมูลที่ยังไม่ initialize ในขณะที่ session ยังเปิดค้างไว้อยู่,
Hibernate จะ initialize ข้อมูลให้โดยอัตโนมัต)

ทางแก้ทางหนึ่งก็คือโอนความรับผิดชอบในการเปิดปิด session
ไปให้กับ Servlet Filter เสีย

Spring ได้เตรียม Filter ที่ทำหน้าที่แบบนี้ไว้ให้แล้ว
ชื่อว่า OpenSessionInViewFilter

การ config ก็แค่เพิ่ม block นี้ลงใน web.xml
<filter> 
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate.support.OpenSessionInViewFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

การทำงานภายในของ OpenSessionInView ก็คือ
เมื่อเกิด request เข้ามา
มันจะทำการค้นหา SessionFactory จากใน Spring Context
โดยมันจะตั้ง Assumption ว่า มันจะค้นหา SessionFactory
จาก Bean ที่มีชื่อว่า sessionFactory
public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";

protected SessionFactory lookupSessionFactory() {

WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
return (SessionFactory) wac.getBean(getSessionFactoryBeanName(), SessionFactory.class);
}

จากนั้น มันก็จะทำการ สร้าง session และ bind session โดยเรียกใช้
TransactionSynchronizationManager.bindResource
ซึ่งจะทำการ put ค่า session นั้นเก็บไว้ใน ThreadLocal
session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory,
new SessionHolder(session));

doFilter ...

TransactionSynchronizationManager.unbindResource(sessionFactory);


ที่นี้บางคนก็จะมีคำถามว่า แล้วเวลาเรียกใช้ session หล่ะ
มันจะเกิดอะไรขึ้น
ใน spring, เรามักใช้ HibernateTemplate เข้้ามาช่วยอยู่แล้ว
ซึ่ง HibernateTemplate เอง มันจะเรียกเปิด openSession
ซึ่งภายใน จะ delegate ต่อไปยัง SessionFactoryUtils
protected Session getSession() {

.. //another case

return SessionFactoryUtils.getSession(
getSessionFactory(),
getEntityInterceptor(),
getJdbcExceptionTranslator());

}

ถ้าตามไปดู code ใน SessionFactoryUtils ก็จะเห็นว่า
มีการใช้ TransactionSynchronizationManager
เข้าไปดึงค่า SessionHolder ที่ OpenSessionInView เป็นคนสร้าง
และเก็บไว้ใน ThreadLocal
private static Session getSession(
SessionFactory sessionFactory, Interceptor entityInterceptor,
SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)
throws DataAccessResourceFailureException, IllegalStateException {

SessionHolder sessionHolder = (SessionHolder)
TransactionSynchronizationManager.getResource(sessionFactory);

... // too much, just pretend that it not exist

return sessionHolder.getSession();

... // case for กรณีไม่ได้ใช้ filter
}


ถ้าสรุปง่ายๆ (ลอกจาก Configuring Hibernate, Spring and (MyFaces) JSF)
  • Sets up the Servlet filter that manages the Hibernate session
  • Associates a Hibernate session with the current thread assigned to the current request
  • Classes that use HibernateTemplate will access the same session by default
  • HibernateTemplate uses SessionFactoryUtils.getSession
  • SessionFactoryUtils.getSession uses existing Hibernate Session associated with the current thread

ง่ายๆเลย ไม่ต้องไปใล่ดู code ให้เสียเวลา

Related link from Roti

Monday, May 08, 2006

viminfo

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

If you exit Vim and later start it again, you would normally lose a lot of
information. The viminfo file can be used to remember that information, which
enables you to continue where you left off.


เนื้อหาใน file viminfo แบ่งออกเป็น
  • Last Search Pattern
    มีแค่รายการเดียวเสมอ เก็บเฉพาะ search แบบที่ใช้ pattern
  • Search String History
  • Command Line History
  • Expressoin History
  • Input Line History
  • Registers
  • File marks
    ตรงนี้น่าสนใจ ตรงที่เราสามารถ jump ไปตาม file+position
    ที่เราเคย edit ได้
    โดย vim จะ assign register 0-9 ให้กับ file+position เหล่านี้
    0 -> file+position ล่าสุด
    เราสามารถใช้คำสั่ง vim -c "normal '0"
    เพื่อเปิด file และกระโดดไปตำแหน่งล่าสุดที่เคยเปิดได้
  • Jumplist
    เก็บไว้เยอะแยะไปหมด อืมม์เวลาใช้มันจะจำอะไรได้บ้างเนี่ย
  • History of marks within files
    เก็บทุกๆ file ที่เคย edit เลย
    อันที่ทำให้ช้า ก็น่าจะเป็นอันท้ายนี่แหล่ะ

Related link from Roti

Friday, May 05, 2006

อยากดู



หนังเรื่องนี้จะหลุดเข้ามาเมืองไทยเหลือเปล่านี่
SKETCHES OF FRANK GEHRY
น่าดูมาก

ผมชอบดูพวกสารดคีที่แสดง process การสร้างสรรค์
ที่เคยดูแล้วชอบก็มี
สารดดี I.M. Pei ที่เอามาออกในช่อง discovery ของ cable TV.
ได้มีโอกาสชม Lourve แบบไม่ต้องบินไปปารีส
อีกเรื่องก็คือสารดดี แสดง process การวาดรูปของ picasso
เลยรู้ว่ารูปหนึ่งๆของ picaasso ก่อนจะออกมาได้นั้น
ผ่านการเขียนทับหรือ transform เยอะมาก

Related link from Roti

Server Push

ช่วงนี้กำลังหา solution เพื่อที่จะทำ Server Push อยู่
โจทย์ตั้งไว้ว่า user จะใช้ browser ในการ monitor เหตุการณ์
ซึ่งต้องการ respone time ที่เร็วพอสมควร

วิธี polling, refresh browser ทุก x วินาที นี่ตัดทิ้งไปก่อนเลย
คราวนี้ก็เลยต้องชั่งน้ำหนักอยู่ว่าจะใช้วิธีไหนดี

วิธีที่ implement ได้ทันทีเลย ก็คือใช้พวก Applet
เปิด socket ไปยัง server
ถ้าให้หรูหน่อย ก็ใช้ applet ต่อเป็น JMS Client
ง่ายขึ้นอีกหน่อย ก็ใช้ Jabber protocol

แต่ผมดันเกิดอยากลองใช้ javascript client ล้วนๆดูบ้าง
ก็เลยต้องลำบากเพิ่มขึ้นหน่อย

เริ่มด้วยวิธี primitive สุด ก็คือ เมื่อ browser
request มาขอข้อมูลแล้ว เมื่อส่งข้อมูลเสร็จ แทนที่ server จะสั่งปิด connection เลย
ก็ให้ server วน loop รอ
เมื่อไรที่เกิด event ที่ server, ก็ค่อย write stream สั่ง flush
จากนั้นก็เข้า loop wait รอต่อไป
โดยวิธีนี้ implement ได้ 2 วิธี ก็คือ
implement ตรงๆ ตอน browser get เลย
กับอีกวิธีหนึ่งก็เอาไปฝากผ่านทาง iframe
แต่ไม่ว่าใช้ทางใด ข้อเสียที่เห็นได้ชัดก็คือ
browser จะแสดง icon หมุนๆๆๆ แสดง status ว่ายังได้รับข้อมูลจาก server ไม่หมด
ซึ่งการแสดง status อย่างนี้ น่าจะสร้างความสับสนให้ user

วิธีถัดไปก็คือให้ XMLHttpRequest
แน่นอนว่าช่วยแก้ปัญหา icon หมุนๆๆๆ ได้
แต่ประเด็นปัญหาของวิธีนี้ ก็คือ จะต้องหาทางทำให้ XMLHttpRequest
รับ response เป็นส่วนๆ(chunk) ให้ได้
ซึ่งในกรณีของ Firefox เราสามารถใช้ multipart/x-mixed-replace
เข้ามาช่วยได้
แน่นอนว่าวิธีนี้ IE ไม่ support
แต่ใน IE ก็มี component หนึ่งที่ช่วย support วิธีนี้เหมือนกัน
เรียกว่า ondataavailable - MSXML 4.0

ดูแล้วถ้าต้องทำให้ support หลายๆ browser
วิธี chunk ก็ดูเป็นการทรมานจิตใจเกินไป

เมื่อ chunk ไม่ได้ ก็เลยมีคนคิดวิธีว่า
ก็ให้เปิด XMLHttpRequest ไปที่ server
โดยกรณีที่ server มี event ที่จะ push อยู่แล้วก็จะตอบกลับมาเลย
แต่ถ้ายังไม่มี server ก็จะ loop รอก่อน
ถ้าครบเวลาที่กำหนดแล้วยังไม่มี event มา
server ก็จะ return กลับมา
ในส่วนของ client เมื่อได้รับ return message จาก server (ไม่ว่าจะมี event หรือไม่มี event มา)
client ก็จะ request เปิด XMLHttpRequest ซ้ำกลับไปอีกทันที
เนื่องจาก request เกิดอย่างต่อเนื่อง
socket connection ก็เลยถูกเปิดแช่ไว้ เป็น persistent connection
บางคนเรียกวิธีนี้ว่า waiting poll mechanism
วิธีนี้ ค่อนข้างมีคนนิยมกันมาก ไม่ว่าจะเป็นใน
DWR 2.0, ActiveMQ Ajax, ฯลฯ

วิธีถัดไปก็คือ ใช้ AFLAX
วิธีนี้แอบใช้ feature ใน Flash 8
มีข้อเสียนิดหน่อยตรงที่
browser ต้อง install flash 8 ด้วย
กับเอกสารที่อธิบายน้อยไปหน่อย

ตอนนี้ทดลองทดสอบ ไป 2 วิธีแล้ว
ก็คือ primitive กับ DWR
ส่วน multipart ยังติดปัญหาที่ servlet มัน overwrite content-type ที่ผม set ไว้
ก็เลยขี้เกียจดูต่อ
เหลือวิธี aflax กับ ActiveMQ Ajax ที่จะลองทำพรุ่งนี้

Related link from Roti

Alex Kanevsky

Alex Kanevsky
งาน Painting ของเขาสวยดี
ผมชอบความรู้สึกมัวๆ ที่ดูไม่ชัดเจน
รวมทั้งโทนสีที่เขาใช้

Related link from Roti

Wednesday, May 03, 2006

CSS Image

ลองเข้าไปดู Link นี้ครับ ProofOfConcept
ดูภายนอกก็ดูปกติดี
ลอง click ขวาเพื่อดู source code สิครับ
อา น่าตื่นเต้นขึ้นมาหน่อย
มี div tag เต็มไปหมดเลย
<div style="top: 0pt; left: 0pt;" class="c0"> </div>
<div style="top: 0pt; left: 3px;" class="c1"> </div>
<div style="top: 0pt; left: 6px;" class="c1"> </div>
...

สังเกตว่า ตำแหน่งว่า ขยับไปทีละ 3 pixel

ที่นี้ลองเปิดดู css ที่แนบมา
.c0 {
border-left:1px solid #CCCC99;
background:#CCCC99;
border-right:1px solid #CCCC99;
}
.c1 {
border-left:1px solid #CCCC99;
background:#CCCC99;
border-right:1px solid #CCCC99;
}
.c2{
border-left:1px solid #CCCC99;
background:#BEBE8F;
border-right:1px solid #363629;
}

div มีขนาด 3x1 pixel และมีการกำหนด สีให้กับแต่ละ pixel

รวมๆกันแล้ว มันก็คือ รูปที่เขียนขึ้นโดยใช้ CSS เพียวๆ
(ไม่ได้ใช้ background image)

อ่านเนื้อหาเต็มๆได้ที่นี่ครับ


แล้วก็มีคนนำไปต่อยอดเป็น CSS Font แล้วครับ
ดูได้ที่นี่ A CSS Font

Related link from Roti

Links - Programming Language for Web application

เมื่อวันก่อนอ่านเจอ paper Links
คำอธิบายย่อๆ ก็คือ
Link คือ Programming Language ที่ออกแบบมาสำหรับการเขียน Web application
โดยเขามองว่า เดิมที่เราเขียน web application นั้น
เราต้องใช้อย่างน้อย 3 ภาษา ก็คือ sql สำหรับ access database
, javascript + HTML สำหรับส่วน UI
สุดท้ายก็ programming language สำหรับส่วน
business logic ซึ่งอาจจะเป็น c, java, python ฯลฯ
ซึ่งคนที่จะเขียน Web Application ต้องใช้เวลาในการเรียนรู้ทั้ง 3 ส่วน
และยังเกิดปัญหาที่เรียกว่า impedance mismatch
(ข้อมูลจาก sql แปลงให้อยู่ในรูป object เพื่อให้เหมาะกับ java
จากนั้นเมื่อจะ render ก็แปลงกลับมาอยู่ในรูปตาราง html )

Links ก็เลยเกิดมา เพื่อเป็น unify language สำหรับ
การเขียน Web application

ลองดูตัวอย่าง program เขาดู จะได้เห็นภาพ
การทำงานของโปรแกรมนี้ ก็คือ
เริ่มต้นมันจะแสดง form ให้เราใส่ตัวเลขที่ต้องการ
เมื่อสั่ง submit ก็จะแสดงผลเป็นตาราง factorial ตามจำนวนที่เราใส่

fun request(s) client {
dom!Document(
<html>
<body>
<h1>Please type a number</h1>
<form l:onsubmit="{response(t)}" l:onkeyup="{request(t)}">
<input type="text" value="{s}" l:name="t"/>
{
if (is_integer(s))
<input type="submit"/>
else
<input type="submit" disabled="disabled"/>
}
</form>
</body>
</html>
)
}

fun response(s) client {
var n = int_of_string(s);
dom!Document(
<html>
<body>
<h1>Factorials up to {enxml(string_of_int(n))}</h1>
<table>{
for ((i,f) <- factorialsUpto(n))
<tr>
<td>{enxml(string_of_int(i))}</td>
<td>{enxml(string_of_int(f))}</td>
</tr>
}</table>
</body>
</html>
)
}

fun factorialsUpto(n) server {
for (row <- Table "factorials"
with { i : Int, f : Int }
order [i : asc]
from database "postgresql:factorials::5432:www-data:")
where (row.i <= n)
[(row.i,row.f)]
}

request("")

จะเห็นว่า ตัวภาษามีลักษณะเป็น functional language
และ function แต่ละ function จะต้องกำหนดว่า
run อยู่บน client หรือ run บน server
ประเด็นที่น่าสนใจ ก็คือ
  • จะเห็นว่า function สามารถ call ข้าม space กันได้ (client -> server)
    โดย technique ที่เขาใช้ implement สำหรับการ call แบบนี้ก็คือ
    XMLHttpRequest (Ajax)
  • html integrate เข้าเป็นเนื้อเดียวกับ language
  • Style โปรแกรมมีลักษณะเป็น Continuations
    (ผมกำลังหัดเขียน Web Application ด้วย PLT Scheme อยู่
    style การเขียนเหมือนกันมากเลย)



ทดลอง run demo ของ Links ได้ที่นี่ Links demos

Related link from Roti

Rails - Project แรก

ที่บริษัทฯผม มีนโยบายไม่รับงานเล็ก (หยิ่งจริงๆ)
เดือนร้อนถึงผมที่อยากจะทดสอบ Rails
ไม่สามารถหา project จริงๆมาลองได้

โชคดีที่บริษัทน้องชาย(สถาปนิก) กำลังขยายงานจากงาน design
(SIPA ของ mk ก็ใช้บริการเหมือนกันนะ)
ออกไปทำงานด้าน web design
แล้วบังเอิญได้งานที่ค่อนข้าง dynamic มา
ผมก็เลยโชคดีได้เหยื่อผู้โชคร้าย
(ส่วนน้องชายเข็ดไปแล้ว เจอค่าตัว programmer เข้าไป)



ผลลัพท์ที่ได้จากการทดสอบ ก็คือ
rails ทำให้งาน web ที่น่าเบื่อ สนุกขึ้นมาได้
(งานนี้ ถ้าให้เขียนด้วย php หรือ java นี่ คงต้องขอคิดดูก่อน)

ส่วนเวลาที่ใช้ coding ก็ประมาณอาทิตย์ละ 1 วัน
ทั้งหมด 4 อาทิตย์
ถ้าเป็นเรื่อง logic นี่ไม่ถึงวันก็เสร็จ
แต่ที่ใช้เวลานาน ก็มีอยู่ 2 เรื่อง
  • เรื่องความสวยงาม
    code ที่ได้จาก designer อยู่ในรูป table layout
    ก็พยายามเปลี่ยนบางส่วนให้เป็น CSS แทน
  • นั่งทดลองทำวิธีต่างๆ
    เนื่องจากเป็น project เล็ก
    ก็เลยทำให้ refactor หรือเปลี่ยนวิธี implement ได้ง่ายๆ

อันนี้คือ สถิติของ project นี้



code 1000 กว่าบรรทัดนี่ เป็น generate code ซะไม่รู้เท่าไร
น่าจะเกิน 80% นะ

ตอนนี้ก็เหลือแต่ deploy ขึ้น host จริง
ซึ่งต้องไปวัดดวงว่า จะเจอ Host ดีหรือไม่ดี

Related link from Roti

Tuesday, May 02, 2006

รำลึกความหลัง

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

ยังจำ การดำน้ำครั้งแรกที่สิมิลัน ได้
เขาปล่อยให้ดำหน้าเกาะ 4 เพื่อให้ซ้อมความคุ้นเคยก่อน
กำหนดความลึกไม่เกิน 30 feet
ผมกับ buddy ผู้อ่อนต่อโลก
อ่านเข็มความลึก ไม่เกิน 30 เหมือนกัน
แต่เป็น 30 metre
ดีนะที่รู้สึกว่ามันลึก เลยอยู่แถว 25 metre
แต่ก็คุ้ม เพราะได้พบเจอกระเบนยักษ์

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

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

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

Related link from Roti

Sunday, April 30, 2006

[Lisp] car, cdr, cons

car, cdr, cons เป็นคำสั่งพื้นฐานใน Lisp
เริ่มที่ cons ก่อน
cons จะทำหน้าที่สร้าง pair ขึ้นมา
ตัวอย่าง
(cons 1 2) ;; => (1 . 2)

ที่สนุกก็คือ list ก็คือ pair แบบหนึ่ง
(1 2) จริงแล้ว ก็คือ (1 . (2 . nil))
(1 2 3) ก็คือ (1 . (2 . (3 . nil)))

ส่วน car ใช้สำหรับ extract element แรกใน pair
(car (cons 1 2)) ;; => 1

กลับกัน cdr ใช้สำหรับ extract element ที่สอง

(cdr (cons 1 2)) ;; => 2


แต่ที่น่าสนใจคืออันนี้
(ไปเจอใน blog ของ archie maskill
เรื่อง Blurring the Line Between Data and Procedures
)
คำถาม ก็คือ ถ้าเราจะ implement cons, car, cdr ด้วย ภาษา lisp เอง
เราจะเขียนมันอย่างไร
(ตัวอย่างนี้ใช้ dialect scheme)
(define (cons a b)
(lambda (pick)
(cond ((= pick 1) a)
((= pick 2) b))))

(define (car x) (x 1))
(define (cdr x) (x 2))

ดู code แล้ว คนที่ไม่คุ้นกับ functional language ก็จะเริ่มงงนิดๆ
ตรงที่การเรียกใช้ cons a b จะได้ function กลับมา
ส่วน car กับ cde ก็เป็นการเรียกใช้ lambda นั้นๆ

แต่ที่ดูแล้ว ต้องปรับตาปรับใจนานหน่อย ก็ต้องแบบนี้
(define (cons x y)
(lambda (m) (m x y)))

(define (car z)
(z (lambda (p q) p)))

ถ้าค่อยๆ ดู ก็จะเห็นว่า cons return เป็น function ที่รอรับ parameter เป็น function
จากนั้นการเรียกใช้ car ก็คือการ call function ที่ได้จาก cons ด้วย function (lambda (p q) p)

Related link from Roti

เมื่อ Rails พบ Asterisk

Ruby Asterisk Gateway Interface (RAGI)
คือ Library ที่ทำให้ Ruby code สามารถทำงานร่วมกับ Asterisk server ได้
ซึ่งแน่นอนว่า เมื่อ ruby คุยกับ Asterisk ได้
rails ก็ย่อยได้อานิสงค์ไปด้วย

RAGI เป็น opensource ของ SNAPVINE, Inc
มี license เป็น BSD

Tutorial ของ RAGI เขาทดลองทำ Application ที่ใช้ track UPS package
ที่มี use case อย่างนี้ดู
  • user ลงทะเบียนการใช้งานผ่านทาง web application
  • user ระบุ UPS package id ที่ต้องการ track
  • หลังจากลงทะเบียนและระบุ package id แล้ว user ก็สามารถ
    โทรเข้ามาสอบถาม สถานะของ package ได้


ลองดูตัวอย่าง code ในส่วนของ rails ที่ทำหน้าที่ตอบรับโทรศัพท์
class DeliveryStatusHandler < RAGI::CallHandler 
def dialup
answer()

# read the caller id from the connection
user_phone_number = @params[RAGI::CALLERID]

user = User.find_by_phone_number(user_phone_number)
if (user == nil)
speak_text("We could not locate any accounts. Please try again later")
hangUp()
return
end

deliverylist = user.deliveries

speak_text("You have #{deliverylist.size} deliveries to track")

if deliverylist.size > 0
updateAll(deliverylist)
readDeliveryStatus(deliverylist)
else
speak_text("Please go to the website and register some deliveries.")
end

speak_text("Goodbye!")
hangUp()

end
end


อ่านเพิ่มเติม

Related link from Roti

ทำความเข้าใจ Ant Task

Ant เป็นอะไรที่ผมหลีกเลี่ยง ไม่ค่อยอยากเข้าไปทำความรู้จักมากนัก
เวลาจะทำอะไรที ก็เปิดตัวอย่างดูอย่างเดียว จากนั้นก็ เดาๆ มั่วๆไปเรื่อยๆ จนกว่าจะทำงานได้
เมื่อวานมีคนถามเรื่องการใช้งาน war task
กรณีที่มี directory ที่เก็บ lib แยกอยู่หลายที่
จะเขียนอย่างไร

ตัวอย่างโปรเจค structure ของ case นี้ก่อน

+ project
+ src
+ bin
+ lib
+ hibernate
+ jdbc
+ doclet
+ dbunit
+ context (www root dir)

ผมก็ใช้วิธีเอาตัวรอดวิธีเดิม มั่วไป 2-3 ที มันก็ work
<war destfile="myapp.war" webxml="src/metadata/myapp.xml">
<fileset dir="context"/>
<lib dir="lib/hibernate">
<lib dir="lib/jdbc">
<classes dir="bin"/>
</war>

แต่ระหว่างที่เดินทางกลับบ้าน ก็เกิดอาการปิ๊งขึ้นในหัว
เข้าใจขึ้นมาทันทีว่าอะไรเป็นอะไร ทำไมใส่อย่างนี้ work
ใส่อย่างนี้ไม่ work

วันนี้ก็เลยนั่งดู code ของ Ant เพิ่มเติม
เริ่มที่ hirarchy tree ของ War Class ก่อน



จะเห็นว่า hirarachy tree ยาวเป็นหางว่าวเลย
ตัว Abstract Class MatchingTask
เป็น base task สำหรับ งานที่ต้องการให้สามารถ include, exclude file
โดยใช้ pattern matching ได้
ซึ่งทำให้เราเขียน ant ประมาณนี้ได้
<xxx includes="**/*.xx" excludes="yy/zz">
</xxx>

<xxx>
<include name="**/*.html"/>
<exclude name="**/*.page"/>
</xxx>

<xxx>
<and>
<size value="4" units="Ki" when="more"/>
<date datetime="01/01/2001 12:00 AM" when="before"/>
</and>
</xxx>

ส่วน Zip รับผิดชอบ logic ในการสร้าง zip file
โดยมีการเพิ่ม element ที่ทำให้กำหนด fileset ที่ต้องการได้
รวมทั้งเพิ่ม ZipFileset ที่ extend มาจาก FileSet โดยมี feature ที่ทำให้เรา ระบุ prefix ของ target file ได้



ซึ่งตัวอย่างของการใช้ zip task ใน ant
<zip destfile="${dist}/manual-and-src.zip">
<fileset dir="${server.src}" casesensitive="yes">
<include name="**/*.java"/>
<exclude name="**/*Test*"/>
</fileset>
<zipfileset dir="htdocs/manual" prefix="docs/user-guide"/>
<zipfileset dir="." includes="ChangeLog27.txt" fullpath="docs/ChangeLog.txt"/>
<zipfileset src="examples.zip" includes="**/*.html" prefix="docs/examples"/>
</zip>

ซึ่ง Jar extend Zipไปใช้งาน
โดยเพิ่ม function ส่วนที่จัดการ Manifest ลงไป
<jar destfile="${dist}/lib/app.jar">
<fileset dir="${build}/classes"
excludes="**/Test.class"
/>
<fileset dir="${src}/resources"/>
<manifest>
<attribute name="Built-By" value="${user.name}"/>
<section name="common/class1.class">
<attribute name="Sealed" value="false"/>
</section>
</manifest>
</jar>

สุดท้าย War ก็ extend จาก Jar อีกที
โดยเพิ่มส่วน element ที่ช่วยจัดการพวก file ที่อยู่ใน lib, classes, WEB-INF



จริงๆแล้ว features ที่เพิ่มเข้าไปใน war task
มีรากฐานจาก features เดิมใน zip task ทั้งสิ้น
จากตัวอย่างข้างบน
<war destfile="myapp.war" webxml="src/metadata/myapp.xml">
<fileset dir="context"/>
<lib dir="lib/hibernate">
<lib dir="lib/jdbc">
<classes dir="bin"/>
</war>

เราสามารถเขียน ในแบบ zip task ได้ดังนี้
(ได้ผลลัพท์เหมือนกัน แต่ editor ที่มี feature dtd-checking
มันจะฟ้องว่าผิด syntax ในส่วนของ file='src/meta..')
<zip destfile="myapp.war">
<zipfileset file="src/metadata/myapp.xml" fullpath="WEB-INF/web.xml"/>
<fileset dir="context"/>
<zipfileset dir="lib/hibernate" prefix="WEB-INF/lib"/>
<zipfileset dir="lib/jdbc" prefix="WEB-INF/lib"/>
<zipfileset dir="bin" prefix="WEB-INF/classes"/>
</zip>


ความยากในการทำความเข้าใจ ant ส่วนหนึ่งน่าจะมาจากการที่มันสามารถเขียนได้มากกว่า 1 วิธี
แล้วก็ element อะไรอยู่ใต้ element อะไรได้บ้าง
รวมทั้ง Types ที่มีมากมายให้เลือกใช้

Related link from Roti

Saturday, April 29, 2006

จาก Signal vs. Noise - It's all the same
Question
“how do you find time to do PR or marketing when you are building your products?”

Answer

Here’s the secret: it’s all the same thing.
...
Building and improving Rails is PR. Designing great interfaces is marketing. Providing quick customer support is advertising.

Related link from Roti

Friday, April 28, 2006

Auto Test with Rails

คิดว่าจะ run continuous test มานานแล้ว
เพราะเครื่องที่บ้านมันมี cpu เหลืออยู่ 1 ตัว
แต่จนแล้วจนรอด ก็ไม่ได้ทำสักที
(สาเหตุอีกสาเหตุหนึ่งอาจจะเป็นเพราะ ช่วงนี้ไม่ได้ทำ project อะไร
เป็นชิ้นเป็นอัน ก็เลยไม่มี test ที่ต้องคอย keep track ยาวๆ)

วันนี้อ่าน nuby on rails เจอว่าใน Rails สามารถทำ continuous ได้เหมือนกัน
โดยใช้ tool ที่ชื่อ ZenTest
ดู screen cast ของการใช้ autotest ได้ที่นี่
Movie of autotest in action

Related link from Roti

IE Bug ใน XMLHttpRequest

วันนี้นั่งทดสอบ Application บน Browser ทั้ง 3 ตัว
โดยจับเอาจอมาวางเรียงกัน 3 จอ
IE, Firefox, Safari

สนุกดี ที่ได้เห็นจุดเล็กจุดน้อยต่างๆกันไป
อย่าง image link บน firefox กับ IE
แสดงสีเป็นปกติ แต่บน Safari แสดงสีเพี้ยนไป
ซึ่งจับได้ว่าเป็นเพราะตอน export image จาก photoshop
มีการ set flag color profile ไว้
ทำให้สีบน safari เพี้ยนไป

แต่ที่หนักสุด ก็คือ Feature Ajax
ที่ไม่ทำงานบน IE
พยายามใส่ message เพื่อตรวจว่ามันตายตรงไหน
ปรากฎว่ามันไปตายตอนที่
เราเรียกใช้ response.responseText
หลังจาก search หาบน google อยู่พักใหญ่
ก็พบว่าเป็น bug ของ IE
ซึ่งเกิดในกรณีที่ message ที่ส่งกลับมา มี Content-Type
ที่ระบุ encoding มาด้วย, การเรียกใช้ responseText
จะทำให้ IE หยุดทำงาน
สาเหตุที่เป็นเช่นนี้เพราะ IE ตั้ง assumption ว่า message ที่ส่งกลับมา
จะมี encoding ในรูปของ utf8 พอมันเจอ header ที่ระบุ
encoding มา ก็เลยเลิกทำงานไปเสียเฉยๆ

Content-type ที่มีปัญหา
Content-Type: text/plain; charset=tis620

Content-type ที่​ IE ชอบ
Content-Type: text/plain


เบื่อจริงๆ เมื่อไรคนจะเลิกใช้ IE กันเสียที

Related link from Roti

Wednesday, April 26, 2006

[Script.aculo.us] Customize Effect

Script.aculo.us คือ javascript library
ที่ช่วยในการ ทำ Effect ต่างๆ
เช่น Fade, SlideDown, Highlight, ...

ช่่วงนี้ ผมอยู่ระหว่าง design UI ของโปรเจคใหม่อยู่
มี effect บางอย่างที่ผมต้องการ แต่ไม่มีอยู่ใน library ของ Script.aculo.us
ก็เลยต้องหาวิธีว่าจะ implement Effect ได้ง่ายๆได้อย่างไร
โชคดีที่ตัว library ของ Script.aculo.us ออกแบบมาดี
ทำให้เราสามารถ extend class เพือสร้าง customize effect ของเราได้

ตัวอย่างที่จะทำให้ดู ก็คือ Effect ที่ทำการ slide ในแนวนอน
ยกตัวอย่าง use case การใช้ ก็อย่างเช่น
ช่วยให้ web application เรามี navigation ด้านข้างที่สามารถเลื่อนแสดงหรือซ่อนได้



โดยผมกำหนด style sheet ของ sidebar และ content ดังนี้
#sidebar {
width: 20%;
height: 100%;
background-color: #ccc;
float: left;
}

#content {
display:block;
width: 99%;
float:right;
}


เริ่มเขียน Effect โดยการ สร้าง class ขึ้นมาก่อน
Effect.BarSlide = Class.create();

จากนั้นก็สั่ง extend จาก class Effect.Base
(javascript ที่เขียนนี้ จะยึด pattern ตามที่ Prototype.js
วาง pattern ไว้)
Effect.BarSlide = Class.create();
Object.extend(Object.extend(Effect.BarSlide.prototype, Effect.Base.prototype), {
// method (function) go here..
});

function หลักๆที่เราต้อง implement มีอยู่แค่ 2 function
คือ initialize กับ update
  • function initlialize ทำหน้าที่เป็น constructor ของ class เรา
    โดยกำหนดให้ pass ค่า Object ID ของ content กับ sidebar เข้ามา
    รวมทั้งขนาดของ sidebar (ระบุเป็น % ของขนาดหน้าจอ)
      initialize: function(left, right, percent) {
    var options = arguments[3] || {};
    this.left = left;
    this.right = right;
    this.percent = percent;
    this.start(options);
    },

    บรรทัดสุดท้ายเป็นบรรทัดที่สั่งให้ effect เริ่มทำงาน
    ส่วน arguments[3] คือ optional argument เช่น
    ช่วงเวลาที่ใช้เลื่อน
  • function update
    function นี้จะถูกเรียกใช้ตามจำนวน FPS (frame per second) * ช่วงเวลา (duration) ที่กำหนด
    โดยเรามีหน้าที่ render ว่า frame ณ จุดนั้นๆ มีหน้าตาเป็นอย่างไร
    ค่า parameter position ที่ pass เข้ามา จะอยู่ในช่วง 0 ถึง 1
      update: function(position) {
    var value = this.percent * position;
    $(this.left).style.width = value + "%";
    $(this.right).style.width = (100 - value) + "%";
    $(this.left).style.display = 'block';
    }
    จะเห็นว่าผมแค่สั่งเพ่ิมความกว้างของ slidebar กับ ลดความกว้างของตัว content ไปเรื่อยๆ
    โดยคิดเป็นอัตราส่วนตาม position ที่ pass เข้ามา


การนำไปใช้งาน ก็แค่สั่ง
function show() {
new Effect.BarSlide('sidebar','content',20, {duration:0.5});
}

Related link from Roti

Use case ของการใช้ Rails

ตอนนี้ผมได้ use case ของการใช้ Rails อีกแบบหนึ่งแล้ว
นั่นก็คือการใช้ rails ทำ prototype
เพราะข้อดีของ Rails ก็คือ มันพ่นออกมาได้เร็ว
ส่วน production ของจริง, ยังใช้ tapestry เหมือนเดิมอยู่
(ใช้ rails ไม่ได้ เพราะติดเงื่อนไข ว่า
rails ยังไม่ support ตัว Database ที่ใช้อยู่)

ผลพวงอีกอย่างที่ได้จาก prototype ด้วย rails ก็ืคือ
มันทำให้เกิด insight ท่ีทำให้มองได้ชัดขึ้นว่า
component ของ tapestry ใน production
ควรจะออกแบบอย่างไรดี

Related link from Roti

Monday, April 24, 2006

Java Byte Code

เมื่อวันเสาร์ไปประชุมบริษัทฯมา เลยได้รับทราบข่าวว่า
มีลูกค้ารายหนึ่งอยากให้ลอง compile java ใหม่บนเครื่อง Target แทนที่
จะ compile มาจากเครื่อง develop แล้วค่อยเอามาวางบน Target
เหตุผล แค่ เผื่อจะแก้ปัญหาได้
ซึ่งปัจจุบันปัญหาที่หลงเหลือ ก็มีเรื่อง Out of Memory
ซึ่งทาง IBM ที่ australia แนะนำมาแล้วว่่าเกิดขึ้นทีส่วน native
กับ ปัญหา jre ตาย แบบ exception fault
ซึ่ง random เกิด 3-4 เดือนครั้ง

เริ่มสงสัยว่าเป็น idea ใครนี่, อาจารย์ที่ปรึกษา หรือ IBM หรือ บริษัทที่ดูแล mainframe, หรือจากลูกค้าเอง
โดยหลักการแล้ว ผมว่า byte code ที่เกิดจาก platform คนละอัน มันไม่น่าจะทำให้
เกิด exception fault หรือ out of memory ได้นะ

ว่าแล้ว ก็อยากรู้ว่า byte code หน้าตามันเหมือนหรือต่างกันแค่ไหน
ก็เลยลอง compile เปรียบเทียบระหว่าง IBM jdk1.3, Sun jdk1.4 ดู

ลองแบบง่ายๆก่อน
public class HelloWorld {
private String name;

public HelloWorld(String name) {
this.name = name;
}

public String toString() {
return "Hello " + name;
}
}

ผลลัพท์ก็คือ หน้าตา byte code ที่ได้ เหมือนกันเพี๊ยะ ยกเว้นเลข version ที่ส่วน header
// class version 45.3 (196653)
// access flags 33
public class HelloWorld {

// compiled from: HelloWorld.java

// access flags 2
private Ljava/lang/String; name

// access flags 1
public (Ljava/lang/String;)V
L0 (0)
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.()V
L1 (3)
LINENUMBER 6 L1
ALOAD 0
ALOAD 1
PUTFIELD HelloWorld.name : Ljava/lang/String;
L2 (7)
LINENUMBER 7 L2
RETURN
L3 (9)
LOCALVARIABLE this LHelloWorld; L0 L3 0
LOCALVARIABLE name Ljava/lang/String; L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2

// access flags 1
public toString()Ljava/lang/String;
L0 (0)
LINENUMBER 10 L0
NEW java/lang/StringBuffer
DUP
LDC "Hello "
INVOKESPECIAL java/lang/StringBuffer.(Ljava/lang/String;)V
ALOAD 0
GETFIELD HelloWorld.name : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuffer.append(Ljava/lang/String;)Ljava/lang/StringBuffer;
INVOKEVIRTUAL java/lang/StringBuffer.toString()Ljava/lang/String;
ARETURN
L1 (10)
LOCALVARIABLE this LHelloWorld; L0 L1 0
MAXSTACK = 3
MAXLOCALS = 1
}

เพื่อความแน่ใจ ก็เอา class file ไป compare แบบ binary diff อีกที
ซึ่งผลที่ได้ก็เหมือนกันคือ ต่างกันแค่ byte ที่ 6 กับ 8 เท่านั้น (ส่วน header)

ทดลอง class ที่ซับซ้อนมากขึ้นอีกนิดหนึ่ง ก็คือ Algorithm Shear sort
ผลที่ได้ก็เช่นเดียวกัน ต่างกันแค่ตรงเลข version number

แต่ถ้าลองเอา jdk1.5 มา compile ดู ,คราวนี้สิ เกิดความแตกต่างแน่นอน
อย่าง StringBuffer ก็จะกลายเป็น StringBuilder

Note: ใครที่เคยผ่านหูผ่านตา forum ที่พูดถึงกรณี recompile ก็ช่วยแนะนำด้วยครับ

Related link from Roti

JDBC 4.0 Query Interface

เห็นแล้วต้องรีบแจ้งข่าว
binkley 's BLOG ลงเรื่อง Nifty JDBC 4.0
ลองดูตัวอย่าง code เขา
เขาใช้วิธีนี้ในการ insert ข้อมูลลง table
final BobQuery query = connection.createQueryObject(BobQuery.class);
query.addBob("Your Name Here.");

ดูเหมือนจะไม่มีอะไร ก็แค่ไปเรียก Query Object มาตัว แล้วก็เรียกใช้
method addBob
แต่จริงๆแล้ว Magic อยู่ที่ BobQuery ซึ่งประกาศเป็น interface ดังนี้
public interface BobQuery
extends BaseQuery {
@Update(sql = "INSERT INTO BOB(name) VALUES(?1)",
keys = RETURNED_KEYS_DRIVER_DEFINED)
DataSet addBob(final String name);

@Select("SELECT * FROM BOB WHERE name = ?1")
DataSet findBobByName(final String name);

@Select("SELECT * FROM BOB")
DataSet findAllBobs();

@Update("DELETE FROM BOB")
int removeAllBobs();
}

ว้าว ดูดี
ยังไม่หมด ทายสิว่า method findBobByName return อะไร
ถ้าดูใน interface มัน return DataSet
แต่ถ้าดูใน code เราสามารถ get Object จาก dataset ได้เลย
public class Bob {
public BigDecimal id;
public String name;
}
...

final Bob bob = query.findBobByName(name).get(0);

ดูจาก code ที่ binkley มันแปลกๆอยู่ตรง การ define interface
จากที่เขาเขียน
DataSet findBobByName(final String name);

จริงๆแล้ว มันน่าจะเขียนแบบนี้ มากกว่านั้น
DataSet<Bob> findBobByName(final String name);

อยากรู้แต่ยังขี้เกียจทดลองอยู่
(jdk1.5 ยังใช้ feature ไม่ครบเลย)

Related link from Roti

JDBC - class.forName in Mustang

เจอใน Amit Handa's Blogs
เรื่อง JDBC Driver Loading with Mustang

ใน Mustang มีกลไกใหม่ที่ชื่อ Service Provider mechanism
ซึ่งช่วยให้ provider สามารถ describe ได้ว่าใน
jar package ของตน มีการ implement service อะไรไว้บ้าง

ซึ่งใน jdbc4.0 ก็นำกลไกนี้มาช่วยในการ solve หา Driver
จากของเดิมที่เคยเขียน

Class.forName("com.myorg.jdbc.jdbcDriverImpl");
Connection con = DriverManager.getConnection(url, user, pass);

ก็เหลือแค่
Connection con = DriverManager.getConnection(url, user, pass);

ทั้งนี้ทั้งนั้น การที่จะใช้อย่างนี้ได้
jdbc provider ต้องมีการ pack jar ในรูปของ service มาด้วย
ซึ่งผมดูวิธีการใส่ Descriptor แล้ว ก็ไม่ได้ยากอะไร
ถ้าใครไม่ได้ใส่มา เราจะมา unpack jar นั้นแล้วใส่เข้าไปเองก็ได้

Related link from Roti

Sunday, April 23, 2006

Blognone Tech Day 2006

ผ่านไปด้วยดีกับงาน Blognone Tech Day
แต่ต้องขอโทษวิทยากรคนอื่นๆ หรือ mk+lew ด้วยที่ใช้เวลาพูดนานไปหน่อย
(mk อดพูดไปเลย)
เดิมอยู่ที่บ้านก็ประมาณเวลาไว้เหมือนกัน
แต่มีข้อผิดผลาดในการประเมินเวลาเล็กน้อย (ยังเข้าข้างตัวเองอยู่ เลยใช้คำว่า เล็กน้อย) คือ
  • ตอนซ้อมจับเวลา ผมใช้วิธีพูดในใจ ไม่ได้ออกเสียง
    ถึงเวลาจริงพอออกเสียง ก็เลยช้าลง
  • ตอนช่วง demo มันควรใช้เวลาน้อยกว่านั้น
    แต่เวลาทำจริง มันตื่นเต้น มือไม้สั่น ก็เลยพิมพ์ช้าลง
    ดีนะ ที่เตรียม emacs function ไว้ช่วยจำนวนหนึ่ง
    ไม่งั้น ถึงเย็นก็ไม่เสร็จ


ส่วนประเด็นเนื้อหา ก็ได้บทเรียนที่ดี
ว่าควรจะปรับเนื้อหาอย่างไรในการ present
ตัวอย่าง present ของคุณ poonlap กับ น้องวีนนาท
เป็นตัวอย่างที่ดี ของการปรับเนื้อหาให้เหลือพอเหมาะ

ส่วนความสามารถเฉพาะตัว ต้องยกให้คุณโดม เจริญยศ
สามารถพูดเรื่องที่เราฟังไม่รู้เรื่อง ให้เราหัวเราะและสนุกได้ตลอด

ส่วนตัวอย่างที่ซับซ้อนที่สุด และดีที่สุด ต้องของ
อ. จิตร์ทัศน์ ฝักเจริญผล
เสียดายที่ยกมาพูดได้ไม่ครบ
ถ้ามีเวลาให้คิด ให้นั่งพิจารณาตาม
เราคงจะเกิดความเข้าใจใน game theory มากขึ้นกว่านี้

ZWSP ของลิ่ว ก็น่าสนใจทีเดียว
เป้าหมาย แค่ทำให้ google search คำไทยได้ นี่ก็เปลี่ยนโฉมอะไรไปได้เยอะแล้ว

ของคุณ dean4j ก็ได้ความรู้เรื่อง o-net เยอะขึ้น :)
(จริงๆแก present หัวข้อ ejb3)

Xen ของคุณ สมพล บุญจริง
ผมจิ๊ก CD ที่แกแจก กลับมาด้วย
ขอบคุณมากครับ

ส่วน Zope ของคุณสุกรี พัฒนภิรมย์ ประเด็นที่ผมสนใจมากสุด ก็คือ
เรื่อง layer ที่โปรแกรมที่ส่วนปรับปรุง มันไม่ได้ปนอยู่กับ code ต้นฉบับ
,ไว้คงต้องหาเอกสารมาอ่านต่อ
เพราะเคยคิดว่า programe ที่เราเขียน มันน่าจะทำอย่างนั้นได้

ทีมงานที่พร้อมเพรียงที่สุด
ก็ต้องทืมของคุณ ปรเมศวร์ มินศิริ
ตัวใหญ่พร้อมเพรียงกันหมดทุกคนเลย

ส่วนภาพงานที่อยากเสริมให้มีในอนาคต
(tech day มีเหมือนเดิม แต่เสริมไอ้นี่ขึ้นมา)
อาจจะเป็น workshop เล็กๆ (10-20 คน)
ที่มา meeting กันแล้วก็ show technique เล็กๆน้อย
แลกเปลี่ยนกัน เช่น ผมอาจจะ show ว่า firebug
มันช่วยอะไรผมได้

Related link from Roti

Saturday, April 22, 2006

Why not take just a picture?

เคยลง link งานของ Bert Monroy ไปแล้ว
มีคนถามเขาว่า "Why not tak just a picture?"
นี่คือคำตอบเขา
Good question, when you consider my paintings look like photographs. Well, for one thing, I’m not a photographer. To me, it is not the destination that is important—it is the journey. The incredible challenge of recreating reality is my motivation.

Related link from Roti

Thursday, April 20, 2006

css shadow, overlay

พอเริ่มใช้ lightbox go wild แล้ว ก็เลยเริ่มมองเห็นพวกที่ใช้ shadow เยอะขึ้น
อย่างอันนี้ เป็น menu ที่มีการใช้ layer เข้ามาทำเงา
Pop-Up Menus



technique ที gone-wild ใช้ จะใช้การ set สี backgroup
และกำหนด opacity
#overlay {
...
background-color:#000;
opacity:.80;
filter: alpha(opacity=80);
-moz-opacity: 0.8;
}

ส่วน popup menu ใช้ image ที่เป็น transparent background
background-image: url(shadow.png);


อ่านเพิ่มเติม

Related link from Roti

ที่นั่ง

Tor Norbye เขาเขียนเรื่อง Learn Twice As Much With Same Effort
ขอบกฎ "Second Law Of Gravitation" ที่ว่า
Students will gravitate towards the back of the class room. The seats in front fill up last.

ดูแผนผังที่นั่งเขาอ้างถึงแล้ว
สมัยที่ผมเรียน ผมนั่งกลุ่ม 31 % มาตลอดเลย
สงสัยอันนี้คงเป็นสาเหตุที่ทำให้ไม่เก่งด้านทฤษฏี

Related link from Roti

คิดถึง

เมื่อก่อนบ้านผมมีหนูมาอาศัยอยู่ด้วยเยอะมาก
ดักจับกันไม่หวาดไม่ไหว
อยู่ๆมา หนูก็เริ่มหายหน้าหายตาไป
โดยไม่มีใครเฉลียวใจ

มีอยู่วันงูหลามเลื้อยเข้ามานอนเล่นหน้าบ้าน
(บ้านอยู่แถว major รัชโยธิน)
ด้วยความเป็นห่วงกลัวว่างูจะมาหยอกรัดลูกชายผมเล่น
งูเลยต้องย้ายออกไป
ก็เลยโทรแจ้งให้เจ้าหน้าที่มาค้นย้าย
ยามที่อยู่ตรงข้ามบ้านแนะนำให้โทรไปแจ้งที่ศูยน์นเรนทร
ฟังชื่อศูนย์แล้วนึกถึงภาพ หน่วย swat ก็เลยเปลี่ยนใจโทรไป
"ร่วมด้วยช่วยกัน" แทน
ได้ผล เขาประสานงานแจ้งให้ อปพร. (อาสาสมัครป้องกันภัยพลเรือน) มาจัดการให้



ผ่านไป 2 เดือน เริ่มรู้สึกคิดถึงเจ้างูตัวนั้นแล้ว
เพราะบรรดา หนูๆ ที่เคยเงียบสงบ เริ่มก่อตั้งสมาคมขึ้นมาอีกแล้ว
บางตัวมีวิธีเจาะเข้าบ้านด้วยการมุดเข้ามาทางท่อน้ำทิ้งในห้องน้ำ
ซึ่งต้องเอาหัวดันฝาดักกลิ่นที่ครอบท่อน้ำทิ้งขึ้นมา

เฮ้อ นี่ถ้าไม่ติดลูกชาย
ก็คงจะปล่อย พี่งู ไว้เฝ้าบ้านเหมือนเดิมแล้ว

Related link from Roti

Saturday, April 15, 2006

[การเมือง] มรดกของทักษิน

ไมเคิล ไรท เขียน "ทำอย่างไรในยุคหลังทักษิณ?" ใน มติชน
แต่สามารถอ่านได้จาก มหาวิทยาลัยเที่ยงคืน ด้วย

รัฐบาลไทยรักไทยให้ความสำคัญ เข้าถึง และปลุกระดมชนชั้นราษฎรชาวชนบทอย่างไม่เคยมีมาก่อน นโยบายนี้จะมีผลดี-ผลร้ายต่อไปเรายังมองไม่เห็น แต่เราต้องยกย่องว่าเป็นกุศโลบายที่ยกฐานะการเมืองไทยจาก "การเล่นละครของชนกลุ่มน้อย" มาอยู่ในระดับ "ชีวิตจริงของคนส่วนใหญ่" ซึ่ง ดีไม่ดี หมายถึงการเติบโตทางการปกครองบ้านเมือง (Political Maturity)

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

Related link from Roti

โปรแกรมประมวลผลสอบ

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

ในแง่คนนอกที่ไม่เกี่ยวข้องแบบเรา ก็ได้แต่เดาว่า เกิดอะไรขึ้นบ้าง
ที่ผมเดาๆไว้ ก็น่าจะมี 2 ประเด็นนะ
ประเด็นแรก คือเรื่องความฉุกละหุก
สังเกตงานราชการ มักจะมีปัญหานี้ทุกครั้ง
ประมูลช้า(ทำให้เหลือเวลาน้อย) หรือไม่ก็
คนเขียนขาดประสบการณ์ โปรแกรมก็เลยก้าวหน้าไปอย่างช้าๆ
ซึ่งทั้งคู่จะไปตายทีเดียวกัน ก็คือ ไม่มีเวลา test
ซึ่งเรารู้กันอยู่แล้วว่าโปรแกรมทุกโปรแกรม
มันมี bug แหง๋ๆ

อันนี้สองนี่ไม่แน่ใจว่าจริงไหม
ใครรู้จริงช่วยแย้งด้วยแล้วกัน
ไม่รู้ยังจำสมัยก่อนที่มีการนำ computer
เข้ามาควบคุมระบบไฟจราจร ในกรุงเทพฯได้หรือเปล่า
ข้อผิดผลาดครั้งนั้น ก็คือ
การ over estimate สิ่งที่โปรแกรมสามารถทำได้
โดนเชื่อกันมากไปว่า computer(software) มันต้องทำได้
ผลสุดท้าย ก็เละเป็นโจ๊กเหมือนกัน
สำหรับ admission คราวนี้ ผมเดาว่าคงมีกลิ่นนี้โชยๆอยู่เหมือนกัน
(ในแง่ของ OCR)

สำหรับผม บทเรียนที่สำคัญในเรื่องนี้ น่าจะเป็นประเด็นเรื่อง Fault tolerance
ที่ควรจะต้อง design และ implement ไว้ทั้งในระดับ work procedure
(เช่น การ scan ต้อง scan 2 ครั้ง แต่ละครั้งทำโดยทีมงานคนละชุด)
และในระดับ software fault tolerance

Related link from Roti

Friday, April 14, 2006

[java] Jetty Continuations

ใน Web container ปัจจุบัน
model ของ thread จะเป็นแบบ Thread per Connection
นั่นก็คือ 1 connection ที่วิ่งเข้ามา จะมี 1 thread ที่ถูก assign ให้รับผิดชอบ
(ผลพวงมาจาก socket api, ที่มีการ assign connection ให้กับ thread)

แนวคิดใหม่ๆ ก็คือการใช้ NIO เข้ามาช่วย
อย่างใน Jetty6 จะมี model ที่เรียกว่า Thread per Request
ความหมายก็คือ connection ที่เปิดทิ้งไว้, แต่ไม่มี request วิ่งเข้ามา
จะไม่มี thread รองรับมัน (thread จะถูกส่งกลับเข้า pool)
เมื่อไรก็ตามที่มี request เข้ามา จึงจะมีการ assign thread ให้

บางคนอาจจะสงสัยว่า 1 connection มีได้หลาย request ด้วยหรือ
Connection ใน HTTP1.1 จะมี default เป็น keep alive
และเมื่อบวกกับ App สมัยใหม่ที่เป็น ajax style
มีข้อมูลวิ่งไปมาถี่ๆ, connection ที่ browser เปิดไว้ก็แทบจะไม่ disconnect เลย
บางคนถึงกับล้อว่า นี่เราจะเปลี่ยน browser ให้กลายเป็น client-server แบบเดิมๆหรืออย่างไร
(client-server แบบที่เปิด connection ค้างไว้)

แล้ว Jetty Continuations คืออะไร
Continuations ใน Jetty ไม่ใช่ continuation แบบใน scheme, ruby (พวก callcc)
ความหมายจริงๆของมันก็คือ ในระหว่างการทำงานของ Servlet,
servlet สามารถที่จะสั่ง suspend thread ได้
โดยถ้ามีการสั่ง suspend เมื่อไร, jetty จะทำการ detach thread ออกจาก request,
และคืน thread กลับเข้าสู่ thread pool

แล้ว App ประเภทไหนที่เหมาะกับ Continations?
ที่ชัดเจนที่สุด ก็น่าจะเป็นพวกที่ต้องมีการ polling เป็นระยะๆ เช่น พวก chat
หรือพวกที่มี nature การทำงานเป็น asynchronous
เช่น app ที่มีการเรียกใช้ service ผ่านทาง JMS แล้วต้องรอ reply message

ลองมาทดสอบ NIO กับ continuation ดู
เริ่มด้วย servlet แบบง่ายๆ
protected void processRequest(HttpServletRequest request, 
HttpServletResponse response)
throws ServletException, IOException {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

out.println("<html>");
out.println("<head>");
out.println("<title>Servlet HelloServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet HelloServlet at " + request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");

out.close();
}

ใน servlet ให้มี sleep 100 ms,
ส่วน version ที่เป็น continuation ก็แค่เปลี่ยนจาก sleep เป็น
    Continuation ctn = ContinuationSupport.getContinuation(request,this);
ctn.suspend(100);


ผมใช้ jmeter ทดสอบ โดยเรียก 120 request ใน 1 วินาที
เป็นเวลา 20 วินาที (รวมทั้งหมด 2400 request)
ตั้ง duration assert ไว้ที่ 200 ms

Jetty 6 NIO
50 % 109 105 106
90 % 147 125 125
> 200 ms 69 91 82

Jetty 6 BIO
50 % 103 103 104
90 % 111 111 114
> 200 ms 0 2 83

Jetty 6 NIO + continuations
50 % 108 106 106
90 % 120 125 122
> 200 ms 0 78 89

Tomcat 5.0.30
50 % 104 103 103
90 % 111 114 113
> 200 ms 0 0 0

Note: 50, 90 % คือ distribution ของ response time
เช่น 50% ใช้เวลาน้อยกว่าหรือเท่ากับค่าที่ระบุ


ดูแล้ว NIO จะช้ากว่า BIO (block IO) อยู่นะ
ส่วน continuation ก็ไม่ต่างกับ NIO
และ jetty ใน version นี้เริ่มสู้กับ tomcat 5.0.x ได้แล้ว

ที่นี้ลองเปลี่ยนช่วงเวลาที่ sleep กับ suspend เป็น 1000 ms บ้าง
Thread.sleep(100);
==========================
ctn.suspend(1000);


ใช้ JMeter เรียกที่ 120 request/ sec, 20 วินาที เหมือนเดิม
แต่เปลี่ยน assert เป็น 1100 ms
ผลที่ได้

Jetty 6 NIO
50 % 1021 1008 1009
90 % 1397 1438 1419
> 1100 ms 956 919 883

Jetty 6 BIO
50 % 1002 1003 1002
90 % 1439 1549 1487
> 1100 ms 788 786 707

Jetty 6 NIO + Continuation
50 % 1002 1002 1002
90 % 1015 1011 1013
> 1100 ms 126 93 93

Tomcat 5.0.30
50 % 1005 1002 1002
90 % 1349 1035 1257
> 1100 ms 562 398 400

จะเห็นว่ากรณีที่ thread เริ่มสะสมกันมากๆ
(เนื่องจากเราตั้งค่า sleep ไว้ที่ 1 sec. ทำให้ request ชุดที่ 2 เริ่มเข้ามากวน request ชุดที่ 1)
jetty continuations ให้ผลลัพท์ที่ดีขึ้นอย่างเห็นได้ชัด

ข้อที่ต้องระวังในการใช้ jetty continuation ก็คือ
หลังจากที่เราสั่ง suspend ไปแล้ว, เมื่อ thread resume กลับมาอีกครั้ง
HttpServletRequest ที่ได้ในคราวนี้ จะไม่ใช่ตัวเดิมแล้ว
ค่า parameter ต่างๆ จะหายหมด
ดังนั้นเราต้อง save ค่านี้เก็บไว้ใน session หรือใน database ก่อน
นอกจากนี้ ยัง suspend ยังห้ามใช้หลังจากมีการเขียน response ไปแล้ว

ประเด็นที่ผมยังไม่รู้คำตอบ ก็คือ
ในการใช้งานจริง เรามักจะวาง web container ไว้หลัง web server
คำถาม ก็คือพวก persistent connection ที่เกิดขึ้น มันจะเกิดกับ web server
แล้ว jetty continuation ยังมีประโยชน์อยู่อีกหรือเปล่า
case หนึ่งที่เห็นว่า น่าจะมีประโยชน์อยู่บ้าง ก็คือพวก asynchronouse nature
(พวกที่ใช้ jms ต่อไปยัง service)
เพราะถ้าใช้ jetty contination เข้ามาช่วย ก็น่าจะทำให้เรา
เพิ่มจำนวน connection ระหว่าง web server กับ web container ได้
ซึ่งมีผล ทำให้ through put เพิ่มขึ้น

Related link from Roti

Wednesday, April 12, 2006

[art] links 12-04-06


Simone Zahradka
design เขาน่าสนใจดี, อย่าลืมลอง click ดูลูกเล่นเขา ตอนที่เลือก รูปใน portfolio


bertmonroy
digital photo-realist artist
Damen คือชิ้นงาน ที่ใครเห็นก็ต้องทึ่ง
  • The image size is 40 inches by 120 inches.
  • The flattened file weighs in at 1.7 Gigabytes.
  • It took eleven months (close to 2,000 hours) to create.
  • Taking a cumulative total of all the files, the overall image contains over 15,000 layers.

ปล. แฟนผมเห็นภาพนี้แล้วพูดออกมาว่า "น่ากลัว"
(ขยันจนน่าตกใจ)

Related link from Roti

[java, ajax] polling in DWR 2.0

Feature ที่น่าสนใจใน DWR 2.0 ก็คือ Reverse Ajax
ที่ทำให้ฝั่ง java server สามารถ call ไปยัง javascript ที่อยู่ใน browser ได้
(จริงๆแล้วมันคือ polling นั่นแหล่ะ แต่ api มันออกแบบมาให้ดูเหมือน
เป็นการ call function โดยตรง)

ลองแกะ demo ของเขาดูกัน
เริ่มด้วย browser ที่ต้องการใช้ feature นี้ต้องสั่งดังนี้
DWREngine.setPolling(true);

มาดู input form ในฝั่ง browser ก่อน
<p>
Your Message:
<input id="text" onkeypress="DWRUtil.onReturn(event, sendMessage)"/>
<input type="button" value="Send" onclick="sendMessage()"/>
</p>

<p>Messages:</p>
<div id="chatlog"></div>

จะเห็นว่าถ้ามีการกด enter หรือ click button เมื่อไร
ก็จะมีการเรียกใช้ function ที่ชื่อ sendMessage

สิ่งที่ sendMessage ทำ ก็คือ เรียกใช้ method addMessage
(สำหรับคนที่ไม่คุ้นกับ DWR, Chat object เป็น object ที่อยู่บน server,
DWR ช่วยทำให้มันเหมือนกับอยู่บน space เดียวกัน)
function sendMessage() {
var text = DWRUtil.getValue("text");
DWRUtil.setValue("text", "");
Chat.addMessage(text);
}


ตามไปดู method addMessage ที่อยู่บนฝั่ง server บ้าง
เริ่มด้วยการ validate message ก่อน
จากนั้นก็ add เข้า LinkList ที่ทำหน้าที่ maintain message history
public class Chat
{

public void addMessage(String text)
{

if (text != null && text.trim().length() > 0)
{
messages.addFirst(new Message(text));
while (messages.size() > 10)
{
messages.removeLast();
}
}


ขั้นถัดไปก็คือการ send message ไป update ที่ browser
หัวใจหลักของขั้นนี้ ก็คือ Class WebContext
Object WebContext จะคอย maintain ว่ามี browser ไหน กำลัง online อยู่บ้าง,
และกำลังใช้ page ไหนอยู่
(ความหมายของ online ก็คือ browser ยังมีการ polling มาที่ server เป็นระยะๆ)
        WebContext wctx = WebContextFactory.get();
String currentPage = wctx.getCurrentPage();

การ call กลับไปยังฝั่ง browser จะใช้ class OutboundVariable มาช่วยทำ serialize
        try
{
OutboundVariable ov = wctx.toJavascript(messages);
String eval = ov.getInitCode() + "receiveMessages(" + ov.getAssignCode() + ");"; //

สำหรับผู้ที่สงสัยว่า getInitCode ใน code ข้างบน มันทำอะไร
สิ่งที่ method getInitCode ของ OutboundVariable generate ออกมา
มีหน้าตาดังนี้
var s0=[];var s1={id:1144813303647,text:"xxxx"};
var s2={id:1144813193425,text:"hello"};
var s3={id:1144813140215,text:"I know"};
var s4={id:1144813133617,text:"yes"};
var s5={id:1144813128413,text:"from camino"};
var s6={id:1144813122412,text:"hello"};
var s7={id:1144813118411,text:"yyy"};
var s8={id:1144813116211,text:"xxx"};
var s9={id:1144813095408,text:"zzzz"};
var s10={id:1144813092406,text:"yyyy"};
s0=s0.concat([s1,s2,s3,s4,s5,s6,s7,s8,s9,s10]);

ส่วน script ที่ ov.getAssigneCode gen ออกมา ก็มีหน้าตาดังนี้
receiveMessages(s0);


จากนั้นก็ loop เพื่อ call ไปยัง page ทุก page ที่ online อยู่
พระเอกในตอนนี้ก็คือ ScriptSession

// Loop over all the users on the current page
Collection pages = wctx.getScriptSessionsByPage(currentPage);
for (Iterator it = pages.iterator(); it.hasNext();)
{
ScriptSession otherSession = (ScriptSession) it.next();
otherSession.addScript(eval);
}
}

message ก็จะวิ่งต่อกลับมายัง javascript
ให้สังเกตว่า code ข้างบน ทำการ call javascript function ที่ชื่อ receiveMessages
โดยมี parameter คือ javascript code ที่ generate จาก method ข้างบน
จากนั้นที่ฝั่ง javascript ก็จะทำการ update หน้าจอ
function receiveMessages(messages) {
var chatlog = "";
for (var data in messages) {
chatlog = "<div>" + messages[data].text + "</div>" + chatlog;
}
DWRUtil.setValue("chatlog", chatlog);
}


ถ้าเราลองไปดู http protocol ที่วิ่งระหว่าง browser กับ server
จะเห็นว่ามีการส่ง polling ออกมาเป็นระยะ
หน้าตาของ polling packet มีหน้าตาดังนี้
POST /dwr/dwr/plainjs/DWRSystem.poll.dwr HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
....

callCount=1
c0-scriptName=DWRSystem
c0-methodName=poll
c0-id=2235_1144813311449
httpSessionId=3h2qek9hq3utl
scriptSessionId=0EADE3F10D560BBF374C1EDB48193BDA


การใช้ polling จะทำให้เกิดประเด็นตามมา ก็คือเรื่อง load ที่มากขึ้น (long lived HTTP Request)
มี solution หนึ่งที่ถูกเสนอขึ้นมา ก็คือ
Jetty 6 continuations
ซึ่งตอนนี้อยู่ในขั้น beta แล้ว

Related link from Roti

Tuesday, April 11, 2006

[Rails] Functional Test & cookie setting

วันนี้นั่งเขียน Functional test case ใน rails
เจอปัญหาว่าไม่สามารถ set cookie ได้
หลังจากพึ่ง google แล้วก็พบว่า
การ set cookie ใน test case ต้องเขียนเต็มๆอย่างนี้
def test_sort_by
@request.cookies['sort_by'] = CGI::Cookie.new(
'name' => 'sort_by',
'value' => 'location')
@request.cookies['lang'] = CGI::Cookie.new(
'name' => 'lang',
'value' => 'th')
get :index
assert_response :success
assert_equal 'location_th', @controller.check_sort_by
end


ห้ามเขียนสั้นๆแบบนี้
@request.cookies['lang'] = 'th'

Related link from Roti

M or F

seapegasus ตั้งข้อสังเกตว่า IDE มีธรรมชาติเป็นผู้หญิง
ลองไปดูเหตุผลที่นี่
NetBeans Evandalists #10

Related link from Roti

Monday, April 10, 2006

Gavin King เขียนถึงเรื่อง Red Hat to acquire JBoss
เล่าถึงสมัยก่อนเริ่มทำ hibernate
Three years ago I was dragging myself out of bed each morning to go to my depressing job building boring web applications for organizations run by risk-averse middle managers and clueless "architects".

That was a world where an idea - however unoriginal - was valued on the basis of how many grey hairs were in evidence on the head of the person expressing the idea.

บ้านเราเป็นแบบนี้หรือเปล่า ?

Related link from Roti

Sunday, April 09, 2006

[C#, LINQ] C# 3.0 กับ Haskell

Erik Meijer เขียน paper นี้ขึ้นมา
Confessions Of A Used Programming Language Salesman - Getting The Masses Hooked On Haskell
(แค่ชื่อ paper ก็เจ็บแล้ว)

ในช่วงแรกๆของ paper เขาพูดถึงความพยายามของเหล่า Haskell programmer
ที่พยายามจะ implement interface ที่ทำให้ Hashkell สามารถติดต่อกับ
โลกภายนอกได้ (imperative world)

ในท้ายบท(ที่ว่าด้วย background ของ Haskell),
เขาได้ปล่อยอารมณ์ขันออกมา
I often joke that the world’s population of Haskell program-
mers fits is a 747, and when that crashed nobody will notice.
Howerver, the world’s population of Mondrian programmers fits
in a Cessna and when that would crash nobody would really notice.

Note: Mondrian -> Internet Scripting Language

ที่ชอบมาก ก็คือประโยคนี้
Apparently, I lack the talents to entice the functional pro-
gramming community, so I decided to sell my soul to the most
popular programming paradigm, objects, and to the company
that has the biggest market share, Microsoft to save the com-
mon programmer.


บทหลังๆ จะพูดถึง Haskell เข้าไปมีอิทธิพลใน C# กับ ​LINQ อย่างไรบ้าง

หลังจาก scan paper นี้แล้ว ก็ได้ข้อสรุปอย่างหนึ่ง
C# 3.0 ที่มี Monads + Closures + Meta Programming + LINQ Framework
Java หมดทางสู้เลย
แฟน Java แบบผมก็ได้แต่ทำใจ
แล้วหันไปหากิ๊ก Ruby

Related link from Roti

Saturday, April 08, 2006

[Ingres] II7 port

ในคู่มือการต่อ jdbc ไปยัง database ingres
ระบุไว้ว่า port number มีค่า default เป็น 'II7'
ซึ่งทำให้ผมเกิดความสงสัยว่า มัน resolve ค่าเป็น integer อย่างไร
แรกสุดก็นึกว่าเป็น feature ของ unix
พยายามค้นใน google แต่ไม่พบ
สุดท้ายก็ไปเจอใน source code ของ ingres jdbc
private int getPort(String target) {
...
char c0 = port.charAt( 0 );
char c1 = port.charAt( 1 );
char c2 = (port.length() == 3) ? port.charAt( 2 ) : '0';

if (
Character.isLetter( c0 ) &&
Character.isLetterOrDigit( c1 ) &&
Character.isDigit( c2 )
)
{
c0 = Character.toUpperCase( c0 );
c1 = Character.toUpperCase( c1 );

return( 1 << 14 | (c0 & 0x1f) << 9 |
(c1 & 0x3f) << 3 | (c2 & 0x07) );
}

...
}

เวลาเราลง Ingres, จะต้องมีการระบุ Installation code
ซึ่งใช้แยก instance, configuration ของ Database Server ออกจากกัน
(1 เครื่อง, start ได้หลาย installation พร้อมๆกัน)
ค่า default ของ Installation code ก็คือ 'II'
นี่จึงเป็นที่มาของ port 'II7'
ถือว่าเป็น idea ที่ดีเหมือนกัน เพราะไม่ต้องจำเลข
เปลี่ยนมาจำตัวหนังสือแทน

Note: เลข 7 มาจากไหน?

Related link from Roti

[Ruby] แปลง float ให้อยู่ในรูป Single precision (IEEE 754)

วันนี้นั่งทดลอง convert Ingres JDBC Library ซึ่งเขียนด้วย java
ให้เป็น ruby code,
ไปเจอ code ของ Java อยู่อันหนึ่ง
Float.floatToIntBits(fValue);

method นี้ทำหน้าที่แปลง float value ให้อยู่ในรูป Single precision

ใน ruby ถ้าต้องการทำแบบเดียวกัน
[fValue].pack("g")


ใน ruby การแปลงเป็น binary format จะใช้
method pack ใน class Array
ส่วนการแปลงกลับจาก binary form ก็จะใช้
method unpack ใน class String

กรณีที่เราต้องเขียนโปรแกรมระดับ Network protocol,
bit-struct เป็น library ตัวหนึ่งที่ช่วยให้เรา pack กับ unpack ได้ง่ายขึ้น
ตัวอย่าง code ของ bit-struct
class IP < BitStruct
unsigned :ip_v, 4, "Version"
unsigned :ip_hl, 4, "Header length"
unsigned :ip_tos, 8, "TOS"
unsigned :ip_len, 16, "Length"
...
octets :ip_src, 32, "Source addr"
octets :ip_dst, 32, "Dest addr"
rest :body, "Body of message"
end

ip = IP.new
ip.ip_v = 4
ip.ip_hl = 5
ip.ip_tos = 0
...
p.ip_src = "192.168.1.4"
ip.ip_dst = "192.168.1.255"
ip.body = "This is the payload text."


Note: สำหรับคนที่ไม่คุ้นกับ ruby, unsigned ที่เห็นใน source ข้างบน
ไม่ใช่การประกาศ datatype แบบใน java หรือ c
แต่เป็นการเรียกใช้ class method เพื่อ dynamic generate instance method

Related link from Roti

Wednesday, April 05, 2006

[Rails] โครงสร้างภายในของ ActiveRecord ในส่วน attributes

สมมติเรามี table products ซึ่งมี definition ดังนี้
+-------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(120) | YES | | NULL | |
| price | decimal(10,2) | YES | | NULL | |
+-------+---------------+------+-----+---------+-------+

เวลาที่เราใช้คำสั่ง find เพื่อค้นหาข้อมูล
@product = Product.find(1)

ActiveRecord จะ delegate การทำงานต่อไปยัง database adaptor ที่เรากำหนด
ซึ่ง adpater จะ fetch ข้อมูลจาก database แล้วแปลงให้อยู่ในรูป hash table
{"name"=>"test", "price"=>"11.00", "id"=>"1"}

ให้สังเกตว่า datatype จะเป็น string หมด
Note: ต้องใช้ debugger เข้าไป step ดู จึงจะเห็น, ถ้าใช้วิีธี print debug
โดยเรียกใช้ method attributes จะเห็นเป็น datatype ที่
activerecord ทำการ convert ให้แล้ว


จากนั้น finder ก็จะทำการ new Model Object ขึ้นมา
และกำหนด instance variable @attributes
ให้มีค่าเท่ากับ hash result ที่ได้จาก database adaptor

การ access object attribute ปกติเราจะใช้คำสั่งแบบนี้

old_price = @product.price
@product.price = 12

ในครั้งแรกที่เราเรียกใช้ method price หรือ price=
method พวกนี้จะยังไม่มีอยู่จริง
แต่ Activerecord จะใช้ method method_missing ดักไว้
(method_missing เป็นกลไกของ ruby ที่ไว้ support กรณี send message ไปยัง method ที่ไม่ได้ define ไว้)
และทำการ manipulate ค่าใน @attributes โดยใช้ method
read_attribute กับ write_attribute
Note: method read_attribute, write_attribute
มีคุณสมบัติพิเศษ ก็คือ มันช่วยในการ convert datatype ให้ด้วย
ถ้าเราเรียกใช้ @product.read_attribute('price')
ผลลัพท์ที่ได้กลับมา จะอยู่ในรูปของ Float value

หลังจากนั้น activerecord ก็จะ check ว่า flag generate_read_methods
เป็น true หรือไม่ ถ้าใช่ มันก็จะทำการ generate method ให้เราอัตโนมัติ
ซึ่งมีผลให้การ access ครั้งต่อไปมี performance ที่ดีขึ้น

เวลาที่เรานำ activerecord model ไปใช้งานร่วมกับ UI layer
ค่าที่ pass มาจาก form เรามักจะกำหนดให้อยู่ในรูป hash table
ผลที่ได้ ก็คือเราสามารถนำ hashtable นั้น มา update model
ของเราได้โดยตรง (เพราะ internal structure ของ model
ก็เป็น hash เหมือนกัน)

Related link from Roti

Git (Version Control)

Git - Tree History Storage Tool

GIT is a version control system designed to handle absolutely massive projects with speed and efficiency; it is used mainly for various open source projects, most notably the linux kernel.


gitk screenshot (repository browser)

Related link from Roti

Tuesday, April 04, 2006

[clisp, perl] dynamic scope

เคยเขียนกรณี Dynamic scope & Static scope ไปที
ตอนนั้นระบุว่า scheme, javascript เป็น static scope
ส่วน ruby เป็น dynamic scope

วันนี้มาดู clisp บ้าง
clisp สามารถเลือกได้ว่าจะให้เป็น static หรือ dynamic scope
โดย default จะเป็น static scope

(setq x 5)
(defun get-x () x)
(let ((x 12)) (get-x)) ;; => 5

ผลลัพท์ที่ได้จากคำสั่งที่ 3 , get-x จะได้ค่า 5 ออกมา
(ซึ่งเราเรียกว่า static scope)
แต่ถ้าเป็น dynamic scope, get-x จะต้องได้ค่า 12 ออกมา

ใน clisp เราสามารถกำหนดให้ตัวแปรเป็น dynamic scope ได้
โดยใช้คำสั่ง defvar
(defvar y 5)
(defun get-y () y)
(let ((y 12)) (get-y))


perl ก็สามารถเป็นได้ทั้ง static หรือ dynamic scope
กรณี static
$x = 5;
sub get_x {
print "x = $x";
}
sub test_static {
my $x = 12;
get_x();
}
test_static # => x = 5

ถ้าต้องการ dynamic ก็จะเป็นแบบนี้
$y = 5;
sub get_y {
print "y = $y";
}
sub test_dynamic {
local $y = 12;
get_y();
}
test_dynamic; # => y = 12

Related link from Roti

Monday, April 03, 2006

[ruby] instance variable อีกที

object ทุก object ใน ruby สามารถมี instance variable ของตัวเองได้
2.instance_variable_set '@x', 1

2.
instance_variable_get '@x' # => 1
4.instance_variable_get '@x' # => nil
(4 - 2).instance_variable_get '@x' # => 1

บรรทัดที่น่าสนใจคือ (4 - 2).instance_vairable_get '@x'
ถ้าจิตนาการ การทำงานภายในของ interpreter ณ บรรทัดนั้น ดู
  • ขั้นแรกก็ต้อง new instance fixnum ค่าเป็น 4
  • จากนั้นก็ new instance fixnum ค่าเป็น 2
  • เรียกใช้ method - ของ instance 4 โดยมี parameter เป็น instance 2
  • เรียกใช้ method instance_variable_get บน instance fixnum 4

ปัญหาเกิดที่ขั้นที่ 3 ว่ามันเรียก instance_variable_get บน object คนละตัวกับ object 2
แล้วทำไมค่ามันยังออกมาถูก มันมีกลไกพิเศษอะไรตรงไหน

ลองเปลี่ยนจาก fixnum เป็น String ดูบ้าง
"hello".instance_variable_set '@x', 1
puts "hello".instance_variable_get '@x' # => nil
x = "hello"
x.instance_variable_set '@x', 1
puts x.instance_variable_get '@x' # => 1

จะเห็นได้ว่า instance_variable ของ 2 กับ "hello" มีพฤติกรรมไม่เหมือนกัน

ข่าวดี ไม่ใช่ทุกอย่างใน ruby ที่เป็น object อย่างเป็นทางการ
คำว่า object อย่างเป็นทางการ ก็คือ มี structure เป็นเรื่องเป็นราว เช่นมี id, methods, attributes
ถ้าจะให้ยกตัวอย่างชัดๆ ต้องดูที่ implementation ของ ruby ที่ใช้ c เขียน
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};

แต่ก็มีพวก special object ที่ไม่ได้ใช้ struct ในการ represent
object ต่อไปนี้ ใช้ unsigned long ตัวเดียวในการ implement object (แทนที่จะใช้ struct)
  • small integers
  • symbols
  • true
  • false
  • nil
  • Qundef (อันนี้ไม่รู้ว่ามันคืออะไร)

เหตุผลที่ต้อง implement special object แบบนี้ ก็คือเรื่อง performance
(เหมือนกับ java ที่ต้องมี primitive datatype)

ที่นี้ ruby สามารถให้ค่า instance_variable ที่ถูกต้อง ในกรณี fixnum ได้อย่างไร
ที่เป็นเช่นนี้ ก็เพราะวิธีการ implement ของ ruby เป็นแบบนี้

/* file variable.c */
static VALUE
ivar_get(obj, id, warn)
VALUE obj;
ID id;
int warn;
{
VALUE val;

switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, &val))
return val;
break;
default:
if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
return generic_ivar_get(obj, id, warn);
break;
}
if (warn) {
rb_warning("instance variable %s not initialized", rb_id2name(id));
}
return Qnil;
}

จะเห็นว่ากรณีของพวก special object จะมีการใช้ generic_ivar_get
ซึ่ง function generic_ivar_get นี้จะไปค้นหาค่าจาก global hashtable ที่ share กันใช้ ระหว่าง special object
นี่เป็นสาเหตุให้ fixnum 2 สามารถเรียกใ้ช้ instace_variable_get แล้ว ยังได้ค่าเดิมกลับมา

ส่วนกรณี String, จริงๆแล้ว มันตก case เดียวกับ fixnum
แต่วิธีการ implement ของ String เป็นอีกแบบหนึ่ง
ลองดูตัวอย่างนี้
irb(main):001:0> 2.object_id
5
irb(main):002:0> (4-2).object_id
5
irb(main):003:0> "hello".object_id
175214
irb(main):004:0> "hello".object_id
169884

จะเห็นได้ว่า id ของ string มันเปลี่ยนไปทุกครั้ง
ดังนั้นการ lookup instance_variable ใน generic table ที่ต้องใช้ object_id เป็นตัวเปิด hashtable
ก็เลยไม่ได้ค่าเดิมออกมา

Related link from Roti

[ruby] instance variable ของใคร?

ลองดู code นี้แล้วทายสิว่า
ถ้าเราเรียก method x แล้วจะได้อะไรกลับมา

class Foo
@x = 1
def x
@x
end
end

f = Foo.new
f.x # => nil

ใน ruby, class ก็คือ object หนึ่ง(ไม่เหมือน java)
ดังนั้น @x ของเราก็ตือ instance variable ของ class Foo

class Foo
@x = 1
def initialize(value)
@x = value
end
def x
@x
end
def self.x
@x
end
end

f = Foo.new(8)
f.x # => 8
Foo.x # => 1

Related link from Roti

จริยธรรมของแท็กซี่

เมื่อวันเสาร์ ภรรยาผมนั่ง taxi ไปกับลูกชายตัวน้อย
taxi เอ่ยปากชวนคุย
"พรุ่งนี้ไปเลือกตั้งไหม"
แฟนผมก็ตอบสั้นๆ
"ไปค่ะ"

taxi เอ่ยต่อ "แล้วจะไปเลือกใคร"
แฟนผมเงียบไปพักใหญ่ก่อนตอบว่า
"ไม่บอกค่ะ เขาเตือนกันว่า ช่วงนี้เวลาขึ้น taxi ห้ามคุยเรื่องการเมือง
กลัวถูกไล่ลง"

taxi ก็หัวเราะ แล้วก็บอกว่า
"ผมเป็น taxi ที่มีจริยธรรมนะ
เด็ก,ผู้หญิง,คนแก่ ผมไม่ไล่ลงหรอก"

Related link from Roti

Sunday, April 02, 2006

ไชยันต์ ไชยพร


คำแถลง รศ. ดร. ไชยันต์ ไชยพร หัวหน้าภาควิชาการปกครอง
คณะรัฐศาสตร์ จุฬาลงกรณ์มหาวิทยาลัย

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

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

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

โดยขอปฏิเสธหน้าที่พลเมืองที่กำหนดไว้ในกฎหมายเลือกตั้ง เพื่อรักษาสิทธิเสรีภาพของผมโดยฉีกบัตรเลือกตั้งของผมเองในวันนี้ ซึ่งผมเห็นว่าเป็นหนทางเดียวที่รักษาสิทธิของผมไว้ไม่ให้ถูกบิดเบือนไปเป็นอื่นได้

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

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

นายไชยันต์ ไชยพร
๒ เมษายน ๒๕๔๙

Related link from Roti