หน้าเว็บ

วันพฤหัสบดีที่ 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