สนุกกับภาษา Ruby

เนื่องจากส่วน book ของ codenone จะปิดตัวลง คุณ mk จึงแนะนำให้ผมย้ายหนังสือไปที่ blognone library ซึ่งเป็นหมวดหนังสือใครเครือ *none :)

ผมย้ายหนังสือเล่มนี้ไปอยู่ที่หน้า Ruby for fun เรียบร้อยแล้วครับ


ผมเจอ Ruby จากการแนะนำของเพื่อน และจากการดู Video สาธิตการพัฒนาโปรแกรมบน Rails framework บอกตามตรงว่าตอนแรกไม่ได้สนใจ Ruby เลยเพราะมัวแต่ไปสนใจ Rails แต่พอได้ลงมาดูตัว Ruby จริงๆ ผมพบว่ามันเป็นภาษาที่ชัดเจนมากๆ ภาษาอ่านง่าย ทำให้เรียนรู้ได้เร็ว ไม่รู้ว่า เพราะคนคิดค้นภาษานี้เป็นชาวญี่ปุ่น ทำให้ออกแบบภาษา ruby ได้แตกต่างภาษาอื่นก็เป็นได้

ตัวภาษา ruby มีสิ่งที่เราคุ้นเคยอยู่ครบ ทั้ง String, Integer, floating-point, array, boolean รวมไปถึงพวก loop ต่างๆ ก็ไม่ต่างจากภาษาอื่นๆ มากนักทำให้การเรียนรู้ภาษา ruby ยิ่งสนุกขึ้นไปอีก

ผมลองแบ่งส่วนที่จะเขียนคร่าวๆ ตามนี้ menu ด้านล่าง

ติดตั้ง ruby

ruby เป็นภาษาที่ใช้ได้หลายระบบปฏิบัติการ แต่ละระบบมีวิธีการติดตั้งไม่เหมือนกัน ใครชอบตัวไหนก็ใช้ตัวนั้นนะครับ วิธีการติดตั้งดูได้ดังนี้

ติดตั้ง ruby บน linux

วิธีติดตั้ง ruby บน linux (ubuntu) ให้ทำดังนี้ครับ

สำหรับคนที่ online ได้

1. ตรวจสอบดูไฟล์ /etc/apt/sources.list แล้วเอา comment ที่สองบรรทัดนี้ออก

deb http://us.archive.ubuntu.com/ubuntu gutsy universe
deb-src http://us.archive.ubuntu.com/ubuntu gutsy universe

คำว่า gutsy เป็นชื่อรุ่นของ ubuntu ถ้าใครมีเก่าหน่อย อาจจะเป็น depper หรือรุ่นใหม่กว่านี้ก็ได้

2. update ระบบให้ทันสมัยโดยคำสั่ง

$ sudo apt-get update

3. ติดตั้ง ruby และ irb ด้วยคำสั่ง

$sudo apt-get install ruby ruby1.8 ruby1.8-dev irb

Rails ยังไม่รองรับ version 1.9 ครับ ป้องกันปัญหาในอนาคต ตอนนี้ลง 1.8 ก่อนดีกว่าครับ

จากนั้นติดตั้ง library พิเศษ สำหรับทำ document และเตรียมสำหรับ rails

$sudo apt-get install rdoc libzlib-ruby libopenssl-ruby

4. ถ้าต้องการใช้ mysql ให้ติดตั้ง mysql ด้วย

$sudo apt-get install mysql-server libmysql-ruby

5. ติดตั้ง gem เพราะต่อไปบน ruby เราจะลง library เสริมด้วยคำสั่ง gem

 $wget http://rubyforge.org/frs/download.php/35283/rubygems-1.1.1.tgz
 $tar zxvf rubygems-1.1.1.tgz
 $cd  rubygems-1.1.1
 $sudo ruby setup.rb

ถ้าไม่แน่ใจว่า download version ล่าสุด เข้าไปดูได้ที่นี่ครับ http://rubyforge.org/projects/rubygems

6. หลังจากติดตั้ง gem เราควร update gem ด้วย

$sudo gem update --system

แถม

7. ถ้าต้องการลง rails ให้พิมพ์คำสั่งนี้ครับ

$sudo gem install rails --include-dependencies

8. ในกรณีที่ต้องการใช้ Mongrel application server ให้พิมพ์คำสั่งนี้ครับ

$sudo gem install mongrel

ตอนนี้เราพร้อมสำหรับ ruby และ rails แล้วครับ

ติดตั้ง ruby บน mac os x

ถ้าใช้ Leopard ก็ไม่ต้องลง ruby และ rails ครับ

ติดตั้ง ruby บน windows

ติดตั้ง ruby หรือ rails บน windows มีทางลัดคือ dowload instance rails

http://instantrails.rubyforge.org

หรือค่อยๆ ทำดังนี้

1. download และ install ruby จากที่

http://rubyforge.org/frs/?group_id=167

2. download gem ตัวล่าสุดจาก

http://rubyforge.org/projects/rubygems/

หรือ download version 1.0.1 ได้ที่

http://rubyforge.org/frs/download.php/35283/rubygems-1.1.1.tgz

untar แล้วเข้า cmd.exe สั่ง run setup.rb ใน folder ที่พึ่ง untar ออกมา

c:\rubygems\>ruby setup.rb

3. สั่ง update gem ให้ทันสมัย

c:\> gem update --system

เป็นอันเรียบร้อย สามารถใช้งาน ruby บน windows ได้เลย

Interactive Ruby (irb)

irb หรือ Interactive เป็นโปรแกรมเล็กๆ มักลงคู่กับ ruby เสมอ เหมือนการใช้งาน cmd, terminal, shell หรือ dos ที่เราสั่งงานคอมพิวเตอร์ที่ละคำสั่ง irb ก็คล้ายกัน จะต่างก็ตรงที่เป็นชุดคำสั่งภาษา ruby

user$ irb
>> 5+3
=> 8
>>

ทดลองเปิด console ขึ้นมาครับ ถ้าบน windows ให้เปิด cmd (Start -> run -> cmd.exe) บน mac ให้เปิด terminal (Application -> utilities -> Terminal.app) ส่วนบน Linux ผมคิดว่าทุกคนคงเปิด terminal ได้อยู่แล้ว

ให้ทดลองพิมพ์ irb ครับ สุดยอดเครื่องคิดเลขอยู่ตรงหน้าคุณแล้ว ;)

เราสามารถทดลองคำสั่ง ruby ได้คำสั่งต่อคำสั่งเลยครับ เช่น

>>say = "I love Ruby"
>>puts say
 
>>say['love'] = "*love*"
>>puts say.upcase
 
>>5.times { puts say }

หรือทดลองใช้แทนเครื่องคิดเลข

>> a = 3 ** 2
>> b = 4 ** 2
>> Math.sqrt(a+b)

ทดลองสร้าง function

>> def h
>>   puts "Hello World!"
>> end
=> nil
>> h
Hello World!
=> nil
>> h()
Hello World!
=> nil

คำสั่ง h หรือการสร้าง function h จะ return ค่าว่าง (nil) การเรียก function ใน ruby ไม่จำเป็นต้องใส่วงเล็บ

การใช้ irb ช่วยให้การทดสอบคำสั่งต่างๆ ของ ruby ทำได้อย่างรวดเร็ว ช่วยลดเวลาในการเรียนรู้ ruby ได้มากทีเดียวครับ

Hello World (hello.rb)

ทดลองเขียนโปรแกรมกัน เริ่มด้วยการสร้างไฟล์ hello.rb โดยมีเนื้อความด้านในดังนี้

# hello.rb
puts('hello world')

จากนั้นทดลองเรียกใช้โปรแกรมของเรา ผ่าน ruby

ruby hello.rb

จะได้ผลดังนี้

hello world

คำสั่ง puts จะคล้ายกับคำสั่ง println มันจะทำหน้าที่พิมพ์ string ที่เราส่งให้กับมันออกมา ส่วนสิ่งที่อยู่หลัง "#" ภาษา ruby จะถึอว่าเป็น comment มันจะข้ามไปโดยไม่สนใจ เราสามารถใส่ comment ลักษณะนี้ก็ได้

puts 'hello world' # say hello

โดยปกติ ruby จะดูว่าจบแต่ละคำสั่งจาก "end of line" แต่เราจะใช้ ";" เพื่อแสดงว่าจบคำสั่งก็ได้ ในกรณีที่ต้องการให้ในหนึ่งบรรทัดมีสองคำสั่ง

puts('hello world');
 
puts('hello '); puts('world')

โดยทั่วไปเราจะไม่ค่อยเห็น ";" ในโปรแกรม ruby เพราะเรามักใช้คำสั่งละ 1 บรรทัด

ในกรณีที่หนึ่งคำสั่งไมได้จบในบรรทัดเดียว parser ของ ruby จะดูสัญลักษณ์สุดท้ายเพื่อวิเคราะห์ว่าจบคำสั่งหรือยัง

x = 10 +
    20 + 30

หรือเราสามารถใส่ "\" เพื่อแสดงการขึ้นบรรทัดใหม่ โดยไม่จบคำสั่งก็ได้

x = 10 \
    + 20 + 30

อีกสิ่งหนึ่งเราเราควรรู้ก่อนเริ่มเขียน ruby คือความแตกต่างของ

puts 'hello world'

และ

puts "hello world"

ทั้ง single quote และ double quote ต่างก็เป็น string แต่การใช้ 'abc\n' จะนับจำนวนตัวอักษรได้ 5 ตัว ส่วน "abc\n" จะนับได้ 4 ตัว นั่นเพราะ ruby แปล \n ที่อยู่ใน "" ว่าเป็นการขึ้นบรรทัดใหม่ ส่วนการใช้ '' ruby จะมองเป็น '\' และ 'n'

Variables

การตั้งชื่อตัวแปรในภาษา ruby คล้ายกับการตั้งชื่อตัวแปลในภาษาอื่นๆ เช่น

สังเกตสองบรรทัดแรก “max_length” และ “maxLength” ในภาษา java ธรรมเนียมปฏิบัติในการตั้งตัวแปลของเค้าคือ “maxLength” แต่ธรรมเนียมปฏิบัติของ ruby เราจะใช้ “max_length” โดยให้เหตุผลว่าการใช้ “_” ทำให้เขียนผิดได้ยากกว่า

ในภาษา ruby “maxlength” ไม่เท่ากับ “maxLength” เพราะมันคำนึงถึงตัวใหญ่ตัวเล็กด้วย (case sensitive) ดังนั้นการเขียน max_length จึงผิดได้ง่ายกว่า แต่โดยส่วนตัวผมชอบ max_langth เพราะมันอ่านง่ายกว่าครับ

สำหรับตัวแปล “___” เป็นตัวแปลที่สามารถตั้งได้ แต่ไม่ควรทำอย่างยิ่งครับ

ที่สำคัญอีกอย่างคือ ruby ไม่ต้องประกาศ type ให้กับตัวแปล เราสามารถกำหนดค่าให้มันได้เลย

first_name = "Apirak"
last_name = "Panatook"
full_name = fitst_name + ' ' + last_name

เมื่อเรากำหนดค่าให้กับ first_name โปรแกรม ruby จะรู้ทันทีว่า first_name ตัวเป็น object String หากเราเปลี่ยนค่าที่เก็บในตัวแปล ชนิดของตัวแปลจะเปลียนตามทันที ทดสอบได้โดยใช้ method class เช่น

iam = "Number" => Number
iam.class => String
iam = 28
iam.class => Fixnum

ขอให้สนุกกับการเขียน ruby นะครับ

Fixnums and Bignums

Object สำหรับเลขจำนวนเต็มใน ruby มีสองตัวคือ Fixnums และ Bignum สำหรับตัวแปลที่มีจุดทศนิยมเช่น 7.5, 3.14.159 หรือ 10.0 จะเป็น Float แม้ว่าใน ruby ตัวแปลสามารถเปลี่ยน type ได้ทันที (Dynamic type)

สำหรับการหารจำนวนเต็มสองจำนวน ผลลัพท์ไม่ได้เป็น Float แต่เป็นจำนวนเต็มและไม่ปัดเศษให้ด้วย

6/3 => 2
7/3 => 2
8/3 => 2
9/3 => 3

ตัวแปล Fixnums มีขนาด 31 bits (ลองคำนวนตัวต่ำสุดสูงสุดดูนะครับ) สำหรับ Bignum เราสามารถใส่ค่าได้ไม่จำกัดครับ (ใช้ได้เต็มที่เท่าหน่วยความจำที่ให้ ruby ครับ)

แม้ว่าการหารจำนวนเต็มสองจำนวนจะไม่กลายเป็น float แต่สามารถเปลี่ยน Fixnum -> Bignum หรือ Bignum -> Fixnum ได้

2 => Fixnum
437 => Fixnum
2**437 => Bignum
1234567890 => Bignum
1234567890/1234567890 => is 0, is Fixnum

ใน ruby ไม่มี a++ หรือ a— ให้นะครับ เราจะใช้ +=, -=, *=, /= แทน

a=4
a += 1 # a is now 5
a -= 2 # a is now 3
a *= 4 # a is now 12
a *= 2 # a is now 6
<blockcode lang="ruby">
 
a += 1 มีค่าเท่ากับ a = a+1

float

ตัวอย่างของตัวเลขที่จะเป็น float คือ

3.14158
-2.5
6.0
0.00000000111

ถ้าต้องการให้ผลลัพธ์ของการคูณหรือการหารมีค่าเป็น float เราต้องมีค่าที่เป็น float อยู่ในสมการ

2.5+3.5 # => 6.0 is float
0.5*10 # => 5.0 is float
10*0.5 # => 5.0 is float
8.0/3.0 # => 2.66666666 is float
.5/10 # => Syntax error

ruby ไม่มี primitives data type

ภาษา Java, C# หรือ C++ มี int, float, boolean เป็นต้น ที่เป็น primitive data type ไม่ได้เป็น Object ทำให้หลายครั้งถ้าเราต้องการ Object Integer เราต้องแปลง primitive ให้เป็น Object ซะก่อน แต่สำหรับ Ruby ขอให้ programmer มั้นใจว่าทุกอย่างในภาษานี้เป็น Object ทั้งหมด ไม่มี primitive data type ให้สับสนดังนั้นเราจึงเรียกใช้งานตัวเลขได้ตรงๆ เหมือนการเรียกใช้ Object ได้เลย

3.7.round    # Give us 4.0
3.7.truncate # Give us 3
-123.abs     # Give absolute value, 123
1.succ       # Successor, or next number, 2

เราสามารถตรวจสอบได้ว่า ตัวแปลของเราเป็น class อะไร

7.class  # Give us Fixnum
88888888888888.class # Give us Bignum
3.14159.class # Give us Pi :P Sorry. It give us Float

ทุก Object ต่างสืบทอดมาจาก class Object และได้ความสามารถพื้นฐานของ Object มาด้วย

'hello'.nil?  # false
44.to_s # Return a two-character string '44'
'hello'.to_s # Return 'hello'

Object สามารถบอกได้ว่าตัวเองเป็นค่าว่างหรือไม่ และสามารถ Override method ของแม่ได้เหมือน toString ใน java

But something is no Object

ถ้าทุกอย่างใน ruby เป็น Object หมด จะเกิดอะไรขึ้นถ้าบางตัวแปลไม่มี Object จริงๆ

black = nil

ทดลองหา class ของตัวแปล black

black = nil
black.class # => NilClass

แม้แต่ nil ก็เป็น Object ครับ :) แต่น่าเสียดายว่าเราไม่สามารถสร้าง instances ของ NilClass ได้ มันมีได้แค่ instance เดียวคือ nil ครับ

จริง, เท็จ และ ค่าว่าง (true, false, and nil)

ภาษาruby มีการใช้ boolean เหมือนภาษาอื่นๆ

1 == 1           # true
1 == 2           # false
'russ' == 'smart' # false
(1 < 2)           #true
(4 > 6)           # false
 
a = 1
b = 10000
(a > b)           # false
 
(4 >=4)         # true
(1 <=2)         # true

true กับ false ก็เหมือนกับ nil ไม่ได้เป็น primitive แต่เป็น instance ของ TrueClass และ flase ก็เป็น instance ของ FalseClass

and และ or ในภาษา ruby สามารถใช้สัญลักษ์แทนได้

(1 == 1) and (2 == 2)  #true
(1 == 1) and (2 == 3)  #false

เราสามารถใช้ && แทนได้

(1 == 1) && (2 == 2)  #true
(1 == 1) && (2 == 3)  #false

ส่วนคำสั่ง or เราสามารถใช้ || แทนได้

(1 == 1) or (2 == 2)  #true
(1 == 2) || (2 == 3)  #false

สำหรับ “not” เราจะใช้ ! แทน

not (1 ==2)  #true
! (1 == 1)     # false
not false       #ture

ทุกตัวแปล (Object) สามารถตีความเป็นเป็น boolean ได้ โดยจำง่ายๆ ว่า false และ nil ถือว่าเป็นเท็จ นอกนั้นเป็นจริงหมด

true and 'fred' #true เพราะว่า ‘fred’ ไม่ใช่ nil จึงถือว่าเป็นจริง

'fred' && 44 # true เพราะ ‘fred’ และ 44 ไม่ใช่ nil จึงเป็นจริงทั้งคู่

nil || false  #false เพราะ false และ nil เป็นเท็จทั้งคู่

ที่น่าจะจำนอกจากนั้นก็มี

if 0
  puts('zero is true!')
end

ไม่เหมือน c หรือ c++คำสั่งข้างต้นจะพิมพ์คำว่า ‘zero is true!’ เพราะใน ruby ค่า “0” เป็นจริง

if ""
  puts('empty string is true!')
end

คำสั่งข้างต้นพิมพ์คำว่า ‘empty string is true!’ เพราะ empty string ไม่ใช่ nil ถ้าต้องการตรวจสอบว่า string empty หรือเปล่าให้ใช้

s = ""
if s.empty?
  puts('s is empty!')
end

เงื่อนไข (if)

บทที่แล้วเขียนเรื่อง จริง, เท็จ และ ค่าว่าง (true, false, and nil) ไหนๆ เรารู้เรื่อง boolean แล้ว ได้เวลาเอามาใช้

การใช้ if ในกรณีที่มี else

money = 10
 
if (money >= 10)
  puts 'You can buy Ray'
else
  puts 'You can't buy Ray'
end

ถ้าต้องการมีเงื่อนไขมากกว่า 1 อย่าง สามารถใช้ elsif ได้

if (money < 10)
  puts 'You can't buy Ray'
elsif (money < 20)
  puts 'You can buy Ray'
elsif (money < 30)
  puts 'You can buy Big Ray'
else
  puts 'You can buy more than one Ray'
end

สังเกตุว่า elsif มีแค่ 5 ตัวอักษร ไม่ใช่ elseif และมีความหมายไม่เหมือนกับ else if

หลายคนอาจจะคุ้นเคยกับการใส่วงเล็บหลัง if แต่ใน ruby เราไม่ต้องใส่ก็ได้

if money < 10
  puts 'You can't buy Ray'
elsif money < 20
  puts 'You can buy Ray'
elsif money < 30
  puts 'You can buy Big Ray"
else
  puts 'You can buy more than one Ray'
end

นอกจากใช้ if แบบที่เราคุ้นเคยแล้ว เราสามารถใช้ if ในแบบที่เราคุ้นเคยมากกว่าได้

puts ('Give me more Ray') if money >= 100

เขียนแบบนี้ใกล้เคียงภาษาคนมากขึ้น และเพื่อให้ใกล้ขึ้นไปอีก ใน ruby มีคำสั่ง if not มาให้เราใช้

unless monty >= 10
  puts 'Don't give him a Ray'
end

unless จะทำงานเมื่อเงื่อนไขเป็น false และแน่นอนว่าเราเขียนแบบที่เราคุ้นเคยได้

puts ('Don't give him a Ray') unless money < 100

Loops

ในภาษา ruby มี loop ให้ใช้สองแบบครับ

แบบแรกคือ loop มาตฐาน while และ for

i = 0
while i < 4
  puts("i = #{i}")
  i = i + 1
end

เมื่อเป็น “จริง” ถึงทำ ผลลัพธ์ที่ได้คือ

i = 0
i = 1
i = 2
i = 3

สำหรับ while เราสามารถเปลียนคำว่า while เป็นคำว่า until ได้ในกรณีที่ต้องการให้เป็น “เท็จ” ถึงทำ

i = 0
until i >= 4
  puts("i = #{i}")
  i = i + 1
end

ผลลัพธ์ที่ได้เหมือนกันครับ

สำหรับ loop for เราไม่ค่อยได้ใช้เท่าไหร จะข้ามไปครับ :p

แบบที่สองคือการ loop โดยดึงค่ามาจาก Array โดยใช้คำสั่ง each

array = ['first', 'second', 'third']
array.each do |x|
  puts(x)
end

ผลลัพธ์ที่ได้คือ

first
second
third

การใช้ each จะคล้ายกับการใช้ iterator ใน java

ArrayList list = new ArrayList();
list.add("first");
list.add("second");
list.add("third");
 
for( Iterator i = list.iterator(); i.hasNext();) {
  System.out.println(i.next());
}

สำหรับคนที่อยากได้ for i = 1 to 10 { } ใน ruby เราใช้

(1..10).each do |i|
   puts i
end

คำสั่งมาตรฐานสำหรับ Loop อย่าง break และ next

ตัวอย่าง break

name = ['george', 'mike', 'gary', 'diana']
 
name.each do |name|
  if name == 'gary'
    puts('Break!')
    break
  end
  puts(name)
end

ผลที่ได้จะหยุด loop ที่ gary

george
mike
Break!

ตัวอย่าง next

name = ['george', 'mike', 'gary', 'diana']
 
name.each do |name|
  if name = 'gary'
    puts('Next!')
    next
  end
  puts(name)
end

ผลที่ได้จะข้าม gary ไป

george
mike
Next!
diana

More about String

string ใน ruby สามารถเขียนได้ทั้ง ” และ “” ดูความแต่กต่าง

first = 'Mary had'
second = ' a little lamb'

เราสามารถต่อ string ได้ด้วย +

poem = first + second

ค่าของ poem จะเท่ากับ

Mary had a little lamb

นอกจากนี้คำสั่งที่ใช้บ่อยๆ ของ string เช่น

คำสั่ง length เพื่อวัดความยาวของ string

first.length #=> 8

คำสั่ง upcase, downcase

poem.upcase #=> MARY HAD A LITTLE LAMB

porm.downcase #=> mary had a little lamb

คำสั่ง gsub และ split

first.gsub(/ry/,'re') #=> mare had

first.split(" ") #=> ["mare", "had"]

first.split("( )") #=> ["mare", " ", "had"]

คำสั่ง <=> เพื่อเปรียบเทียบ string

first <=> second #=> false

เรามอง string คล้ายกับ array เราสามารถเปลี่ยนตัวอักษรใน string ได้แบบนี้

poem[0] = 'G'
puts(poem) #=> Gary had a little lamb
puts(poem[1]) #=> 97, the code for 'a'

ในการสร้าง String หลายบรรทัด การใช้ ” และ “” อาจดูไม่ดีนัก เราสามารถใช้ %Q{ } แทนได้

multiline_string = %Q{
first line
second line
}

Symbols

แต่เดิม String ในภาษา C, C++ เป็น mutable พอมาใน Java และ C# กลายเป็น immutable พอมาถึง Ruby มันกลับมาเป็น mutable อีกครั้ง ถ้าจะเถียงกันเรื่อง mutable ดีหรือ immutable ดีกว่าคงเถียงกันไม่จบ ใน Ruby เลยมี String เป็น mutable และ Symbols เป็น immutable

เวลาประกาศ Symbol เราจะขึ้นต้นด้วย colon

:a_symbol
:an_other_symbol
:first_name

ชาว Ruby มักใช้มันตอนที่ต้องการแสดงความเป็นตัวชี้ (identifiers) เพราะแน่ใจว่า string ตัวนี้มีอยู่ใน memory แค่ที่เดียว ตัวอย่างการใช้งานเช่น

Food.eat(:all, :by => "hand", :on => "table")

หรือใช้ในคำสั่ง find ของ rails

Employee.find(:all, :conditions => 'name LIKE "Paul%"')

คุณ pphetra อธิบายเกี่ยวกับ Symbol ว่า
Symbol ถือเป็น object พิเศษแบบหนึ่ง นั่นคือ มันจะมีแค่ตัวเดียวเสมอ (ถ้าชื่อเหมือนกัน)
สมมติเราอ้างถึง :a ไป 10 ครั้งใน program
ก็จะมี object symbol :a เกิดขึ้นใน memory แค่ตัวเดียว
แต่ถ้าเราใช้ string ‘a’ 10 ครั้งใน program
ก็จะมี object string ‘a’ เกิดขึ้น 10 ตัวใน memory

Array

ตัวอย่างการใช้งาน array

x = []  #=> สร้าง arry ว่างๆ
y = Array.new #=> สร้าง array ว่างๆ อีกอัน
a = ['neo', 'trinity', 'tank'] #=> สร้าง array ขนาดเท่ากับ 3

Array เริ่มต้นที่ 0

a[0] #=> neo
a[1] #=> trinity

ดูขนาดของ Array จากคำสั่ง length และ size (คือคำสั่งเดียวกัน)

puts(a.length) #=> 3
puts(a.size) #=> 3

เราสามารถเพิ่มข้อมูลใน array ได้ตลอด

a[3] = 'morphieus'

ถ้าเติมค่าให้ array เลยตำแหน่งสุดท้าย ค่าของ size จะขยายออกไปทันที

a[6] = 'keymaker'
a.size #=> 7

ได้ค่าของ array a คือ

a = ['neo', 'trinity', 'tank', 'morphieus', nil, nil, 'keymaker']

อีกวิธีในการเพิ่มค่าให้ array คือใช้เครื่องหมาย <<

a << ‘mouse’

ค่า mouse จะไปต่อท้าย a ไม่ว่าตัวแปลสุดท้ายของ a จะเป็น nil ก็ตาม

array ใน ruby มีคำสั่ง sort และ reverse ในตัวเอง

a = [77, 10,120, 3]
a.sort # return => [3, 10, 77, 120]
 
a = [1, 2, 3]
a.reverse # return => [3, 2, 1]

สำคัญว่าคำสั่งทั้งสองไม่ได้ sort หรือ reverse ค่าใน array a เลย ทำแค่ return array ใหม่เท่านั้น ถ้าต้องการให้ a เปลี่ยนค่าไปจริงๆ ต้องเรียก method ชื่อ sort! และ reverse!

a = [77, 10,120, 3]
a.sort! # a => [3, 10, 77, 120]
 
a = [1, 2, 3]
a.reverse! # a => [3, 2, 1]

เครื่องหมาย “!” เป็นที่รู้กันในคนเขียน ruby ว่าหมายถึงการเตือนว่า method นั้นจะเปลี่ยนค่า object นั้นไปด้วย เหมือนเครื่องหมาย “?” ที่หมายถึงการถามเพื่อให้ method นั้นตอบเป็น true/false

(คนเขียน java คงคุ้นกับ “is” เราใช้เหมือน “?” ใน ruby ครับ)

Hashes

Hashes เป็นการเก็บข้อมูลคล้ายกับ Array ต่างกันที่ Hashes จะใช้ key word ในการอ้างอิงค่า แทนที่จะนับตามลำดับ

family = { :dad => "Sumon", :mom => "Viraporn", :sister => "Sasivimon"}

เราสามารถใช้ :dad หรือ “dad” ก็ได้ เพราะเป็น string เหมือนกัน แต่ที่เรานิยมใช้ :dad เพื่อแสดงความเป็น keyword ให้กับคนที่มาอ่าน code ของเรา (ดูความแตกต่างของ : และ “” ได้ในบท string)

วิธีการเรียกใช้ข้อมูลใน hashes

puts family[:dad]  #=> "Sumon"

แสดงข้อมูลที่อยู่ใน hashes

family.keys #=> [:dad, :mom, :sister]
family.value #=> ["Sumon", "Viraporn", "Sasivimon"]

เราสามารถเพิ่มข้อมูลลงใน hashes ได้เรื่อยๆ

family[:dog] = "Bobby"
family.keys #=> [:dad, :mom, :sister, :dog]

ผมพบว่าการส่งค่าให้กับ function ด้วย hashes เป็นวิธีที่มีปรสิทธิภาพมาก ลองเทียบแบบนี้

แบบแรกไม่ใช้ hashes

def love(hunter , victim)
   puts "#{hunter} love #{victim}"
end
 
# มี code กั้น 2000 บรรทัด หรืออยู่คนละไฟล์
 
love("Somsri","Somchai")

ปัญหาเกิดขึ้นตอนที่เรากลับมาอ่านโปรแกรมของเราแล้วไม่รู้ว่าจริงๆ ตัวแปลที่เราส่งไปอะไรมาก่อนมาหลัง ลองเทียบกับ

def love(option)
   puts "#option[:hunter] love #option[:victim]"
end
 
... 2000 line of code ...
 
love(:hunter =>"Somsri", :victim =>"Somchai")

แบบนี้เราจะอ่านรู้เรื่องว่าส่งตัวแปลอะไรไป

อย่างหนึ่งที่ควรรู้คือใน ruby ถ้าตัวแปลตัวสุดท้ายเป็น hashes เราไม่ต้องใส่ { } ดังนั้น การเรียนคำสั่ง love เราสามารถเรียกแบบนี้ได้

love :hunter => "Somsri", :victim => "Somchai"

หรือ

has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"

เป็นต้น

Regular Expressions

ruby มีเครื่องหมาย =~ เอาไว้เทียบ regular expression กับ String

/old/ =~ 'this old house' # return 5

เป็นตำแหน่งของ old ใน ‘this old house’

/Russ|Russell/ =~ 'Fred' # return nil

เพราะใน Fred ไม่มี Russ หรือ Russell

/.*/ =~ 'any old string' # return 0

เพราะ .* หมายถึง string ใดๆ

เราสามารถเอา =~ ไปใช้ในประโยคเงื่อนไข

if /Russ|Russell/ =~ s
  puts "Russ or Russell"
end

หรือใช้เพื่อกำหนดค่าให้ตัวแปล

x = /old/ =~ 'this old house'

สำหรับการใช้สุดยอดเครื่องมืออย่าง regular expression ลองอ่านใน wiki pedia หรือลองเล่มนี้ครับ Mastering Regular Experession

A Class of You Own

Class เป็นเหมือนแบบแปลนของ Object มาลองสร้าง Class แรกกันดู

Class BankAccount
    def initialize(account_owner)
      @owner = account_owner
      @balance = 0
    end
 
    def deposit(amount)
      @balance = @balance + amount
    end
 
    def withdraw(amount)
      @balance = @balance - amount
    end
end

เราลองไล่โปรแกรมที่ละชุดนะครับ

Class BankAccount
  ...
end

เป็นการกำหนดชื่อของ Class เพื่อเรียกใช้ในภายหลัง เรานิยมสร้างชื่อของ Class ให้ตัวหน้าเป็นตัวใหญ่และคำที่ตามมาไม่มี “_” แต่ใช้ตัวอักษรใหญ่เพื่อแยกระหว่างคำ ไม่เหมือนการตั้งชื่อตัวแปร

        ...
    def initialize(account_owner)
      @owner = account_owner
      @balance = 0
    end
        ...

ใน ruby เราสร้าง method ด้วยคำสั่ง def ตามด้วยชื่อของ method นั้นๆ ตามด้วย argument ที่ต้องการส่งให้กับ method นั้นๆ สิ่งที่พิเศษสำหรับ method นี้คือ initialize ซึ่งทำให้ method นี้เป็น method แรกที่ถูกเรียกใช้เมื่อเราแปลง Class เป็น object

@owner = account_owner

ตัวแปรที่ขึ้นต้นด้วย @ จะหมายถึง instance variable ซึ่งทำเราสามารถเรียกใช้ตัวแปรที่ชื่อ owner ได้จากที่ใดก็ได้ใน class ในบรรทัดนี้เรากำหนดให้ owner มีค่าเท่ากับ account_owner ซึ่งเป็นตัวแปรที่มีคนส่งมาให้ผ่าน argument ของ method

เมื่อเรานำ @owner และ @balance มาไว้ที่ initialize จะเป็นเหมือนเรากำหนดค่าเริ่มต้นให้กับตัวแปรทั้งสองตัวนี้ หากเราไม่เขียน initialize ภาษา ruby จะสร้าง Object โดยมองว่า method initialize ไม่มีอะไรอยู่ข้างใน

การสร้าง Object จาก class เราสามารถทำได้ดังนี้

my_account = BankAccount.new('Russ')

เราจะได้ Object BankAccont ที่มีตัวแปร @owner เท่ากับ ‘Russ’ และ @balance มีค่าเริ่มต้นเท่ากับ 0

        ...
    def deposit(amount)
      @balance = @balance + amount
    end
 
    def withdraw(amount)
      @balance = @balance - amount
    end
        ...

deposit และ withdraw ใช้สำหรับการกำหนดค่าให้กับ @balance จากนั้น method จะ return ค่าสุดท้้ายให้กับคนที่เรียกมัน จะเห็นว่าสิ่งสุดท้ายที่ method นี้ทำคือกำหนดค่าให้ @balance ดังนั้นสิ่งที่ return คือ @balance

my_balance = my_account.deposit(100,000)

ค่าของ my_balance จะเท่ากับ 100,000 แต่ถ้าเราต้องการค่าของ balance โดยไม่ต้อง deposit หรือ withdraw ล่ะ

Getting at the Instance Variables

หลังจากที่เราได้ Class BankAccount จากบทที่แล้ว เรามาลองเรียก balance ออกมาจาก method Bank account ดู

 my_account = BankAccount.new('Russ')
        puts(my_account.balance)

เราจะเจอ error ว่า

account.rb:8: undefined method 'balance' ... (NoMethodError)

แปลว่าแทนที่มันจะไปหาตัวแปล balance มันกลับไปตามหา method balance แทน … ไม่ต้องทดลอง my_account.@balance นะครับ ตัวแปลใน rails ไม่สามารถเรียกใช้โดยตรงได้จากนอก object

แล้วถ้าต้องการเรียกใช้ตัวแปลจากนอก object สิ่งที่ต้องทำคือกำหนด accessor method ให้กับตัวแปล

 def balance
      @balance
    end

สิ่งสุดท้ายที่ method balance ทำคือค่า @balance ดังนั้นสิ่งที่ return ออกมาคือ @balance หากทดลองเรียบกใช้แบบเดิม

 my_account = BankAccount.new('Russ')
        puts(my_account.balance)

Ruby จะแสดงค่า @balance ออกมาอย่างถูกต้อง … แล้วถ้าต้องการกำหนดค่าให้กับ @balance ล่ะ?

 def set_balance(new_balance)
      @balance = new_balance
    end

set_balance สามารถทำงานได้ดีเยี่ยมเหมือน method balance

my_account.set_balance(100)

ปัญหาคือมันไม่สวยและดูไม่เป็นธรรมชาติ มันจะดีกว่านี้ถ้าเป็น

my_account.balance = 100

ทำอย่างไรเราถึงจะสามารถสร้าง method ให้เป็นธรรมชาติและ ได้คุณสมบัติ encapsulation ไปพร้อมๆ กัน

 def balance=(new_balance)
      @balance = new_balance
    end

เปลี่ยนชื่อ method โดยใส่ “=” ลงไป

my_account.balance=(100)

หรือ

my_account.balance= 100

หรือ

my_account.balance = 100

ถ้าไม่ต้องการจ้างคนมาเขียน set, get จำนวนมาก Ruby มีคำสั่ง attr_accessor ซึ่งจะช่วยสร้าง set และ get ในแบบที่เราพูดถึงข้างต้น

attr_accessor :balance

วิธีนี้ช่วยลด method ได้หลายบรรทัดทีเดียว หรือจะใช้ทีละหลายๆ ตัวแปลก็ได้

attr_accessor :balance, :grace, :agility

และในกรณีที่ต้องการให้อ่านอย่างเดียวให้ใช้ attr_reader

attr_reader :name

ทีนี้ลองเดาว่า attr_writer ใช้ทำอะไร

Inheritance, Subclass, and Superclasses

Ruby ทำได้แค่ single inheritance ทุก class จะมีแม่เพียง class เดียว ถ้าไม่มีการระบุทุก class จะเป็นลูกของ class Object

วิธีการกำหนด superclass

class InterestBearingAccount < BankAccount
  def initialize(owner, rate)
    @owner = owner
    @balance = 0
    @rate = rate
  end
 
  def deposit_interest
    @balance += @rate * @balance
  end
end

InterestBearingAccount เป็น class ลูกของ BankAccount ดังนั้นจึงได้คุณสมบัติ (method) ทั้งหมดของ BankAccount มาใช้

ถ้าเราต้องการยกเรื่องการกำหนด @balance และ @owner ให้กับ class แม่ เราสามารถกำหนดได้ดังนี้

  def initialize(owner, rate)
    super(owner)
    @rate = rate
  end

คำสั่ง super จะ ค้นหา method ที่มีชื่อเหมือนกันใน class แม่ แล้วเรียก method นั้น ในตัวอย่างคำสั่ง super(owner) จะค้นหา method ที่ชื่อ initialize ใน class แม่ พร้อมเรียกและส่งตัวแปร owner ไปให้ ในกรณีที่ไม่เจอ ruby จะดูต่อไปที่ class แม่ของแม่ต่อๆ ไป ถ้าไม่เจอจึงแสดง error ออกมา

สิ่งนี้ต่างจากภาษาอื่นๆ เราต้องระวังไว้ว่า class ลูกจะไม่เคยเรียก initialize ของ class แม่เอง ถ้าเราไม่เรียกให้

Argument Option

ตัวอย่างการใช้ Argument ใน ruby

def create_car( model, convertible=false)
  ...
end

เราสามารถเรียกใช้ create_car โดยส่งตัวแปรให้เพียงตัวเดียวหรือสองตัวก็ได้

create_car('sedan')
create_car('sports car', true)
create_car('minivan', false)

กรณีที่ method ถูกเรียกโดยส่งค่าให้เพียงตัวแปรเดียว method จะกำหนดค่า false ให้ convertible โดยอัตโนมัติครับ

argument ที่มีค่ากำหนดไว้แล้วต้องเรียงอยู่หลังสุดนะครับ

method ของ ruby ทำให้เรากำหนด argument ใด้ flexible มากๆ เช่น

  def add_students(*names)
    for student in names
      puts("adding student #{student}")
    end
  end
 
  add_students( "Fred Smith", "Bob Tanner")

เมื่อเราใส่ * ไว้ท้ายสุด ruby จะแปลงค่าที่อยู่ต่อท้ายเป็น array อย่างในตัวอย่าง ruby จะทำให้

names = ["Fred Smith", "Bob Tanner"]

ลองดูอีกตัวอย่างที่เราใสทั้ง regular arguments และ arguments array ไว้ด้วยกัน

  def describe_hero(name, *super_powers)
    puts("Name: #{name}")
    for power in super_powers
      puts("Super power: #{student}")
    end
  end

ตัวอย่างการเรียกใช้ method hero

describe_hero("Batman")
describe_hery("Flash", "Speed")
describe_hero("Supreman", "can fly", "x-ray vision", "invulnerable")

Exception

ภาษาส่วนใหญ่ มักจัดการกับปัญหาที่คาดไม่ถึง ด้วย exception เมื่อโปรแกรมมีปัญหาแทนที่จะออกจากโปรแกรมไปเลย ถ้านักพัฒนาเขียน exception ไว้โปรแกรมจะก็จะมาเรียก exception แทนที่จะออกจากโปรแกรมไป

ruby ไม่มี exception :p

เราจะใช้ begin/rescue ในการ catch exceptions แทน

begin
  quotient = 1/0  #Boom!
rescue
  puts('Something bad happen')
end

ในกรณีข้างต้น แทนที่โปรแกรมจะแสดง error

ZeroDivisionError: divided by 0
    from (irb):1:in `/'
    from (irb):1

โปรแกรมจะแสดงคำว่า “Something bad happen”

ถ้าเราต้องการแสดงผลให้ตรงกับปัญหาที่เกิดขึ้นเราสามารถ กำหนดได้ว่าปัญหาแบบไหนที่ให้ rescue

begin
  quotient = 1/0  #Boom!
rescue ZeroDivisionError
  puts('You tried to divide by zero')
end

ในตัวอย่างข้างต้นถ้าปัญหาที่เกิดไม่ได้เป็นการหารด้วยศูนย์ โปรแกรมจะแสดง error ตามปกติโดยไม่เข้า rescue

ถ้าเรารู้อยู่แล้วว่า method ของเรามีโอกาศ error แทนที่จะรอให้เกิด error ก่อน เราสามารถตรวจสอบแล้วส่งไป rescue ได้เลย

if denominator == 0
  raise ZeroDivisionError
end
return numerator/denominator

ในกรณีที่เราต้องการสร้าง exception ของตนเอง ruby มีทางลัดให้

>>> raise 'You did it wrong'
RuntimeError: You did it wrong

ruby จะสร้าง Object RuntimeException ขึ้นมาแล้วใช้ ‘You did it wrong’ เป็นข้อความแนบไปกับ exception ที่เกิดขึ้น ในกรณีนี้เราไม่ต้องใช้ rescue เลย