Just a blog of Web Developer

Optimize database query ด้วย :include

August 7th, 2009 Posted in My Project, Programming | No Comments »

ในการเขียนโปรแกรมบน Ruby on Rails นั้น เรามักที่จะใช้ ActiveRecord ในการทำหน้าที่เป็น ORM ระหว่างตัว Application กับ database ซื่งทำให้การเรียก Record นั้น สามารถทำได้อย่างง่ายดาย เช่น ถ้าผมจะเรียกดู post ทั้งหมดที่มีอยู่ในระบบ ผมแค่สั่ง

Post.find(:all)    # หรือว่า Post.all ก็ได้ ใน Rails 2.x

ซึ่งตรงนี้ ถ้าเราไปดูใน Log file จะพบว่า ActiveRecord นั้น จะใช้คำสั่งค้นหาข้อมูลประมาณนี้ครับ

  Post Load (0.1ms)   SELECT * FROM "posts"

(ผมใช่ sqlite3 เพราะฉะนั้น table name/field name จะถูกใส่ไว้ใน quote ครับ)

ถ้าสมมุติในโปรแกรมนั้น เราได้ทำ Association ระหว่าง Post และ Comment (Post has many comments) และระหว่าง Comment กับ User (comment belongs to user)

class Post < ActiveRecord::Base
  has_many :comments
 
  # ...
 
end
 
class Comment < ActiveRecord::Base
  belongs_to :post
  belongs_to :user
 
  # ...
 
end

ถ้าเราต้องการจะแสดงผล comment แต่ละอันด้วย เราก็สามารถทำได้โดยเรียกเมธอด #comments ที่ถูกสร้างขึ้นมาอัตโนมัติโดยการทำ association และเช่นเดียวกัน ถ้าเราต้องการแสดงด้วยว่า comment นั้นถูกเขียนโดยใคร เราก็สามารถเรียกเมธอด #user บน comment เช่นกัน

<% Post.find(:all).each do |post| %>
  <!-- display post -->
  <% post.comments.each do |comment| %>
    By: <%= comment.user.username %>
    <!-- display comments -->
  <% end %>
<% end %>

คราวนี้ สมมุติว่าบล็อกเรามีทั้งหมด 10 Post แล้วแต่ละอันมี 5 comment … SQL ที่ออกมานั้น จะเป็นประมาณนี้ครับ

  Post Load (0.1ms)   SELECT * FROM "posts"
  Comment Load (0.5ms)   SELECT * FROM "comments" WHERE ("comments".
"post_id" = '1')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '5')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '24')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '30')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '4')
  CACHE (0.0ms)   SELECT * FROM "users" WHERE ("user"."id" = '5')
  Comment Load (0.3ms)   SELECT * FROM "comments" WHERE ("comments".
"post_id" = '2')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '38')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '14')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '40')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '2')
  User Load (0.2ms)   SELECT * FROM "users" WHERE ("user"."id" = '9')
  ...

จะเห็นได้ว่า ในสถานการณ์ที่แย่ที่สุดนั้น (user ที่มา comment แต่ละ post นั้น ไม่ตรงกันเลย เป็นต้น) ActiveRecord จำเป็นต้องทำการ Query ทั้งหมด 1 + 10 + (10 * 5) = 61 ครั้ง ซึ่งไม่มีประสิทธิภาพเลยครับ เพราะเปลือง Query มากมาย

ดังนั้น เพื่อให้ Query ทั้งหมดนี่มีประสิทธิภาพมากขึ้น ActiveRecord จึงมี key หนึ่งชื่อว่า :include เอาไว้สำหรับสั่งว่าให้ ActiveRecord นั้นทำการโหลด Model ที่ associates กับ object นี้ขึ้นมาด้วยพร้อมๆ กันเลย เพื่อประหยัด Query ครับ เพราะฉะนั้นโค้ดในการค้นหาของเราจะเปลี่ยนเป็น

Post.find(:all, :include => {:comments => :user}).each do |post|
  # ... display post
  post.comments.each do |comment|
    By: <%= comment.user.username %>
    # ... display comments
  end
end

แล้วผลของมันน่ะหรอครับ? 61 query -> 3 queries ครับ!

  Post Load (0.1ms)   SELECT * FROM "posts"
  Comment Load (0.3ms)   SELECT * FROM "comments" WHERE ("comments".
"post_id" IN (1, 2, 3, 4, 5))
  User Load (1.3ms)   SELECT * FROM "users" WHERE ("user"."id" IN (5, 24,
30, 4, 2, 38, 14, 40, 2, 9, 23, 41, 48, 50, 32, 10, 48)

เพราะฉะนั้นการใช้ :include นั้น เป็นการ optimize query อย่างได้ผลทีเดียวละครับ โดยจะเห็นได้ว่าเรายังสามารถโหลด model แบบ nested ได้โดยการใช้ Hash และโหลดโมเดลหลายๆ อันพร้อมกันโดยใช้ Array ครับ อย่างเช่นถ้าเราต้องการโหลด Attachments จาก Comment และโหลด Tags จาก Post ด้วย เราก็สามารถใช้คำสั่งอย่างนี้ได้ครับ

Post.find(:all, :include => [{:comments => [:user, :attachments]}, 
:tags])

คำเตือน: Use it, but don’t abuse it!

ในบางครั้ง การใช้ :include นั้น อาจจะทำให้เวลาในการ query นั้นลดลงได้ถ้าเทียบกับการ query object เล็กๆ หลายๆ ครั้งแทน เพราะฉะนั้นมันไม่ใช่สิ่งที่เวิร์คที่สุดครับ ต้องปรับใช้ให้เข้ากับงานซะมากกว่า โดยที่ผมแนะนำให้ใช้ #find method ตามปกติก่อน แล้วจึงค่อยเพิ่ม :include เข้าไปถ้าเราเห็นว่ามีการ query record จำนวนมากๆครับ … ถือซะว่าการใช้ :include นั้นเป็นการ refactor code ครับ และไม่ใช่สิ่งที่ต้องมาคิดตั้งแต่แรกว่าตรงนี้ต้องใช้มันหรือไม่ ;)

truemove เปิดตัว iPhone 3GS แล้ว

August 4th, 2009 Posted in Garbage | No Comments »

หลังจากที่เมื่อวันก่อน ผมได้โพสไปเรื่องราคาของ iPhone 3GS ที่ว่าคุณปพนธ์ได้กล่าวเอาไว้ว่าราคาของ iPhone ใหม่นั้น ต้องแพงกว่าเครื่องเก่าแน่นอน

ปรากฎว่าหลังจากการเปิดตัวนั้น มันไม่จริงตามที่พูดครับ .. โดยราคาตอนนี้ดูได้ตามนี้เลย:

table.jpg

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

แต่ไปๆ มาๆ ไปเจอคนวิเคราะห์มาว่า การบอกว่า “เครื่องใหม่จะแพงกว่าเครื่องเก่า” ของคุณปพนธ์นั้น กลับอาจจะทำให้ทรูนั้นยังสามารถขายเครื่อง iPhone 3G ได้เรื่อยๆ เพราะคนคิดว่าเครื่องใหม่ยังไงก็ต้องมาในราคาสูงกว่าแน่นอน

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

เพราะฉะนั้น ถ้าตามราคานี้ … ผมว่าผมสนับสนุนให้คนซื้อเครื่องจาก truemove ครับ … แต่ไปใช้ค่ายอื่นนะครับ เพราะว่า edge ของ truemove นี่ ไม่ไหวจริงๆ …

true iPhone 3GS และราคาที่ต่างไป

August 2nd, 2009 Posted in Apple, Garbage | 2 Comments »

หลังจากที่พยายามติดตามข่าวสารเกี่ยวกับการนำเข้า true iPhone 3GS … ตอนนี้ก็เห็นแล้วครับว่าเว็บไซต์ของ truemove นั้นได้ทำการเปลี่ยนเป็นรูปของ iPhone 3GS เรียบร้อยแล้ว

Picture 5.png

ซึ่งตรงนี้ผมว่าเป็นข่าวดี (มั้ง?) กับการที่ true จะนำเครื่องศูนย์เข้ามาขายสักที และทำให้ผู้ที่อยากจะใช้ไม่จำเป็นต้องไปซื้อมาจากต่างประเทศ …

… จนกระทั่งผมเห็นข้อความในกระทู้นี้

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

“iPhone รุ่นใหม่ก็ต้องแพงกว่า (รุ่นเดิมที่ขายตอนนี้) แน่นอน” ปพนธ์กล่าว

ผมยังไม่เคยเห็นเลยครับว่า Apple นั้นมี trend ของการเพิ่มราคาสินค้าที่จะเข้ามาแทนรุ่นเก่า โดยที่ส่วนใหญ่เราจะเห็นกันๆ นั้นก็คือการที่ราคาของเครื่อง Macintosh ที่ราคาแทบจะไม่ลดไม่เพิ่มเลย แม้มีเครื่องรุ่นใหม่เข้ามาก็จะเข้า “มาแทน” เท่านั้น

ซึ่งตรงนี้ ก็เป็นสิ่งที่ Apple กำหนดไว้ตลอด

คราวนี้ ลองมามองทางด้านราคาของ iPhone ที่ทรูกล่าวอ้างว่าราคานั้นต้องขึ้นเพราะเป็นรุ่นใหม่บ้าง ปรากฎว่า .. มันไม่จริงเลยครับว่าเป็นรุ่นใหม่แล้วต้องขึ้นราคา ผมเลยขอยกตัวอย่างจาก Apple Store HK ครับ

เริ่มจากราคาของ iPhone 3G

iphone3g.jpg

ต่อด้วยราคาของ iPhone 3GS

iphone3gs.jpg

จะเห็นได้ว่า ราคานั้นแทบไม่ต่างกันเลย อยู่ในช่วงราคาเดียวกันด้วยซ้ำครับ (ซึ่ง 16GB ที่มาแทน 8GB นี่ จะเห็นได้ว่าราคานั้นถูกลงกว่าเดิมอีก) ซึ่งนั่นก็เป็นมาตรฐานราคาของ Apple ครับ ถือว่าราคาไม่เคลื่อนสักเท่าไร

ดังนั้นตามที่มีข่าวลือว่าราคา 29,000 – 34,000 นั้น … ลองคิดดูดีๆ ครับว่าถ้าทรูออกราคานั้นมาจริง ผมว่าคงจะสู้ไม่ได้กับราคาตู้ตาม MBK และราคาที่ HK แน่นอน … และแน่นอนว่าทรูต้องแย่แน่ๆ กับการทำยอดให้ถึงเป้า ซึ่งผมก็เห็นด้วยตามความเห็นของคุณ NBC ครับ

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

เหลือเวลาอีกไม่ถึง 24 ชม. หวังว่าทางทรูคงจะคิดได้นะครับว่าควรจะทำอย่างไรกับการตั้งราคา … ตั้งราคาไม่สูงมาก เพื่อที่เชียร์ให้คนซื้อเครื่องในประเทศ กับการตั้งราคาพรีเมียมเอากำไรเยอะๆ แต่ต้องเสียส่วนแบ่งไปให้กับประเทศอื่นๆ ที่ขาย iPhone แบบไม่ unlock … ถึงแม้จะมี truewifi แต่อย่างนี้ผมว่าคงช่วยไม่ได้จริงๆ ครับ

ปล. ผมเป็นคนหนึ่งที่เชียร์คนไทยให้ซื้อเครื่องจากทรูครับ เพื่อให้ประเทศของเราเป็นอีกหนึ่งเป้าหมายของ Apple ในการพัฒนาสิ่งต่างๆ ให้ตามทันประเทศอื่นๆ เสียที แต่ถ้าทรูออกราคามาเหมือนกับ “ขโมย” กัน …​ผมก็คงเชียร์ให้ไปซื้อที่อื่นกันครับ

Thin Rolling Restart Patch

August 2nd, 2009 Posted in My Project, Programming | No Comments »

I wrote a patch for Thin web server to do a rolling restart (i.e. restart the server one at a time) when I was working for my final thesis, Localmapia. I put my source on Github, and already inform the developer of Thin. However, seems like he forgot about it.

Anyway, I just updated my code to reflect the edge version of Thin. You can checkout my code from:

http://github.com/sikachu/thin

Also, if you want to install my version of Thin, just do this command to install from source:

git clone git://github.com/sikachu/thin.git
cd thin
rake install

So, to use this feature, you just add onebyone: true into your thin cluster file, or use the flag -O or --onebyone when you’re issue restart command.

I still wishing this would be merged into the main branch. :)

(If you don’t know about Thin, it’s a web server purely written in Ruby. It uses very small memory, very lightweight, yet very powerful. Usually people will use it with Nginx acting as reverse proxy. I wrote this patch because during normal restart Nginx would display 501 gateway error, which this patch solves the problem by having at least 1 old-version running at the time of restart.)

Object#try ใน Rails 2.3

August 2nd, 2009 Posted in My Project, Programming | No Comments »

เคยเจอปัญหาบ้างไหมครับ กับการที่บางครั้ง object ที่เราเรียก method ไปเนี่ย มันกลายเป็น nil ขึ้นมา ทำให้เกิด exception ขึ้นมา

>> @user.username
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.username
	from (irb):1

ซึ่งตรงนี้ เพื่อที่จะหลบ exception ในบางครั้งทำให้ Developer ต้องทำการเช็คก่อนว่า object นั้นเป็น nil หรือไม่ เช่น

>> (@user ? @user.usename : "Guest")
=> "Guest"

ซึ่งตรงนี้ทำให้โค้ดนั้นดูุวุ่นวายมากขึ้น และทำให้โค้ดนั้นดูไม่ค่อยเหมือน Ruby สักเท่าไร (ซึ่งผมก็เห็นด้วยว่า <cond> ? <if-true> : <if-false> นั้น มันดูแปลกๆ) จึงทำให้มีคนคิด Object#try ออกมา แล้วทางทีมผู้พัฒนา Ruby on Rails ถึงเอาเข้าไปเพิ่มใน Rails 2.3 ระหว่างที่ Ruby กำลังรอเพิ่ม method นี้เข้าไปอยู่

เพราะฉะนั้นหลังจากการเพิ่ม method นี้ ทำให้เราสามารถที่จะทำอย่างนี้ได้

>> @user.try(:username)
=> nil
>> @user.try(:username) || "Guest"
=> "Guest"

แล้วยังทำให้ เราสามารถทำ method chaining ได้ด้วย (เพราะทุกอย่างมี #try)

>> @user.try(:username).try(:capitalize)
=> nil
>> # Fetch @user from record
?> @user = User.first
=> #<User id: 1, email: "test@test.com", usename: "test", persistence_token: "95908c3801d55ce389af90f0909192cbda4e37c632afd6b50c2...", created_at: "2009-06-30 05:44:43", updated_at: "2009-07-16 08:56:46", crypted_password: "f37c87cb2d731c0c0710ae9c2b9721d352f6336e38e988bb907...", password_salt: "UIENYMKzDjJYgy2E89Qn", status: nil>
>> @user.try(:username).try(:capitalize)
=> "Test"

ลองปรับไปใช้กับ Application กันนะครับ คิดว่าส่วนนี้น่าจะช่วยให้ debug กันง่ายขึ้นเยอะเลย ;)

Flickr Submit Button CSS Retouch

July 17th, 2009 Posted in News, Programming | No Comments »

วันนี้ได้เข้า Flickr หลังจากไม่ได้เข้าไปหลายวัน
ไปสังเกตเห็นบางอย่าง ตรงแถวๆ comment box

Picture 3-1.png

สังเกตเห็นอะไรไหมครับ?

นี่คือการ Retouch ปุ่ม Submit ในส่วนของการเพิ่ม comment ครับ .. ซึ่งผมว่าเป็นจุดที่ทำให้เว็บดูสวยขึ้นมาอีกหน่อยเลยทีเดียว เพราะว่าถ้าเทียบกันกับอันเก่า ..

breadkrumbs-add-1.png

ต้องบอกกับทีมงาน Flickr ว่า .. น่าจะทำใหม่ได้ตั้งนานแล้ววว ;)

McDonald Drive-Thru

July 15th, 2009 Posted in Garbage | 1 Comment »

เมื่อก่อนตอนผมยังอยู่ที่จรัญสนิทวงศ์ เวลาผมจะเดินทางโดยใช้ MRT ในเวลาเช้าๆ ผมกับแฟนมักจะไปกินอาหารเช้ากันที่ McDonald สาขาเมเจอร์อเวนิว เพราะว่าเป็นทางผ่านในการเดินทางไปจอดรถที่ MRT ลาดพร้าว

IMG_9582.JPG

ผมมักจะใช้บริการ Drive-Thru ในการสั่งอาหาร เพราะว่ามันเร็วดี ไม่ต้องจอดรถแล้วลงไปให้เสียเวลา … แต่ก็มีหลายครั้งเหมือนกันครับ ที่มันน่าเคืองงงงงง เลยขอมาระบายให้ฟังหน่อยครับ

ครั้งแรก วันนั้นผมกับแฟนต้องไปสัมภาษณ์งานที่ในเมือง ทำให้เราตัดสินใจขึ้นรถไฟใต้ดินกันดีกว่า ก็แหม .. รถในเมืองนี่มันออกจะโล่ง ขับสบายซะขนาดนั้น (ประชด -*-)

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

ปรากฎว่าพอถึงที่จอดรถ .. แกะห่อกระดาษออกมา มันดันไม่มีซามูไรเบอร์เกอร์หมู (- -!)

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

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

แต่เรื่องนี้มันยังไม่จบครับ (ฮา)

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

ได้ครบตามจำนวนครับ แต่ว่า … มันเป็น แมคฟิช สองชิ้นเลย -*-

รอบสองนี้แบบว่าแอบจี๊ดเลยครับ -*- เพราะว่า มันไม่ใช่เรื่องที่น่าพลาดแล้วนะเนี่ย แมคฟิช กับ ซามูไรเบอร์เกอร์หมู + ดับเบิ้ลชีสเบอร์เกอร​์ นี่มันคนละเรื่องกันเลยนะเนี่ย ผมก็เลยคิดว่าเขาน่าจะหยิบสลับกับของรถคันข้างหน้าผม ซึ่งตรงนี้ผมก็ไม่รู้เหมือนกันว่าเขาจดถูกหรือเปล่า เพราะว่าผมจำไม่ได้ว่าผมดูบิลแล้วนะครับ ..

สองครั้งสองครา .. อุทาหรณ์ของการสั่งอาหารแบบ Drive-Thru มีอยู่ข้อเดียวครับ … นั่นคือ “ตรวจเช็คเบอร์เกอร์และเฟรนช์ฟรายส์ก่อนออกจากจุดรับอาหาร … เพราะว่าหลังจากคุณขับออกไปจากตรงนั้นแล้ว ส่วนใหญ่คุณก็จะไม่ได้ผ่านกลับมาตรงนั้นอีกเลย .. หรือถึงขับกลับเข้ามาได้มันก็ไม่คุ้มครับ ต้องเสียเวลามาอธิบายอีกว่าเกิดอะไรขึ้น ถ้าพนักงานคนที่คุณรับอาหารด้วยเขาออกจากกะแล้ว ยิ่งแล้วไปใหญ่เลย

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

ไปกินมอสเบอร์เกอร์ดีกว่า (ฮา …) เมื่อไรจะมาเปิดที่ซีคอนน๊ออออออ ~_~

เนกิมะ เล่มที่ 24

July 15th, 2009 Posted in Comic | 1 Comment »

d2785e5300.jpg

[ลงปกภาคภาษาญี่ปุ่น เพราะว่าภาษาไทยไม่เข้าใจว่าจะติดลายน้ำกันทำไม .. ตรูขี้เกียจสแกนเฟร้ย!]

(สปอยหน่อยๆ นะครับ อย่าว่ากัน … ของมันมันส์!)

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

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

ในเล่มนี้เรายังได้เห็นความสามารถของเนกิ หลังจากที่ได้ฝึกฝนมนต์แห่งความมืดไปตั้งแต่เล่มที่แล้ว ซึ่งดูแล้วเจ้าหนูเนกิทำได้ไม่เลวเลย แต่ก็ไม่รู้เหมือนกันว่ามันจะเพียงพอที่จะโค่นโค่นล้มคนที่เนกินับเป็นศัตรูอย่าง “เฟท อาเวอรังคัส” ได้หรือไม่ … หลังจากที่ทั้งสองนั้นได้พบเจอกันอีกครั้งโดยบังเอิญในเล่มนี้

สำหรับคนที่ไม่ได้อ่านเล่มนี้ … ผมแนะนำว่ารอซื้อตอนเล่ม 25 ออกเลย จะดีกว่าครับ -*- เนื้อเรื่องตอนท้ายมันค้างคาใจมากๆ ค้างมากกว่า 23 -> 24 อีกครับ ที่เหมือนจะจบไปในเล่ม … ไม่อย่างนั้นก็คงต้องมาลุ้นกันว่าทาง VBK เมื่อไหร่จะพิมพ์รวมเล่มสักที -*-

จบกันดื้อๆ อย่างนี้แหละ ๕๕๕๕

Ruby และ active_support/whiny_nil

June 29th, 2009 Posted in My Project, Programming | No Comments »

สำหรับนักพัฒนาส่วนใหญ่ที่เริ่มเขียน Ruby on Rails คาดว่าตอนนี้ในเครื่องของทุกๆ คน น่าจะลง Ruby 1.8 อยู่ เนื่องจากยังคงมี Gem หลายๆ ตัว ที่ยังไม่รองรับ Ruby 1.9 และทำให้เกิดปัญหาทางด้านความเข้ากันได้อยู่บ้าง ฉะนั้นผมเลยอยากพูดถึงหลุมพรางที่ Ruby 1.8 ได้ทิ้งเอาไว้ และทำให้หลายๆ คนนั้นพลาดตกหลุมกันไปบ้างครับ

ผมขอสมมุติเอาไว้ว่า ผมได้สร้างระบบ Blog แห่งหนึ่ง โดยที่มี Model สามตัวคือ Post เอาไว้เก็บข้อความ Comment เอาไว้เก็บความคิดเห็น และ User เอาไว้เก็บชื่อผู้ใช้ ที่สามารถแก้ไขข้อความได้ครับ

สมมุติว่า User ที่สามารถเข้ามาแก้ไขได้นี้ มี id = 4 ผมก็เลยทำการ hard-coded เอาไว้ในโปรแกรมเลย เป็น filter ว่า

class PostController < ApplicationController
  before_filter :load_user
  before_filter :check_authorization!, :except => [:show, :index]
 
  # ... controller actions
 
  private
 
  def check_authorization!
    render :text => "Unauthorized!", :status => 401 unless @user.id == 4
  end
end

คราวนี้ผมก็ลองรันดู ปรากฎว่าเจอปัญหาว่าถ้าคนที่ไม่ได้เป็นสมาชิกเข้ามาที่บล็อก มันจะเกิด exception ขึ้นมา เพราะผมไปเรียก id method บน nil object ผมก็เลยจัดการ catch exception อย่างนี้ครับ

  def check_authorization!
    render :text => "Unauthorized!", :status => 401 unless (@user.id == 4 rescue false)
  end

โออ คราวนี้โปรแกรมผมหล่อกิ๊งเลย ไม่พบว่าเกิดปัญหาอะไร ผมก็เลยจัดการ deploy ขึ้นไปบนเว็บเสร็จสรรพ สร้าง user ให้เหมือนกับบนเครื่อง Development ทุกประการ และให้ User ที่มี id = 4 สามารถแก้ไขโพสได้เป็นคนเดียวเช่นเคย

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

หลังจากการตามหาบักมานานแสนนาน .. ผมก็ขอเข้าเรื่องของ whiny_nil เลยละกันครับ

ในตัว active_support ที่ติดมากับ Ruby on Rails นั้น มีไฟล์อยู่อันหนึ่งชื่อว่า whiny_nil.rb ซึ่งไฟล์นี้เป็นส่วนของ Core Extension ซึ่งทำให้การเรียกใช้เมธอด #id บน object ที่เป็น nil นั้น จะมีการโยน RuntimeError ออกมาบอกว่าเรากำลังทำการเรียกใช้ #id บน nil อยู่

Sikachus-Notebook:rails sikachu$ script/console
Loading development environment (Rails 2.3.2)
>> nil.id
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
	from (irb):1
>>

ซึ่งตรงนี้ นักพัฒนาบางคนก็จะใช้วิธีการ rescue RuntimeError ไป เพราะคิดเอาว่าถ้าเผลอไปเรียก #id บน object ที่เป็น nil จริง มันก็ต้องโยน RuntimeError ออกมาบอกเรา ถูกไหมครับ

คำตอบคือ ผิดถนัด ครับ เพราะบน Production environment นั้น เจ้าตัว whiny_nil นั้นจะไม่ถูกเปิดใช้ครับ แล้วผลของการไม่ได้เปิดใช้หรอครับ?

Sikachus-Notebook:~ sikachu$ irb
irb(main):001:0> nil.id
(irb):1: warning: Object#id will be deprecated; use Object#object_id
=> 4

นั่นแหละครับ! การที่เรียกเมธอด #id บน nil นั้น Ruby จะคืนค่าเป็น 4 ครับ เพราะว่า nil นั้นมี id ของตัวมันเองคือ 4 ครับ :)

เพราะฉะนั้นคงเดาออกใช่ไหมครับว่าทำไมคนที่ไม่ได้ล็อกอินทุกคน ถึงสามารถเข้าไปแก้ไขบล็อกของผมได้? ก็เพราะว่า @user.id ของเขา คืนค่าเป็น 4 กันทุกคนเลยครับ ไม่ได้มีการโยน RuntimeError แต่อย่างใด

ปัญหานี้ใช่ว่าไม่มีทางแก้ครับ แต่ผมจะไม่ขอเจาะลึกลงไปแล้วกันครับ เพราะว่าปัญหานี้หายไปแล้วใน Ruby 1.9.1 (object#id deprecated ไปแล้วครับ) ก็ต้องรอให้มันถูกใช้แพร่หลายเท่านั้นล่ะครับ ซึ่งตอนนี้วิธีการแก้ก็คงเป็น

  • เปิดการใช้งาน config.whiny_nil ใน production.rb
  • ใช้ object.try(:id) ที่จะคืนค่ามาเป็น nil หากว่า object เป็น nil
  • เขียนโค้ดใหม่โดยพยายามไม่เช็คจาก #id

หวังว่าโพสนี้จะทำให้หลายคนหายข้องใจได้บ้างนะครับ

ปล. บางท่านคงสงสัยใช้ไหมครับว่าแล้วทำไม nil.id หรือ nil.object_id มันถึง return เป็น 4 … เพราะว่า Ruby ทุกอย่างมันเป็น object ครับ ไม่เว้นแม่กระทั่ง nil! ไม่เชื่อลองดูนี่นะครับ

irb(main):002:0> nil.object_id
=> 4
irb(main):003:0> nil.class
=> NilClass
irb(main):004:0> 100.class
=> Fixnum

ใครจะชอบไม่ชอบผมไม่รู้ แต่ขอเอวังด้วยประกาลฉะนี้ :)

Battery ใหม่กิ๊ง ~

June 29th, 2009 Posted in Apple, My Project | No Comments »

หลังจากที่รอมานานแสนนาน ที่จะเก็บเงินซื้อ Battery ใหม่ ของ Macbook Pro 15” ซึ่งยังงั้ยยังไง ก็ไม่สามารถตัดใจซื้อได้สักที เพราะด้วยราคามหาโหด (6,xxx บาท) .. ทำให้โปรแกรมเมอร์น้อยๆ ได้แต่ทนใช้ battery ก้อนเก่าไป (ซึ่งจริงๆ ก็เป็นของ @lukeinth ซะด้วย – -”)

แต่แล้วคุณเจ้านาย (@trawut) ก็ได้โปรดเมตตา ซื้อแบตก้อนใหม่มาให้เป็นของขวัญ !!

(แต่รู้สึกว่าจะไม่เนียนเลยนะครับของขวัญนี่ … เพราะตอนแรก @hunt บอกว่า @trawut ซื้อมาให้เพราะบีฝากซื้อ จนตอนกลางคืนถึงบอกว่าไม่ได้ซื้อมาฝาก ทำอกสั่นขวัญแขวน กลัวเสียเงินไปใหญ่ -*-)

ไหนๆ ก็ได้แบตก้อนใหม่แล้ว เลยเอามาให้ดูก่อนการ Calibrate คร๊าบบ

before.png

after.png

coco.png

เวลาเห็น Coconut Battery รายงานว่า 100% แล้วรู้สึกดีพิลึก!

ลองเล่นดู ปรากฎว่าได้ระยะเวลาประมาณ 3 ชม. กว่าๆ ซึ่งก็ถือว่าเยอะแล้วนะครับสำหรับเครื่องรุ่นนี้ ก็แปลว่ารับได้ละกัน ๕๕๕๕

ขอบคุณคุง @trawut มากนะขอรับ ๕๕๕๕