Saturday, February 25, 2006

มายา

ดูแล้วรู้สึกว่า โลกปัจจุบัน เรามีแต่สิ่งลวงตาเต็มไปหมด
FluidEffect
เข้าไปที่ portfolio->before/after

Related link from Roti

Rails "with_scope"

ใน ActiveRecord มีคำสั่งหนึ่งที่ชื่อ with_scope
คำสั่งนี้ช่วยให้เราจัดการกับ business logic ประเภท

user A ที่สังกัดหน่วยงาน 1001 เมื่อ login เข้ามาแล้ว
สามารถ query ข้อมูลได้เฉพาะข้อมูลของหน่วยงาน 1001 เท่านั้น
และเมื่อสร้าง transaction,
transaction นั้นจะมีรหัสหน่วยงานที่รับผิดชอบเป็น 1001

หรือไม่ก็

User B มี authorize แบบ Regulator
ดังนั้นจึงสามารถ grant สิทธิให้กับเฉพาะ user
ที่สังกัดอยู่ภายใต้ Company ที่ user B ดูแลอยู่


ลองดู controller code ของตัวอย่างที่ 1
กรณีที่เขียนแบบไม่มี with_scope

def list
@txs = Tx.find(:all,
:conditions =>
["organize_id = ?", session[:user].organize_id])
end

def create
...
# tx ที่สร้างจะสังกัดหน่วยงาน ตามหน่วยงานของ user
tx.organize_id = session[:user].organize_id
...
tx.save
...
end


ถ้าใช้ with_scope เข้ามาช่วย

def list
Tx.with_scope(:condition => "organize_id = #{session[:user].organize_id}") do
@txs = tx.find(:all)
end
end

def create
Tx.with_scope(:create => {:organize_id = session[:user].organize_id}) do
...
Tx.save
end
end


ถ้าให้สวยขึ้นอีก ก็ให้แยกส่วนที่ซ้ำๆออกมา

protected
def org_scope
{
:find => {:condition => "organize_id = #{session[:user].organize_id}",
:create => {:organize_id = session[:user].organize_id}
}
end

public
def list
Tx.with_scope(org_scope) do
Tx.find(:all)
end
end

def create
Tx.with_scope(org_scope) do
...
end
end


Note: เริ่มพบว่า Rails api เริ่มไม่ consistency เยอะขึ้นเรื่อยๆ เช่น
กรณี find โดยตรงเราสามารถทำแบบนี้ได้

@txs = Tx.find(:all,
:conditions =>
["organize_id = ?", @org_id])

แต่ถ้าใช้ผ่าน with_scope จะ call แบบนี้ไม่ได้

Tx.with_scope(:find =>
{:conditions =>
["organize_id = ?", @org_id]}) do
Tx.find(:all)
end


สาเหตุก็คือ ใน method add_conditions ใน file "base.rb" ของ ActiveRecord
มีการใช้ sanitize_sql (method ที่รับผิดชอบการแปลงแบบที่กล่าวข้างบน) เฉพาะกับ
:condition ที่ส่งมากับคำสั่ง find เท่านั้น
ไม่ได้ใช้กับ :condition ใน with_scope ด้วย

940 def add_conditions!(sql, conditions)
941 puts "add_conditions #{sql}, #{conditions}"
942 segments = [scope(:find, :conditions)]
943 segments << sanitize_sql(conditions) unless conditions.nil?
944 segments << type_condition unless descends_from_active_record?
945 segments.compact!
946 sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
947 end

ทางแก้ ก็อาจจะ patch base.rb
เพิ่ม sanitize_sql เข้าไปครอบส่วน scope

940 def add_conditions!(sql, conditions)
941 puts "add_conditions #{sql}, #{conditions}"
942 segments = [sanitize_sql(scope(:find, :conditions))]
943 segments << sanitize_sql(conditions) unless conditions.nil?
944 segments << type_condition unless descends_from_active_record?
945 segments.compact!
946 sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
947 end


Note: เข้าไป check source code ของ Rails
มีคน patch ส่วนแก้ไขเข้าไปแล้ว อยู่ใน change set [3379]

Related link from Roti

Wednesday, February 22, 2006

Testing with Hansel

Hansel คือ code coverage ตัวหนึ่ง
จุดที่ผมชอบ ก็คือ วิธีที่มัน integrate กับ JUnit ซึ่งดูเรียบง่ายดี

ลองดูตัวอย่าง
ทดลองเขียน Collection ขึ้นมาอันหนึ่ง ที่เก็บ integer
โดย item ใน collection จะเรียงลำดับจากน้อยไปมากเสมอ
public class OrderList {

private ArrayList bag;

public OrderList() {
bag = new ArrayList();
}

public void add(int value) {
int size = bag.size();
boolean found = false;
for (int i = 0; i < size; i++) {
Integer elm = (Integer) bag.get(i);
if (value < elm.intValue()) {
if (i == size -1) {
bag.add(new Integer(value));
} else {
bag.add(i, new Integer(value));
}
found = true;
break;
}
}
if (! found) {
bag.add(new Integer(value));
}
}

public int first() {
return ((Integer) bag.get(0)).intValue();
}

public int last() {
return ((Integer) bag.get(bag.size() - 1)).intValue();
}
}


เขียน JUnit Testcase
public class TestOrderList extends TestCase {

/*
* Test method for 'util.OrderList.first()'
*/

public void testFirst() {
OrderList l = new OrderList();
l.add(6);
l.add(9);
l.add(1);
assertEquals(1, l.first());

}

/*
* Test method for 'util.OrderList.last()'
*/

public void testLast() {
OrderList l = new OrderList();
l.add(6);
l.add(9);
l.add(1);
assertEquals(9, l.last());

}

}


ทดลอง run ดู ก็จะพบแถบเขียวสวยงาม



ที่นี้ลองใส่ Hansel เข้าไป
โดยการเพิ่ม method นี้ลงไปใน test case
    public static Test suite() {
return new CoverageDecorator(TestOrderList.class,
new Class[] {OrderList.class});
}


ทดลอง run ใหม่



จะเห็น message error ว่า
 Coverage failure: Branch not completely covered. Condition 'i == size - 1' is not fulfilled.
at util.OrderList.add(OrderList.java:20)


ไปไล่โปรแกรมแล้ว จะพบที่ผิดตัวเบ้อเริ่มเลย
บรรทัดที่ 20 เป็นบรรทัดที่ตกค้างจากการที่เขียน code โดยไม่ได้วางแผนมาก่อน
สามารถตัดทิ้งออกไปได้ทั้งยวงเลย

จะเห็นว่า Hansel เข้ามาช่วยอุดจุดอ่อน
ของ Programer ที่ไม่ชอบวางแผน ชอบเขียนโปรแกรมไป-คิดไป-แก้ไป
(แก้ไปแก้มา จนงงเอง) ได้เป็นอย่างดี

หลักการทำงานของ Hansel ก็คือ มันจะทำการ modify class file ที่เราต้องการ test
โดยใช้ BCEL (Byte Code Engineering Library)
ทำการใส่ probe เข้าไปตามบรรทัดต่างๆ แล้ว check ดูว่ามีการเรียกใช้ probe เหล่านั้นหรือไม่
การใส่ probe ของ Hansel ก็ไม่ได้ใส่ดะไปทุก statement นะ
มีการเลือกใส่เฉพาะ statement ที่เป็น branch เท่านั้น

Related link from Roti

Tuesday, February 21, 2006

Sea of Lies

วันนี้ดูสารคดีเรื่องเครื่องบินของสายการบิน iran air ที่ถูกยิงตกโดยเรือรบสหรัฐ เมื่อวันที่ 3 กรกฏาคม 1988
ผู้โดยสารและลูกเรือเสียชีวิตไป 290 คน
สาเหตุเกิดจากความผิดผลาดของลูกเรือและกัปตัน
ในการจำแนกระหว่างเครื่องบิน F14 กับ airbus
"Identify, Friend or Foe?" query, he received a different response: military aircraft. Rogers' decision to fire was made while under the impression that the query was correct--in fact, Anderson had forgotten to reset the system after the first query, and the response he received was probably from a fighter plane on the runway back at Bandar Abbas.

รวมทั้งอาการ panic ของลูกเรือบางคน
Then something happened that psychologists call "scenario fulfillment" - you see what you expect. Petty Officers Anderson and Leach both began singing out that the aircraft, now definitively tagged on the big screen as an F-14, was descending and picking up speed. The tapes of the CIC's data later showed no such thing. Anderson's screen showed that the plane was travelling 380 knots at 12,000 feet and climbing. Yet Anderson was shouting out that the speed was 455 knots, the altitude 7,800 feet and descending.

โดยที่ระหว่างที่ยิง เรือรบสหรัฐกำลังอยู่ในน่านน้ำอิหร่านอีกด้วย

เมื่อเรือกลับถึงอเมริกา ก็ได้รับการต้อนรับเยี่ยงวีรบุรุษที่กลับจากสงคราม
ลูกเรือทั้งหมดได้รับ Combat-action ribbons
commander ผู้ควบคุมการต่อสู้ทางอากาศ ได้รับ navy 's Commendation Medal
(for heroic achievement, "ability to maintain his poise and confidence under fire",
or his ability to "quickly and precisely complete the firing procedure"")

แน่นอนกัปตันต้องถูกขึ้นศาลทหาร
ผลการสอบสวน ถือว่าปฎิบัติโดยชอบแล้ว (justifiable self-defense)

อ่านเหตุการณ์ทั้งหมด
Sea of Lies

ปล. สำหรับเติือนสติ ผู้ที่ชอบ Hero แบบในหนัง hollywood
และถูกสะกดจิตว่า ทหารอเมริกันมันเก่งแบบในหนัง

Related link from Roti

Selenium on Rails

ดู demo ของการ integrate
Selenium กับ Rails ได้ที่นี่
Show don't Tell
In the demo I create a new Rails project, install Selenium on Rails, create a test case using Selenium IDE, create another in RSelenese, and run all the test as a Rake task.


ช่วงนี้ผมเริ่มใช้ selenium กับ java project แล้ว
พอมาดูการใช้กับ Rails แล้ว
รู้สึกว่ามัน integrate เข้ากับ Rails ได้ดีมากเลย
(ไม่ว่าจะเป็นการเก็บ test script ไว้ใต้ directory test,
การใช้ script/generate เข้ามาสร้าง stub,
การใช้ rake test,
การใช้ ruby selenium script)

Related link from Roti

จบ trip จักรยาน

จบ trip จักรยานครั้งนี้ ด้วยความรู้สึกยากจะบรรยาย
ความรู้สึกเหมือนได้สัมผัสทั้งสวรรค์ และ นรก คละเคล้ากันไป
เส้นทางที่ขี่คราวนี้ อยู่ในจังหวัดน่านจังหวัดเดียว

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

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


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

Note:
  • ช่วงเวลาที่สุข มักจะน้อยกว่า ช่วงเวลาที่ทุกข์ เสมอ
  • พื้นที่ 75% ของจังหวัดน่าน เป็นภูเขา

Related link from Roti

Monday, February 20, 2006

Visualizing Regular Expression
อ่านวิธีการที่เขา implement
น่าสนใจทีเดียว

Note: เขาใช้ OpenLazlo implement ด้วย

Related link from Roti