หน้าเว็บ

วันศุกร์ที่ 29 กรกฎาคม พ.ศ. 2559

การเรียกใช้ render พร้อมกับส่ง instance ของโมเดล





สมมติว่าเรามีโมเดล Book และ Review ที่ associate กันอยู่
แล้วเราต้องการแสดงข้อมูลใน Review จากหน้า show ของ Book โดยโค้ดของหน้า show มีหน้าตาประมาณนี้
/app/views/books/show.html.erb


<$= render @book.reviews %>
เมื่อใช้ render แบบนี้ สิ่งที่เกิดขึ้นคือ

  1. ไฟล์ partial ของ Review จะถูกเรียกขึ้นมารัน เพราะมีการใส่ argument เป็น instance ของโมเดล Reviews ลงไป (ซึ่งก็คือ @book.reviews) ในที่นี้ไฟล์ partial ที่ถูเรียกจะเป็น /app/views/reviews/_review.html.erb
  2. Rails จะสร้างตัวแปร local ที่มีชื่อดียวกับ partial เอาไว้โดยอัตโนมัติ (ไม่นับเครื่องหมายขึดล่าง ในที่นี้ไฟล์ partial ชื่อ _review.html.erb จะมีตัวแปรชื่อ review) ค่าของตัวแปร local ที่ Rails สร้างขึ้นจะอ้างอิงไปยัง instance ของโมเดลที่ใส่เข้ามาตอนเรียกใช้ render (ในที่นี้ก็คือ อ็อบเจกต์ @book.reviews ซึงเป็น instance ของโมเดล Review )
การแสดงผลในไฟล์ partial จึงเขียนออกมาได้แบบนี้

/app/views/reviews/_review.html.erb
<%= review.rating %>
<%= review.rating %>

รายละเอียดเพิ่มเติมอยู่ในหัวข้อ 3.4.4 Passing Local Variables จากหน้า Rails Guide - Layout and Randering ครับ

วันอังคารที่ 19 กรกฎาคม พ.ศ. 2559

ลองใช้เมธอดใน ActionView::Helpers จาก rails console



ActionView::Helpers เป็นโมดูลที่มีเมธอดที่เกี่ยวกับการแสดงผลใน view อยู่เยอะมาก แต่ละเมธอดนี่มีลักษณะเป็น “Magic” แทบทั้งนั้น
ผมเป็นคนนึงแหละที่ไม่คุ้น และไม่ค่อยชอบเท่าไหร่กับความเป็น “Magic” ที่ถูกออกแบบมาแบบนั้น เอาซะเลย
เกิดความรู้สึกอึดอัดทุกครั้งที่จะเรียกใช้เมธอดแต่ละที ถึงแม้ว่ามันจะเป็นเมธอดง่ายๆ อย่าง link_to หรือ image_tag ก็ตาม
แต่ทำไงได้ ถ้าคิดจะใช้ Rails เราก็หนีไม่พ้นที่จะต้องเรียกใช้เมธอดเหล่านี้ อย่างหลีกเลี่ยงไม่ได้

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

มีความรู้สึกว่า คิดถูกแล้ว ที่ยอมเสียเวลาค้นหาความจริง เพราะว่าหลังจากที่ไปไล่ดู document ใน api.rubyonrails.org ก็พบว่า document นั้นทำไว้ค่อนข้างดีถึงดีมาก มีคำอธิบายรายละเอียดพร้อมตัวอย่างของแต่ละเมธอดไว้ครบถ้วน

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

เมฆหมอกของความกลัวและความไม่รู้ ค่อยๆ จางลงไป ความเข้าใจค่อยๆ เข้ามาแทนที่

พอค้นคว้าเพิ่มอีกเล็กน้อยก็รู้ว่าเราสามารถ ทดลองเล่น ทดลองใช้ เมธอดพวกนี้ผ่านทาง Rails Console ได้เลย โดยใช้เรียกใช้ผ่านอ็อบเจกต์ helper ดังตัวอย่างด้านล่าง

$rails console
Loading development environment (Rails 5.0.0)
irb(main):001:0> helper.tag("br")
=> "
"
irb(main):002:0>


อีกตัวอย่างที่ผมทดลองเล่นใน console โดยลองเพิ่ม srcet เข้าไปเวลาเรียกใช้เมธอด image_tag ซึ่งทำแบบถึกๆ ได้ดังนี้

irb(main):017:0>helper.image_tag(“photo.jpg”, {class: “photo”, srcset: "#{helper.image_path("photo-small.jpg")} 480w"})
=> "< img class=\"photo\" srcset=\"/images/photo-small.jpg 480w\" src=\"/images/photo.jpg\" alt=\"Photo\" />"
irb(main):018:0>


ลองเล่นดูนะครับ

วันอาทิตย์ที่ 8 พฤษภาคม พ.ศ. 2559

แก้ปัญหารันคำสั่ง rails แล้วเจอ error ว่า TZInfo::DataSourceNotFound

วันนี้เจอปัญหาสั่ง rails server แล้วขึ้น error "TZInfo::DataSourceNotFound"
เราใช้ Rails 4.1.15, Ruby 2.1.7

หลังจากหาข้อมูลในกูเกิล ก็เจอวิธีแก้ไขจาก กระทู้บน stackoverflow อันนี้

ปัญหาเกิดจากการที่เราให้ windows 64bits ซึ่ง ไม่มีข้อมูลของ zoneinfo ที่ gem tzinfo-data เรียกหา
วิธีการแก้ไขทำได้โดยการใส่ :x64_mingw ลงไปเพื่อบอก tzinfo-data ว่าเราใช้ windows 64 bits ซึ่งตรงนี้ทำได้โดยการแก้ไขบรรทัดที่มีการเรียก tzinfo-data ใน Gemfile โดยแก้ให้เป็น

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]

จากนั้นให้รัน bundle update ก็เป็นอันเรียบร้อย

วันจันทร์ที่ 12 มกราคม พ.ศ. 2558

Ruby Weekly จดหมายข่าว รายสัปดาห์

Ruby Weekly เป็นข่าวรายสัปดาห์ทาง email ซึ่งผมเป็นสมาชิกอยู่ซักพักใหญ่ๆ ละ มีข่าวสารอัพเดต, Tip, Blogpost เรื่องเกี่ยวกับ Ruby และยังรวมถึงตำแหน่งงานว่างสำหรับ Ruby developer ด้วย

ผมว่ามันเป็นจดหมายข่าวที่ดีทีเดียวเลย

เพื่อนๆ สามารถเข้าไป subscribe ได้ฟรีตาม link นี้ครับ

นอกจาก Ruby Weekly แลัว ยังมีจดหมายข่าวของสาย web app อื่นๆ ให้ subscribe ด้วยเช่น HTML5, javascript รับประกันคถณภาพโดยคุณ Peter Cooper คนดัง สะใจกันไปเลย

วันอังคารที่ 28 ตุลาคม พ.ศ. 2557

โชว์ข้อมูลดีบักสำหรับ Rails

เราสามารถเรียกดูข้อมูลในตัวแปรต่างๆ ที่ใช้อยู่ใน Rails ได้โดยใช้เมธอดพิเศษของ Rails ซึ่งมีอยู่ 3 เมธอด ได้แก่


1. เมธอด debug
2. เมธอด to_yaml
3. เมธอด inspect

ทั้งสามแบบใช้งานคล้ายๆ กัน โดยเราสามารถเรียกใช้เมธอดนี้แล้วใส่ตัวแปรที่เราอยากตรวจสอบลงไปเป็น argument โดย debug และ to_yaml จะให้ผลลัพธ์คล้ายๆ กันจะต่างกันตรงที่ข้อมูลของตัวแปรที่เราตรวจสอบด้วย to_yaml จะอยู่ใน format ของ yaml เลย ในขณะที่ debug จะเป็นข้อความที่จัดเรียงให้อ่านง่ายกว่า ส่วน inspect นั้น ให้ผลลัพธ์เหมือนกับที่เราใช้ inspect ใน Ruby เลย

วิธีการนำไปใช้ก็ง่ายมาก คือเราสามารถเรียกเมธอดทั้ง 3 ตัวนี้ได้จาก view (ไฟล์ .html.erb) แล้วให้มันแสดงผลออกมาทางหน้าเพจเลย

<!DOCTYPE html>
<html>
<head>
  <title>Depot</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>
<%= debug(params) %>
</body>
</html>



จากโค้ดตัวอย่าง เราเรียกเมธอด debug จากหน้า layout (ไฟล์ app/views/application.html.erb) โดยตัวแปรที่เราต้องการตรวจสอบในที่นี้คือ param
ผลลัพธ์ที่ได้จะเป็นข้อความดีบัก ซึ่งมีหน้าตาประมาณนี้



ลองใช้ดูละกันครับ

วันพฤหัสบดีที่ 9 ตุลาคม พ.ศ. 2557

Method lookup และ super

ทุ
กครั้งที่ message ถูกส่งไปยังผู้รับ (receiver) Ruby จะทำการตรวจสอบทันทีว่าภายในคลาสต้นสังกัดของอ็อบเจกต์ที่เป็นผู้รับ message นั้นมีเมธอดที่มีชื่อเดียวกันกับชื่อของ message ที่ถูกส่งเข้ามาหรือไม่
กระบวนการค้นหาเมธอดที่มีชื่อเดียวกับ message นั้น หากค้นหากันภายในคลาสของผู้รับแล้วไม่เจอเมธอดที่มีชื่อเดียวกับ message Ruby จะทำการค้นหาต่อไปยัง โมดูลที่คลาสนั้นผนวก (include) เข้ามา รวมถึงคลาสแม่ทุกๆ คลาสที่สืบทอดต่อกันมา เรื่อยไปขึ้นไปจนถึงคลาส BasicObject ซึ่งเป็นคลาสแม่ลำดับบนสุดของทุกๆ คลาส กันเลยทีเดียว 
เรื่องของลำดับขั้นในการค้นหาเมธอดของอ็อบเจกต์เมื่อตัวมันได้รับ message นั้นถือว่ามีความสำคัญไม่น้อย เพราะนอกจากจะช่วยให้เราแยกแยะการทำงานของอ็อบเจกต์และเมธอดได้แล้ว ยังช่วยให้เราเข้าใจถึงการ override ของ Ruby อีกด้วย

สมมติว่าเรากำหนดให้คลาส A, คลาส B และ โมดูล M มีความสัมพันธ์กันตามโค้ดต่อไปนี้

module M
  def print
    puts “this is print method”
  end
end

class A
  include M

วันศุกร์ที่ 12 กันยายน พ.ศ. 2557

เทคนิคการแปลงข้อมูลโดยใช้ Type Conversion

R
uby มีความสามารถที่เรียกว่า built-in conversion ที่ช่วยให้เมธอดสามารถ เปลี่ยน (convert) อ็อบเจกต์ที่ป้อนเข้ามา ให้เป็นค่าของข้อมูลพื้นฐานอย่างเช่น ตัวเลข สตริงหรืออาร์เรย์ ตามที่เมธอดนั้นคาดหวังได้<br /> คำอธิบายข้างต้นอาจทำให้ไม่เห็นภาพ ลองดูตัวอย่างตามนี้ ครับ<br /> สมมติว่าเรามี อาร์เรย์ grade ซึ่งประกอบด้วยชื่อระดับราคาและความหรูหราของโรงแรม ตั้งแต่ โมเต็ล จนถึง โรงแรมระดับลักเซอร์รี่ เราต้องการแสดงข้อความที่ระบุชื่อของโรงแรมจากอ็อบเจกต์ hotel และพร้อมบอกระดับชั้น ตามจำนวนของดาว ซึ่งแทนด้วย rating ที่เป็น attribute ของ hotel โดย rating จะเป็นตัวเลข มีค่าตั้งแต่ 0 ถึง 5 เราสามารถสร้างอ็อบเจกต์ hotel ง่ายๆ ได้โดยใช้ Struct ดังนี้

Hotel = Struct.new(:name, :rating, :address)
h1 = Hotel.new("Sweet Inn", 1, "Bangkok")
h2 = Hotel.new("Centrara Grand", 5, "Phuket")

กำหนดให้ระดับมาตรฐานและบริการ (ความไฮโซ) ของ hotel อยู่ในอาร์เรย์ที่ชื่อ grade

grade = ["Motel", "Bed and breakfasts", "Contel", "Boutique", "Resort", "Luxury"]

เราสามารถแสดงข้อความที่ต้องการได้ดังนี้

วันพฤหัสบดีที่ 21 สิงหาคม พ.ศ. 2557

เก็บตกเทคนิคการใช้ irb

ก็บตกเทคนิคการใช้ irb ที่น่าจะเป็นประโยชน์ครับ

เปลี่ยน irb prompt ให้สั้นลง
irb prompt แบบเดิมหน้าตาแบบนี้
C:\>irb
irb(main):001:0>

ถ้าต้องการย่อ prompt ให้สั้นลง อ่านสบายขึ้น ให้ใส่อ็อบชั่น --simple-prompt เวลาเรียก irb ดังนี้
C:\>irb --simple-prompt
>>

อ้างอิงค่า return จากบรรทัดก่อนหน้า
เราสามารถอ้างอิงค่าสุดท้ายที่ return จากการรันโค้ดก่อนหน้าได้จาก "_" ดังตัวอย่างต่อไปนี้

>> "Manchester United F.C".gsub(/United/, "City")
=> "Manchester City F.C"
>> better_team = _
=> "Manchester City F.C"
>>

วันอังคารที่ 19 สิงหาคม พ.ศ. 2557

การใช้เมธอด collect

มธอด collect เป็นหนึ่งในเมธอดของโมดูล Enumerable ที่มิกซ์อิน (mix-in) อยู่ในคลาสพื้นฐานอย่าง คลาส Array, Hash หรือ Struct
ในเบื้องต้นเราสามารถเรียกใช้เมธอด collect จากอ็อบเจกต์ที่เป็นอินสแตนซ์ของคลาสที่ว่าข้างต้น

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

ประโยชน์ของ collect คือเราสามารถใช้มันเพื่อ "transform" ข้อมูลที่เราต้องการ
ลองดูตัวอย่างการใช้ เมธอด collect ดังต่อไปนี้

>> [1, 2, 3, 4, 5].collect { |x| x*5 }    => [5, 10, 15, 20, 25]
>> [65, 66, 67].collect { |x| x.chr }     => ["A", "B", "C"]

จะเห็นว่าเราสามารถใช้ collect เพื่อลองเปลี่ยนอาร์เรย์ของข้อมูลที่เป็นตัวเลขอย่าง [65, 66, 67] ให้กลายเป็นอาร์เรย์ของตัวอักษร ดังนี้ ซึ่งการแปลงร่างของเราอาศัยเมธอด chr ซึ่งจะมองค่าของตัวเลขที่เรียกใช้มันไปเป็นค่าของ ASCII code แล้วคืนค่าออกมาเป็นตัวอักษร
สังเกตุว่าในการวนรอบแต่ละรอบ ค่าของสมาชิกแต่ละตัวของอ็อบเจกต์ receiver (ในที่นี้คือ [65, 66, 67]) จะถูกโยนลงไปในบล็อก ซึ่งผลลัพธ์สุดท้ายที่ได้จากการรันบล็อกในแต่ละรอบจะถูกเก็บลงไปในอาร์เรย์ เมื่อวนรอบจนครบ
ค่าของอาร์เรย์ดังกล่าวก็จะถูกคืนออกมาเป็นค่า return ของเมธอด collect ซึ่งในที่นี้ก็คือ ["A", "B", "C"] นั่นเอง

collect จะคืนค่าออกมาเป็นอาร์เรย์ที่มีจำนวนสมาชิกเท่ากับจำนวนสมาชิกของอ็อบเจกต์ตั้งต้นเสมอ เช่น

[1, 2, 3, 4, 5].collect do |x|
  x*5 if x > 3
end

=> [nil, nil, nil, 20, 25]

คุณอาจจะไม่ค่อยคุ้น หรือยังนึกไม่ออกว่าจะใช่ collect ตอนไหน ถ้าคุณพบว่าคุณมักจะเขียนโค้ด ออกมาในรูปแบบนี้ ...

def my_transform(data)
  a = []
  data.each do |x|
    # do something that manipulate x
    a << "#{x}!!!"
  end
  return a
end

puts my_transform([1,2,3])

ซึ่งให้ผลลัพธ์ดังนี้
1!!!
2!!!
3!!!

แต่คุณสามารถใช้ collect เพื่อนำปรับปรุงโค้ดใหม่ โดยให้ผลลัพธ์เหมือนเดิมได้ดังนี้

def my_transform2(data)
  data.collect { |x| "#{x}!!!" }
end

puts my_transform2([1,2,3])

ในโค้ดใหม่ คุณจะเห็นว่าเราไม่จำเป็นต้อง setup ตัวแปรโลคอลอย่าง a ขึ้นมา เพราะว่าเมธอดค่าที่คืนออกมาจาก collect นั้นเป็นอาร์เรย์อยู่แล้ว

ลองนำไปใช้ดูนะครับ

วันพฤหัสบดีที่ 7 สิงหาคม พ.ศ. 2557

สรุปการใช้คำสั่ง rails generate

คำสั่ง rails generate เป็นหนึ่งในเครื่องมือที่ใช้ในการอำนวยความสะดวก และช่วยจัดระเยียบของไฟล์และโฟล์เดอร์เพื่อให้ตรงกับรูปแบบที่ rails กำหนดไว้เป็นค่า default

คำสั่งนี้จะทำการสร้างโค้ดหรือไฟล์ให้เราโดยอัตโนมัติในกรณีที่ ต้องการสร้างหรือเปลี่ยนแปลง model หรือ controller ของเรา หรือ

โดยทั่วไปนั้นการใช้งาน rails generate จะแบ่งออกเป็น 3 กรณีด้วยกัน

1. ใช้สร้าง controller
โดยปกติแล้วเมื่อทำงานภายใต้ Rails ขั้นตอนของการสร้างหน้า page ขึ้นมาหน้าหนึ่งนั้น จะต้องสร้างไฟล์ที่ทำหน้าที่แสดงผลผ่านทาง browser ซึ่งเป็นไฟลฺ์ .erb โดยไฟล์นี้จะอยู่ในโฟลเดอร์ของ view และอาจจะต้องสร้างไฟล์ในส่วนของ controller ขึ้นมาเพื่อทำหน้าที่กำหนด logic หรือ เงื่อนไข สำหรับการแสดงผลในหน้าของ view เช่นอาจจะต้องไปดึงข้อมูลจาก database แล้วเอามาแสดงผลซึ่งตรงนี้ก็จะรับผิดชอบโดย controller

เราสามารถลดขั้นตอนของการสร้างไฟล์และโค้ดในส่วนของ view และ controller ได้โดยการสั่งให้ Rails สร้างไฟล์ที่เป็น view และ controller ให้เราผ่านคำสั่งเพียงคำสั่งเดียว

สมมติว่าเราต้องการสร้างหน้า page เกี่ยวกับข้อมูลเบื้องต้นของบริษัท โดยกำหนดให้ชื่อของ section นี้ เป็น company และหน้าแรกของ section ให้ชื่อว่า about
เราจะใช้คำสั่งดังนี้
 
>rails generate controller welcome index

หลังจากรันคำสั่งแล้ว ไฟล์ต่อไปนี้จะถูกสร้างขึ้นโดยอัตโนมัติ

app/controllers/company_controller.rb
app/controllers/view/about.html.erb
app/helpers/company_helper.rb
...
...

เราสามารถไปใส่รายละเอียดสำหรับ logic ในไฟล์ company_controller.rb และกำหนดรูปร่างหน้าตาของ view ได้ในไฟล์ about.html.erb 

2. ใช้สร้าง model ใหม่
สำหรับ Rails นั้น การสร้าง model คือการสร้าง table ใน database เพื่อเก็บข้อมูลของสิ่งที่เราสนใจ (เช่น ลูกค้า, คำสั่งซื้อ) ซึ่งข้อมูลเหล่านั้นจะถูกมองเป็นอ็อบเจกต์ table ที่สร้างขึ้นก็จะสอดคล้องกับคลาสของอ็อบเจกต์ตัวนั้น คุณลักษณะต่างๆ อ็อบเจกต์ก็จะเปรียบเทียบได้กับ field หรือเป็น column ของ table (ซึ่งก็คือคลาส) โดยการจัดการกับข้อมูลในลักษณะของอ็อบเจกต์นี้จะเป็นไปตามหลักการ ORM (Object Relational Mapping) ซึ่งถูกใช้ใน database อย่าง ActiveRecord หรือ DataMapper

ขั้นตอนของการสร้าง model ค่อนข้างที่จะเป็น routine job นั้นคือ คุณต้องสร้างคลาสของข้อมูลที่ต้องการจะเก็บไว้ใน database ขึ้นมา โดยในคลาสนี้คุณจะต้องกำหนดคุณลัษณะของข้อมูลซึ่งจะถูกใช้เป็นชื่อของ column ใน table ที่จะถูกสร้างขึ้นในลำดับถัดไป จากนั้นจะต้องสร้างคลาสอีกคลาสหนึ่งเพื่อให้ Rails ใช้อ้างอิงในการ "migrate" ซึ่งก็คือการสร้าง table ใน database โดยจะเป็นขั้นตอนสุดท้ายที่จะถูกจัดการด้วย rake โดยใช้ rake:migrate

กระบวนการสร้าง model ที่ยุ่งยากข้างต้นสามารถทำให้สั้นลงได้โดยใช้คำสั่ง rails generat
โดยสมมติว่าเราต้องการสร้าง model ที่ชื่อว่า User โดยประกอบด้วย field ง่ายๆ 2 field ได้แก่ name และ string เราก็สามารถทำได้โดยใช้คำสั่งต่อไปนี้

> rails generate model User name:string email:string 

หลังจากใช้คำสั่งแล้ว Rails จะสร้างไฟล์ ขึ้นมา 2 ไฟล์ได้แก่

db/migrate/[time_stamp]_create_user.rb
app/models/user.rb

เมื่อลองตรวจสอบดูจะพบว่า
โค้ดในไฟล์ user.rb เป็นโค้ดที่เก็บคลาสที่ชื่อว่า User (ชื่อเดียวกับไฟล์และ table ที่จะถูกสร้างขึ้นใน database) ณ ตอนนี้เป็นคลาสเปล่าๆ ไม่มีโค้ดใดๆ

class User < ActiveRecord::Base
end

ส่วนโค้ดในไฟล์ [time_stamp]_cretae_user.rb จะเป็นโค้ดที่รับผิดชอบในการสร้าง table ที่ชื่อว่า User ลงใน database (ในที่นี้ก็คือ ActiveRecord)

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

เมื่อได้ไฟล์ทั้ง 2 ไฟล์แล้วเราเพียงแค่พิมพ์คำสั่ง rake db:migrate ลงใน command prompt แล้ว Rails ก็จะทำการสร้าง table ที่ชื่อว่า User ที่ประกอบด้วย column name และ email ตามที่เรากำหนดไว้ให้โดยอัตโนมัติ

3. ใช้เพื่อเปลี่ยนแปลงแก้ไขโครงสร้างของ Table ใน database
ในกรณีที่เราต้องการแก้ไขโครงสร้างของ table ที่เราสร้างขึ้นก่อนหน้า เช่น ต้องการเพิ่ม field ของเบอร์โทรของผู้ใช้ (phone_number) ซึ่งเป็น field ใหม่เข้าไป เราจะไม่เข้าไปแก้ไข table ผ่านทาง database โดยตรงแต่จะใช้กระบวนการ migration ของ Rails แทน ทั้งนี้เพื่อให้การจัดการข้อมูลของ Rails ในลักษณะของอ็อบเจกต์โมเดลนั้นสอดคล้องกัน

แต่ปัญหาคือการ migration แต่ละครั้งมีขั้นตอนยุ่งยากพอควร
ขั้นตอนในการ migration แบบคร่าวๆ จะเริ่มตั้งแต่ คุณจะต้องสร้างคลาสที่เป็นตัวแทนของการ migration ในครั้งนั้นขึ้นมา โดยระบุการเปลี่ยนแปลงการต้องการเอาไว้ภายในคลาส เช่น เพิ่ม column เพิ่ม index ลบ column เป็นต้น (ทำผ่านทางเมธอดตามรูปแบบที่กำหนดของ Rails) โดยให้ระบุด้วยว่าเป็นคลาสที่สืบทอดมาจากคลาส ActiveRecord::Migration เสมอ
เมื่อสร้างคลาสเรียบร้อยแล้วก็ให้ทำการรันคำสั่ง rake db:migrate เพื่อสั่งให้ Rails ดำเนินการเปลี่ยนแปลงที่เราต้องการกับ database

เช่นเดียวกับ 2 ข้อที่ผ่านมา เราสามารถลดขั้นตอนที่ยุ่งยากโดยการใช้คำสั่ง rails generate เพื่อให้ Rails สร้างไฟล์และโค้ดที่เหมาะสมกับการเปลี่ยนแปลงให้โดยอัตโนมัติ

สมมติว่าเราต้องการเพิ่ม field ชื่อ phone_number เข้าไปในโมเดล User เราก็ทำได้โดยใช้คำสั่ง

> rails generate migration add_phone_number_to_users phone_number:string

เมื่อรันคำสั่งแล้ว เราก็จะได้ไฟล์
db/migrate/[time_stamp]_add_phone_number_to_users.rb

มีหน้าตาดังนี้

class AddPhoneNumberToUsers < ActiveRecord::Migration
  def change
    add_column :users, :phone_number, :string
  end
end

จากนั้นก็เพียงแค่ใช้คำสั่ง rake db:migrate เพื่อให้ Rails ดำเนินการ update database ให้เราโดยอัตโนมัติ


วันศุกร์ที่ 1 สิงหาคม พ.ศ. 2557

Mock ด้วย MiniTest

จุดประสงค์ของการ mock คือ ต้องการรู้ว่าเมธอดของ mock อ็อบเจกต์ถูกเรียกใช้อย่างถูกต้องตามที่คาดหวัง (expect) ไว้หรือไม่

สรุปก็คือการ mock เป็นการทดสอบให้แน่ใจว่าว่าเมธอดที่เราดูอยู่จะต้องถูกเรียกออกมาใช้จากอ็อบเจกต์ที่เราได้ mock เอาไว้ และค่าที่คืนออกมาจากเมธอดนั้นต้องเป็นค่าที่ถูกต้องด้วย
ลองดูตัวอย่างของการใช้ mock ดังนี้

require 'minitest/autorun'

class Label
  def initialize(note)
    @note = note
  end

  def emphasize
    @note.highlight
  end
end

describe Label do
  it "has all charecter upcase when emphasize" do
    special_note = "*** HAPPY DAY ***"

    note = MiniTest::Mock.new
    note.expect(:highlight, special_note)
    
    label = Label.new(note)
    label.emphasize.must_match(/\*\*\*/)
    note.verify
  end
end

ตัวอย่างข้างต้นอ็อบเจกต์ note เป็นอ็อบเจกต์ที่เราสมมติขึ้นมาหรือเรียกว่า mock มันเอาไว้ ทีนี้ เมธอดที่ต้อวการจะตรวจสอบว่าจะต้องถูกเรียกด้วยอ็อบเจกต์ note ก็คือ เมธอด highlight ดั้งนั้นจึงต้องใส่โค้ดที่ระบุความคาดหวังจากมธอด highlight ซึ่งคือโค้ด note.expect(:highlight, special_note)
จากนั้นจึงเป็นการทดสอบโดยกำหนดให้ label เรียกเมธอด emphasize แล้วจะต้องได้ค่าที่สอดคล้อง (must_match) กับ *** แล้วจึงสั่งให้ note.verify

ถ้าดูจาก spec อย่างเดียว ความหมายก็คือ ข้างในเมธอด emphasize จะทำงานอย่างไรไม่รู้ รู้แต่ว่า spec ข้อนี้จะผ่านได้ก็ต่อเมื่อ เมธอด highlight จะต้องถูกเรียกใช้จาก mock อ็อบเจกต์ (ในที่นี้คือ note) ตามที่ระบุไว้ใน expect ดังนั้น จึงเป็นการบอกโดยนัย หรือโดยปริยายว่า ภายใน emphasize จะต้องมีการเรียก note.highlight แน่นอน และค่าที่คืนออกมานั้นก็จะต้องสอดคล้องกับสตริง *** ด้วย

ถ้าเอาโค้ดข้างต้นไปรันด้วย minitest ผลของการทดสอบก็จะผ่านไปได้

C:\CodeProject\Ruby\TDD\minitest\example1>ruby spec/book_spec.rb
Run options: --seed 8903

# Running:

.

Finished in 0.005000s, 200.0000 runs/s, 400.0000 assertions/s.

1 runs, 2 assertions, 0 failures, 0 errors, 0 skips


ลองดูอีกสักตัวอย่างกับการ mock
spec มีหน้าตาแบบนี้...

describe Book do
  it "uses authors name in description" do
    author = MiniTest::Mock.new
    author.expect(:name, "Worrawut")

    book = Book.new("Royal Assassin", :author => author)
    book.description.must_match(/Written by Worrawut/)
    author.verify
  end
end

จากโค้ดจะเห็นว่าเรา mock เพื่อต้องการย้ำว่าเมธอด name ของอ็อบเจกต์ note จะต้องถูกเรียกใช้พร้อมกับให้ค่าที่ถูกต้อง เมื่อเมธอด description ของ book ถูกเรียกขึ้นมาใช้งาน
ดังนั้นโค้ดที่จะทำให้ spec นี้รันผ่านไปได้จึงมีหน้าตาดังนี้

class Book
  def initialize(title, h)
    @title = title
    @author = h[:author]
  end
  def description
    "#{@title} is Written by #{@author.name}"
  end
end

วันจันทร์ที่ 21 กรกฎาคม พ.ศ. 2557

ตัวอย่างการใช้ subject และ should ใน RSpec


นการ Test อ็อบเจกต์หรือคลาสใดๆ ด้วย Rspec รูปแบบหรือฟอร์มที่เป็นพื้นฐานที่สุดสำหรับทดสอบ incomming message (หรือ instance เมธอด) ก็คือการใช้ expect(object) to respond_to(something) รูปแบบนี้มีความหมายชัดเจนในตัวอยู่แล้ว แต่ถ้าหากเราจะต้อง test เมธอดหลายๆ เมธอด การใช้ฟอร์มแบบนี้อาจก่อให้เกิดโค้ดที่ซ้ำกันโดยไม่จำเป็นและที่สำคัญมันอาจทำให้เราตาลายได้

วิธีแก้ ง่ายมาก ให้กำหนดอ็อบเจกต์เบื้องต้น (default object) ให้กับ rspec เพื่อทำการทดสอบในขอบเขตนั้นๆ โดยใช้ subject และใช้ should เพื่ออ้างถึง default object ดังกล่าว

สมมติว่า test โค้ดของเรามักจะมีหน้าตาประมาณนี้

describe MyClass do
    before { @my_object = MyClass.new(color: "Red", weight: 60) }

    it "should respond to 'color'" do
      expect(@my_object).to respond_to(:color)
    end

    it "should respond to 'weight'" do
      expect(@my_object).to respond_to(:weight)
    end
end 

โค้ดข้างต้นสามารถลดรูปให้สั้นลงและอ่านง่ายขึ้นได้ โดยใช้ subject และ should เข้ามาช่วย ดังนี้

describe MyClass do
    before { @my_object = MyClass.new(color: "Red", weight: 60) }
    subject { @my_onject  }

    it { should respond_to(:color)  }
    it { should respond_to(:weight)  }
end

เมธอด subject {@my_object} เป็นตัวหนดให้ @my_object กลายเป็น default object ที่จะถูกนำมารัน test ด้วยเมธอดต่างๆ ของ rspec ส่วนเมธอด should จะทำหน้าที่ตรวจสอบเงื่อนไขว่าเป็นจริงหรือไม่
จากตัวอย่างนี้ respond_to(:color) จะมีความหมายเดียวกับการใช้เมธอด respond_to? (จากคลาส Object ใน Ruby) ซึ่งจะคืนค่า true ออกมา ทำให้ผ่านเงื่อนไขของการ test ในที่สุด

ลองดูผลลัพธ์ที่เกิดจากเมธอด respond_to? กัน

>> class MyClass
>> def color
>> end
>> end
=> nil
>> obj = MyClass
=> MyClass
>> obj.respond_to?("color")
=> true
>> obj.respond_to?("other")
=> false

วันศุกร์ที่ 18 กรกฎาคม พ.ศ. 2557

เริ่มใช้ Rails

มี project ส่วนตัวที่อยากทำออกมาให้เป็น web app
ตั้งใจว่าจะใช้ sinatra เหมือนที่เคยทำกับ project ก่อน แต่ก็ต้องเปลี่ยนใจกลับมาลองใช้ Rails อีกครั้ง

จริงๆ แล้วเคยลองใช้มาก่อนช่วง Rails 2.0 ออกใหม่ (ตอนนี้ 4.1 แล้ว) แล้วไม่ค่อยประทับใจ
ในตอนนั้น รู้สึกว่าต้องเสียเวลาทำความเข้าใจกับการ config ที่เยอะมาก MVC ก็ยังงงใช้ผิดๆ ถูกๆ และเหมือนถูกบังคับให้ต้อง migrate database อยู่บ่อย ๆ ซึ่งในตอนนั้นยังมองไม่เห็นภาพ ไม่รู้ว่าทำเพื่ออะไร
ซึ่งเมื่อเปรียบเทียบกับ sinatra ผมรู้สึกว่าชีวิตมันง่ายกว่า เข้าใจถึงคำว่าเบากว่า (lighweight) ขึ้นมาเลย

อย่างไรก็ตาม หลังจากที่ลองศึกษาข้อมูลเพิ่มขึ้นเรื่อยๆ ทำให้เกิดอาการอยากลองของดูอีกสักครั้ง
ตอนนี้กำลังไล่อ่าน tutorial ของ Rails อยู่จากหลายๆ แหล่ง หลักๆ เลยตามอยู่ที่ Ruby On Rails Tutorial (อ่าน online ฟรี :) )

ยังคงเกิดความรู้สึกเดิมคือ ทำไมมันเยอะ จะเยอะจะเทอะไปไหน
คือจะ build web app นี่ต้อง dependency เยอะอย่างงี้เลย ? (gem มากมาย รายการใน Gemfile ยาวขึ้นทุกที)
แต่ เอาน่ะ ไหนๆ จะลองแล้วคราวนี้อยากเอาให้สุดซะที

วันอาทิตย์ที่ 28 เมษายน พ.ศ. 2556

สร้าง Wafer Map ด้วย ruby และ Cairo

Wafer Probe หรือ Wafer Sort เป็นขั้นตอนที่สำคัญขั้นตอนหนึ่งของกระบวนการผลิต IC chip โดยจะเป็นการนำ Silicon Wafer ที่ทำการขึ้นลายวงจรเรียบร้อยแล้วมาทดสอบ "ได"(Die, ตัว chip ที่ยังอยู่บนแผ่น Wafer โดยยังไม่ได้ถูกตัดออกมาเป็นชิ้นๆ) ที่อยู่บน Wafer นั้นว่าดีหรือเสีย ซึ่งหลังจากทดสอบไดทุกตัวบนเวเฟอร์เรียบร้อยแล้ว เราจะได้ soft file ซึ่งระบุตำแหน่ง coordinate x,y และสถานะของไดที่ถูก probe ไปแล้วว่าตำแหน่ง coordinate นั้นๆ เป็นไดที่ดีหรือเสีย ไฟล์ที่ว่าอาจเป็น text file ง่ายๆ ซื่งผมขอเรียกมันว่า coordinate file แล้วกันครับ

อันนี้่คือตัวอย่างของแผ่นซิลิกอนเวเฟอร์จริงครับ


วันนี้เราจะทดลองนำไฟล์ coordinate มาใช้ในการสร้างกราฟฟิกในรูปของ Wafer map image file
Wafer map ที่เราต้องการจะมีหน้าตาประมาณนี้ครับ สีเขียวที่เห็นคือไดที่ดี (good die) ส่วนสีอื่นๆคือไดเสีย (rejected die) ซึ่งสีที่ต่างกันแสดงถึงสาเหตุของอาการเสียที่แตกต่างกันออกไป

วันอังคารที่ 9 เมษายน พ.ศ. 2556

กลับมาแล้ว


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

ก่อนลากันวันนี้ ขอฝาก link นี้ไว้แล้วกันครับ

เป็น note Ruby2.0 ที่ Matz พูดไว้ในงาน Waza 2013
http://showyou.com/popular/v:61043050

อันนี้เป็น full slide ครับ
https://speakerdeck.com/yukihiro_matz/ruby-2-dot-0-en

วันพุธที่ 15 กันยายน พ.ศ. 2553

Singleton เมธอด

Singleton mehtod คือเมธอดที่เราสร้างขึ้นมาสำหรับ "อ็อบเจก" ตัวใดตัวหนึ่ง (individual object) เท่านั้น
หมายความว่า นอกจากอ็อบเจกจะมีความสามารถทำโน้นทำนี่ตามเมธอดต่างๆ ที่กำหนดเอาไว้ในคลาสแล้ว เราสามารถกำหนดแบบเจาะจง ให้อ็อบเจกตัวใดตัวหนึ่งมีเมธอดเพิ่มขึ้นมาจากเมธอดเดิมที่มีอยู่แล้วในคลาสได้ด้วย เมธอดที่เราเจาะจงให้กับอ็อบเจกต์นั้นแหละที่เรียกว่า singleton method

วันจันทร์ที่ 6 กันยายน พ.ศ. 2553

แปลง String ให้เป็น Constant

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

วันอังคารที่ 24 สิงหาคม พ.ศ. 2553

การส่ง message ให้กับอ็อบเจ็ก



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

เช่น ถ้าเราเรียกเมธอด 100.to_s นั่นหมายความว่าีเรากำลังส่ง message (ที่เป็นเมธอดชื่อ) "to_s" ออกไปหา receiver ซึ่งในที่นี้ก็คือตัวเลข 100

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

การมองเมธอดให้เป็น message ที่ส่งไปหา receiver จะเป็นวิธีหนึ่งที่ช่วยเราแก้ปัญหานี้ได้ โดยเมธอดในภาษา Ruby ที่เกี่ยวข้ิองในการแก้ปัญหานี้ได้แก่ respond_to? และ send
ลองดูตัวอย่างต่อไปนี้นะครับ

เราจะทำอย่างไร ถ้าเราต้องการสั่งให้อ็อบเจกต์ Men ทำการกระโดด เดิน หรือ นั่ง ด้วยการป้อนการกระทำดังกล่าวผ่านลงไปทาง keyboard โดยกำหนดให้โปรแกรมของเรารับค่าสตริงที่ป้อนเข้ามาด้วยเมธอด gets จากนั้น ตัดอักขระ new line ด้วยเมธอด chomp หนึ่งที แล้วเก็บค่าที่ได้ลงใน ตัวแปร request
เราอาจใช้วิธีตรวจสอบว่า สตริงที่รับเข้ามามีค่าตรงกับชื่อของเมธอดที่มีหรือไม่ ถ้าใช่ก็ให้แสดงผลของเมธอดนั้นออกทางหน้าจอดังนี้
class Man
  def jump
    "I'm jumping"
  end
  def walk
    "I'm walking"
  end
  def sit
    "I'm sitting"
  end
end

guy = Man.new
puts "What you want me to do ?"

case gets.chomp
  when "jump"; puts guy.jump
  when "walk"; puts guy.walk
  when "sit"; puts guy.sit
  else puts "The man not understand this command"
end

จะเห็นว่าวิธีดังกล่าวอาจทำให้โค้ดของเรายาวมาก หากเราคลาส Men มีเมธอดมากกว่านี้
ทีนี้ ถ้าเรามองการเรียกเมธอด jump, walk หรือ sit เป็นการส่ง message ที่บอกถึง "การกระทำ(method)" ไปให้กับอ็อบเจกต์ดู เราจะเขียนโค้ดใหม่ได้ดังนี้
request = gets.chomp
if guy.respond_to?(request)
  puts guy.send(request)
else
  puts "The man not understand this command"
end

จากโค้ดข้างต้น จะเห็นว่ามีเมธอดที่มาช่วยเราแก้ปัญกาอยู่ 2 เมธอด คือ เมธอด respond_to? และ send
ในบรรทัดที่ 5 เมธอด respond_to? เป็นเมธอดของคลาส Object ทำหน้าที่ตรวจสอบว่า เราสามารถเรียกใช้เมธอดที่เป็นพารามิเตอร์ของมันอยู่จากคลาสนั้นๆ ได้หรือไม่ โดยจะคืนค่าจริงถ้าสามารถเรียกเมธอดจากคลาสนั้นได้ (แสดงว่ามีเมธอดอยู่จริง)
และหัวใจของโค้ดนี้ก็อยู่ที่เมธอด send ในบรรทัดที่ 6 นี่ละครับ โดยเมธอดนี้จะทำหน้าที่เหมือนกับเป็น dot operator เลย แต่แทนที่จะมองว่ามันจะ "เรียก" เมธอดออกมาใช้ตรงๆ คราวนี้เราจะมองว่ามัน "ส่ง" message ที่มีค่าเป็นชื่อเมธอด ไป "เรียก" เมธอดตัวนั้นออกมาใช้

อย่างไรก็ตาม มีข้อควรระวังในการใช้ send เหมือนกันครับ
ถึงแม้ว่าเมธอด send จะเป็นเมธอดของคลาส Object ที่เราแทบจะสามารถเรียกใช้มันได้จากทุกอ็อบเจกต์ (เพราะอ็อบเจกต์แทบทุกตัวล้วนสืบทอดมาจากคลาส Object) แต่ชื่อของมันก็อาจจะไปซ้ำกับเมธอดอื่นที่มีชื่อว่า "send" ได้เหมือนกัน ซึ่งน่าจะมีโอกาสซ้ำสูงซะด้วย เพราะเราอาจตั้งชื่อเมธอดในคลาสของเราว่า send แต่ใช้ส่งข้อมูลอย่างอื่นก็ได้
แต่ Ruby ก็มีมี alias สำหรับเมธอด send เอาไว้กันพลาดแล้ว ซึ่งก็คือเมธอดชื่อ "__send__" นั่นเอง
ดังนั้นหากจะนำหลักการส่ง message ไปยัง receiver มาใช้ก็ให้คิดถึง เมธอด __send__ เอาไว้แล้วกันครับ

วันศุกร์ที่ 16 กรกฎาคม พ.ศ. 2553

Matz Keynote จาก European Ruby Conference ครั้งล่าสุด

เพิ่งไปเจอ clip นี้ ที่เวปของ Euruko 2010
เป็น keynote ของ Matz


น่าสนใจครับ

Matz พูดถึงความสำเร็จของ Ruby community และฝากให้ชาว Rubyist ทำผลงานเจ๋งๆ กันต่อไป
เขายังได้ให้ความมั่นใจว่าจะยังคงเป็นหัวเรือในการพัฒนา Ruby ต่อไป ซึ่ง Ruby 1.92 น่าจะ release ได้ประมาณ เดือนสิงหา หรือกันยาของปีนี้ จากนั้นก็จะไปตั้งหน้าตั้งตา develope ในส่วนของ Ruby 2.0 ต่อไป
นอกจากนี้เขายัง สรุปให้เห็นเป็นฉากๆว่า มีส่วนไหนที่เขาจะ focus ในการเพิ่มหรือพัฒนาเพื่อ Ruby ในเวอร์ชั่นต่อๆ ไป

ช่วงกลางๆ keynote แกก็ยาหอมว่า "I promise you, I will keep moving forward"
เรียกเสียงตรบมือได้พอสมควร

ดู clip นี้จบ
มันก็อดคิดไม่ได้ว่า อยากจะมี Ruby conference แบบนี้เกิดขึ้นสักครั้งในเมืองไทยบ้าง T T
Rubyist ในเมืองไทยคิดเหมือนผมมั้ยครับ

วันอังคารที่ 2 กุมภาพันธ์ พ.ศ. 2553

แปลงซอร์ซไฟล์ Ruby ให้เป็น .exe

ริงอยู่ที่เราสามารถรันซอร์ซไฟล์ .rb เพื่อเรียกโปรแกรมที่เขียนขึ้นมาใช้ได้เลย แต่มันมีเงือนไขอยู่ที่ว่าเครื่องของเราต้องมี Ruby intepreter และมันต้องรู้จักไฟล์ .rb ด้วยนะ อันนี้ถ้าเราลง Ruby installer หรือ Ruby package ไปแล้วก็ไม่มีปัญหา แต่ถ้าสมมติว่าจะเอาโปรแกรมที่เขียนด้วย Ruby ไปรันที่เครื่องอื่น(สมมติว่าจะรันบน Windows นะ)ที่ไม่มี Ruby ล่ะจะทำยังไง ...ทางเลือกหนึ่งก็คือแปลงซอร์ซไฟล์ .rb ให้เป็น .exe ครับ ซึ่งวันนี้เราจะมาดูกันว่ามันมีวิธีการทำอย่างไร

เครื่องมือ?
ณ ตอนนี้มี gems อยู่ 2 ตัวที่ผมรู้จักที่สามารถใช้ในการแปลงไฟล์เป็น .exe ซึ่งได้แก่ rubyscript2exe และ ocra
ทั้ง 2 ตัวนี้ผมเคยใช้มาแล้วครับ ขอเล่าประสบการณ์ให้ฟังหน่อยแล้วกัน

Rubyscript2exe

rubyscript2exe เป็น gems ตัวแรกที่ผมได้มีโอกาสทดลองใช้การแปลงไฟล์ครับ แล้วก็เคยประสบความสำเร็จในการใช้มันเมื่อนานมาแล้วด้วย(เย้) แต่มีอยู่วันหนึ่งมีเหตุให้ผมต้องลง Ruby และ RubyGem บนเครื่องใหม่ ปรากฏว่าหลังจากวันนั้นผมก็ไม่สามารถใช้เจ้า rubyscript2exe เพื่อแปลงไฟล์ให้ผมได้อีกเลย มันพ่น error message ตลอดเวลาที่ผมพยายามจะ complie มัน เคยพยายามหา solution จาก webboard ต่างๆก็แล้วแต่ก็ไม่ได้ช่วยแก้ปัญหาเลยครับ ทำเอาผมปวดหัวอยู่หลายวัน

ผมเข้าใจว่า rubyscript2exe น่าจะมีปัญหากับ version ของ RubyGems นั่นคือมันไม่สามารถใช้กับ RubyGems version > 1.2.0 (ขณะนี้ version ล่าสุดของ RubyGems คือ 1.3.5) แต่จะให้ผมลง Ruby กับ RubyGems ใหม่เพื่อทดสอบนั้นคงไม่ดีกว่าครับ กลัว gems ตัวอื่นๆของผมจะเดี้ยงไปด้วย ฮา
หากมีท่านใดอาสาช่วยผมทดลองก็จะขอบคุณมากๆเลยครับ ถ้าใช้งานได้ล่ะก็อย่าลืมเอาผลมาบอกกันบ้างนะครับ
ใครอยากลอง rubyscript2exe ก็สามารถ install ผ่าน gems ได้เลยด้วยคำสั่ง

C:\gem install rubyscript2exe


ส่วนการใช้งานเมื่อค้องการแปลงไฟล์เป็น .exe ก็ใช้ดังนั้นครับ(สำหรับท่านที่ install ด้วย RubyGem เท่านั้นนะครับ)

C:\>rubyscript2exe your_code.rb


สำหรับรายละเอียดของการใช้งานรวมถึง option ต่างๆของ rubyscript2exe นั้นสามารุเข้าไปดูได้ที่ official website ของ rubyscript2exe ครับ

Ocra

เมื่อผิดหวังจาก rubyscript2exe แล้วผมก็เริ่มค้นหาครับว่ามีตัวช่วยอื่รหรือไม่ แล้วในที่สุดผมก็เจอครับ ซึ่งก็คือ gem ที่ชื่อ Ocra นั่นเอง
เท่าที่ผมใช้งานมานั้นยังไม่เจอปัญหาหรือ error ใดๆกับเจ้า Ocra เลยครับ ผมจึงขอแนะนำท่านที่ต้องการแปลงไฟล์เป็น .exe ลองใช้ดูนะครับ ของเขาดีจริงๆ
Ocra นั้นมาเป็น RubyGem แพกเกจครับดังนั้นเราสามารถติดตั้งได้โดยใช้คำสั่ง

C:\gem install ocra

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

puts "Hello Ocra"
STDIN.getc

ดังนั้นผมสามารถแปลงไฟล์ซอร์ซโค้ดดังกล่าวให้เป็นไฟล์ .exe ได้ด้วยคำสั่งต่อไปนี้

C:\ocra test_ocra.rb

จากนั้น ocra จะทำการ complie ซอร์ซไฟล์ของเราและตรวจสอบ include ไฟล์ต่างๆ(ถ้ามี)
ซึ่งตรงนี้เราจะเห็นโปรแกรมของเรารันขึ้นมาก็ไม่ต้องตกใจให้ปิดมันลงไปซะ เพื่อให้ ocra ทำการ Build ไฟล์ของเราต่อไป
ซึ่งจะได้ผมลัพธ์ดังนี้

=== Loading script to check dependencies
Hello Ocra
a
=== Building test_ocra.exe
m src
a src\test_ocra.rb
m bin
a bin\ruby.exe
a bin\msvcrt-ruby18.dll
a bin\zlib1.dll
m lib
m lib\ruby
m lib\ruby\gems
m lib\ruby\gems\1.8
m lib\ruby\gems\1.8\specifications
a lib\ruby\gems\1.8\specifications\ocra-1.1.3.gemspec
m lib\ruby\site_ruby
m lib\ruby\site_ruby\1.8
m lib\ruby\site_ruby\1.8\rubygems
a lib\ruby\site_ruby\1.8\rubygems\defaults.rb
m lib\ruby\1.8
m lib\ruby\1.8\i386-mswin32
a lib\ruby\1.8\i386-mswin32\thread.so
a lib\ruby\1.8\thread.rb
a lib\ruby\1.8\i386-mswin32\etc.so
a lib\ruby\1.8\i386-mswin32\rbconfig.rb
a lib\ruby\site_ruby\1.8\rubygems\exceptions.rb
a lib\ruby\site_ruby\1.8\rubygems\requirement.rb
a lib\ruby\site_ruby\1.8\rubygems\version.rb
a lib\ruby\site_ruby\1.8\rubygems\dependency.rb
a lib\ruby\site_ruby\1.8\rubygems\gem_path_searcher.rb
a lib\ruby\site_ruby\1.8\rubygems\user_interaction.rb
a lib\ruby\site_ruby\1.8\rubygems\platform.rb
a lib\ruby\site_ruby\1.8\rubygems\specification.rb
a lib\ruby\site_ruby\1.8\rubygems\source_index.rb
a lib\ruby\site_ruby\1.8\rubygems\builder.rb
a lib\ruby\1.8\i386-mswin32\stringio.so
m lib\ruby\1.8\yaml
a lib\ruby\1.8\yaml\error.rb
a lib\ruby\1.8\i386-mswin32\syck.so
a lib\ruby\1.8\yaml\ypath.rb
a lib\ruby\1.8\yaml\basenode.rb
a lib\ruby\1.8\yaml\syck.rb
a lib\ruby\1.8\yaml\tag.rb
a lib\ruby\1.8\yaml\stream.rb
a lib\ruby\1.8\yaml\constants.rb
a lib\ruby\1.8\rational.rb
m lib\ruby\1.8\date
a lib\ruby\1.8\date\format.rb
a lib\ruby\1.8\date.rb
a lib\ruby\1.8\yaml\rubytypes.rb
a lib\ruby\1.8\yaml\types.rb
a lib\ruby\1.8\yaml.rb
a lib\ruby\1.8\i386-mswin32\Win32API.so
a lib\ruby\site_ruby\1.8\rubygems\config_file.rb
a lib\ruby\site_ruby\1.8\rubygems\custom_require.rb
a lib\ruby\1.8\fileutils.rb
a lib\ruby\site_ruby\1.8\rubygems.rb
a lib\ruby\site_ruby\1.8\ubygems.rb
m lib\ruby\site_ruby\1.8\i386-msvcrt
a lib\ruby\site_ruby\1.8\i386-msvcrt\zlib.so
a lib\ruby\1.8\i386-mswin32\socket.so
a lib\ruby\1.8\timeout.rb
m lib\ruby\1.8\net
a lib\ruby\1.8\net\protocol.rb
m lib\ruby\1.8\uri
a lib\ruby\1.8\uri\common.rb
a lib\ruby\1.8\uri\generic.rb
a lib\ruby\1.8\uri\ftp.rb
a lib\ruby\1.8\uri\http.rb
a lib\ruby\1.8\uri\https.rb
a lib\ruby\1.8\uri\ldap.rb
a lib\ruby\1.8\uri\mailto.rb
a lib\ruby\1.8\uri.rb
a lib\ruby\1.8\net\http.rb
a lib\ruby\1.8\parsedate.rb
a lib\ruby\1.8\time.rb
a lib\ruby\site_ruby\1.8\rubygems\remote_fetcher.rb
a lib\ruby\site_ruby\1.8\rubygems\spec_fetcher.rb
e RUBYOPT -rubygems
e RUBYLIB
e GEM_PATH  \gemhome
l bin\ruby.exe ruby.exe " \src\test_ocra.rb"
=== Compressing
=== Finished (Final size was 505569)


เท่านี้ก็เป็นอันเสร็จสิ้น เราจะได้ไฟล์ test_ocra.exe ที่ต้องการออกมา
เมื่อลองรันไฟล์ .exe ดูก็จะได้ผลลัพธ์ดังนี้


ไม่ยากเลยใช้ไหมครับ
คุณสามารถเข้าไปดูรายละเอียดของการใช้งาน ocra นอกเหนือจากที่ผมได้นำเสนอไว้ในวันนี้ได้ที่ Official website ของ Ocra นะครับ

อย่างไรก็ตาม การทำซอร์ซไฟล์ .rb ให้เป็น .exe ด้วย Ocra หรือ Rubyscript2exe อาจไม่ใช้ทางเลือกที่ดีที่สุด เพราะข้อเสียที่เห็นได้ชัดเจนคือขนาดของไฟล์ exe หลังจากที่แปลงไฟล์แล้วนั้นมีขนาดใหญ่โตจริงๆครับ ไฟล์ test_ocra.exe จากตัวอย่างข้างต้นที่เป็นแค่โปรแกรม hello ธรรมดาๆยังปาเข้าไปกว่า 500K แล้วครับ แต่ถ้าเป็นโปรแกรม hello เหมือนกันแต่เป็น GUI โดยเขียนภายใต้ไลบรารี WxRuby นั้นหลังจากแปลงไฟล์เป็น .exe แล้วขนาดของไฟล์จะใหญ่ถึง 2.6M เลยทีเดียว

อันนี้ถือว่าเป็นโจทย์ข้อสำคัญของภาษา Ruby เหมือนกันครับ หากต้องการใช้ application ที่พัฒนาด้วยภาษา Ruby ถูกนำไปใช้อย่างจริงจังในตลาด

แล้วมีทางเลือกอื่นไหม ถ้ายังอยากนำ app ที่เขียนด้วย Ruby ไปใช้?
ถามผมตอนนี้คงตอบว่า ให้ไปลอง JRuby ดูครับ เพราะมันรันบน JVM มันควรจะมีวิธีการที่สามารถนำไปใช้ได้กว้างกว่ารึเปล่า
ยังไม่เคยศึกษาอย่างจริงจังเหมือนกัน
ท่านใดรู้กรุณาบอกเป็นวิทยาทานทีนะคร้าบ(ซะงั้น - -'')