原本是在練習 Kubernetes 的時候,由於需要模擬節點,所以會使用到 Virtualbox 來建立虛擬機,而使用的軟體是 Vagrant。
為了要更能理解 Vagrant 的設定寫法,所以才有了這一篇學習 Ruby 的文章。
Ruby 是一種物件導向、指令式、函數式、動態的通用程式語言。在20世紀90年代中期由日本電腦科學家松本行弘(Matz)設計並開發。
本筆記參考以下課程:はじめてのRuby on Rails入門-RubyとRailsを基礎から学びウェブアプリケーションをネットに公開しよう
初期先以 Syntax 為主要學習目標。
Installation
- 安裝連結:Download Archives
可以看到有一個 Devkit 的東西在,這個為了讓 Ruby 可以去使用以 C 寫的第三方 Library 用的,如果您要引入的 library 是使用純 Ruby 寫的,那麼就不需要安裝 devkit。
Ruby 跟 Java 一樣都有一個 VM,稱為 RVM,儘管實現方式有所不同。
Document
- 官方文件:Ruby – 程式設計師的摯友
- 課程中有使用到的 Sample Code:「よくわかるRuby on Rails入門」サポートサイト
Ruby 入門
要執行 Ruby 程式有兩種方法:
- 使用 irb (Interactive Ruby),類似互動式的方法
- 直接在 CMD 輸入 irb 就可以進入環境。
- 執行以檔案形式保存的程式
在 Ruby 中, nil 是一個物件,表示『沒有值』或『未定義的值』,可以用來代表一個變數未被賦值或一個方法沒有返回值。
在 IRB 中,有以下快捷鍵可以使用:
快捷鍵 | 作用 | 說明 |
---|---|---|
Ctrl + L | 畫面清除 | |
Ctrl + D | 離開 IRB | 輸入 exit 也有一樣的效果 |
Hello world
直接建立一個檔案:sample.rb
# puts 是 Ruby 中的一個方法,用於將字符串或其他數據輸出到控制台。
# 它的作用類似於 print 方法,但是在輸出完數據之後會自動換行。
puts "Hello world."
=begin
也可以像 python 直接輸入運算式
=end
puts 10+10
然後執行 ruby sample.rb 就可以看到結果。
- 可以注意到單行註解是用井字號來表示的,跟 yaml 一樣。
- 多行註解則是要使用等號 begin/end 來表示,但大部分的情況下還是使用井號來表示比較好看一點。
変数/定数
變數宣告不用加上任何單字,只要使用等於即可:
- 変数名 =式
# 前面不需要寫 var 等關鍵字做宣告
message = "hello ruby."
puts message
而常數的部分,基本上是將變數名稱改大寫開頭就行,但是在 ruby 中,大寫開頭的常數宣告一樣是可以去改變的,只是會報警告而已不會出錯,這點跟 python 一樣。
例如這樣的寫法:
VARIABLE = 1
VARIABLE = 2
puts VARIABLE
執行後會看到報警告:
"C:\Program Files\Ruby\3.2.2\bin\ruby.exe" H:/IntelliJ_Workspace/RubyPractice/lib/hensuu.rb
2
H:/IntelliJ_Workspace/RubyPractice/lib/hensuu.rb:9: warning: already initialized constant VARIABLE
H:/IntelliJ_Workspace/RubyPractice/lib/hensuu.rb:8: warning: previous definition of VARIABLE was here
另外關鍵字的部分可以參考官方文件:字句構造
Literal リテラル
所謂 literal 指的是可以直接寫在 Code 中表示的文字,舉例子比較好理解,有以下種類:
- String
- “message”
- 數字
- 123
- 陣列
- [‘a’,‘b’,‘c’]
- 關聯陣列 – 連想配列:也就是 Map,在 Ruby 中也被稱作 Hash
- { “name” => “John”, “age” => 30 }
數值型別
在 Ruby 中,數字相關的類別有以下幾種:
- Integer:整數類別,用於表示整數值。
- Float:浮點數類別,用於表示帶小數的數值。
- BigDecimal:高精度十進制類別,用於表示需要高精度計算的數值。
- Rational:有理數類別,用於表示分數形式的數值。
- Complex:複數類別,用於表示複數形式的數值。
由於 Ruby 在宣告變數時就能自動判定型別,關於整數與小數的部分只有插在有沒有小數點而已。
想要查看物件的類別與其擁有的方法,可以使用 class 與 methods:
# 列出型別
puts 1.class
# 列出 Interger 的方法
puts 1.methods.join ","
String
字串宣告分成兩種,雙括號與單括號,這與 Groovy 的 GString/String 是一樣的。
如果要將變數、換行特殊符號等置入字串中,需要使用雙括號才可以:
name = "John"
puts "Hello, #{name}!"
而且 String 在 Ruby 中是 Mutable 跟 Java 不一樣,是可以直接修改原本字串內容而不是新建字串:
str = "Hello, world!"
str[0] = "J"
puts str # => "Jello, world!"
而針對多行字串的寫法,Ruby 使用像是 Linux 中的here document 寫法:
text = <<~EOF
This is a multi-line text.
It can contain multiple lines.
EOF
puts text
字串與數字之間的轉換
Ruby 對於型別間無法像 Javascript 一樣自動轉換,需要使用轉換方法才可以:
# 需要使用 to_? 系列方法才能統一型別
puts 'Number is '+ 1.to_s
puts 1 + '2'.to_i
Exclamation mark methhod
可以參考以下文章:[ Ruby ] Methods 後面有!驚嘆號 (exclamation marks)
Ruby 中可以發現有些方法成對的出現,分為有無驚嘆號在方法後面。
有驚嘆號的方法代表的是『危險版本』方法,提示使用者該方法作用會改變物件本身,也就是 mutable 的方法操作。 反之沒有驚嘆號的表示結果會回傳的是新的物件,也就是 immutable 的方法。
驚嘆號本身並不會改變方法的行為。它只是一個命名慣例而已,作為方法名稱的一部分。
# frozen_string_literal: false
message = 'hello'
# 反轉卻未改變物件本身
puts message.reverse
puts message # hello
# 反轉作用在物件上
puts message.reverse!
puts message # olleh
- 在 Ruby 3 中,String 在所有檔案中預設凍結,開頭 frozen_string_literal 註解可以改變該檔案的這個預設行為
- 此為展示故將其改為 false
- 如果是 true (預設),你會看到 reverse! 方法會有紅字報錯。
- 參考 [2019 鐵人賽 Ruby on Rails] Day09 – frozen_string_literal: true 有什麼作用 !?
含有問號在方法名後面
在 Ruby 中,方法名後面加上問號(?)通常表示該方法返回的是一個 Boolean(true 或 false)。這種命名慣例用於表達方法的返回值是一個問題或條件的答案。
例如,如果有一個方法名為 empty? ,它可能用於檢查物件是否為空,返回值為 true 或 false。
演算子
參考官方文件:演算子式
大部分應該都與其他語言沒什麼差異。
運算子可以複寫,但也不是全部都可以,詳細看文件。
i++、i– 不存在
Ruby 沒這種寫法,請改用 i += 1 之類的寫法。
流程控制
先說明 Ruby 中,那些物件是屬於 True、那些又是 False。
基本上原則是 false 與 nil 屬於 false,其他都是 true
屬於 True 的物件有以下:
- true
- 所有數字,包含 -1、0 等
- 所有的 String,包含像空字串等皆是。
and/or/not 與 &&/||/! 的差異
Ruby 的運算子有多了 and/or/not 可以使用,這三個比起一般的符號相比優先度會比較低。兩類最好不要混用以免悲劇,請用括號。
詳細用法文件有寫。
条件分岐 if
num = (rand * 10).to_i
result = if num <= 3
"low"
elsif num <= 6
"medium"
else
"high"
end
puts result
- 這裡的語法在 else if 部分有點不一樣,是 elsif,請注意。
- 不需要括號包裹條件,但是也可以加上去沒差。
- Ruby 跟 Kotlin 一樣,if 都是表達式 ( expression ),擁有返回值,也就是你可以直接把結果賦予到變數中。
条件分岐 unless
只要視為 if 的反面用法即可,但是沒有類似對應 else if 的用法,else 還是有。
考慮到語意,unless 如果很難懂就不要應用。
num = (rand * 10).to_i
unless num > 5
puts "num<=5"
else
puts "num>5"
end
条件分岐 case
num = (rand * 10).to_i
isZero = case num
when 0
true
else
false
end
puts "is zero ? => #{isZero}"
繰り返しの処理 for / each
array = ('a'..'z').to_a
# 一般 each 寫法
array.each do |letter|
puts letter
end
# 單行 each 簡潔性寫法,要用大括號
array.each { |letter| puts letter }
- 兩種寫法根據可讀性來選擇
# マップの場合
map = {
a: 100,
b: 200
}
puts ""
# 改成兩個參數:key, Value
map.each do |letter, weight|
puts "#{letter} is #{weight}"
end
array = ('a'..'z').to_a
# for 寫法
for letter in array do
print letter
end
兩種方法在功能上是一樣的,但是原則上 Ruby 官方指南表示能用 each 就用 each,for 基本上不用除非你有明確的理由,但這也不是強制性的。
繰り返しの処理 times
如果今天要做的迴圈,跟 Collection 沒有任何關係,單存的指定迴圈次數則可以使用 times 來做迴圈。
# times 直接使用要執行的次數來表示,後面的 num 可以省略,會是從 0 開始。
5.times do |num|
print num
end
- |num| 可以省略
繰り返しの処理 while
# while
numArray = (0..5).to_a
while !numArray.empty? do
print numArray.pop
end
繰り返しの処理 upto / downto
前面提到 times 方法可以在從 0 開始去做迴圈,但是如果今天要從一個非零開始去做的話,可以使用 upto 或 downto 來處理。
# upto
-2.upto(2) do |num|
print "[#{num}]"
end
puts ""
# downto
2.downto(-2) do |num|
print "[#{num}]"
end
繰り返しの処理 step
如果不要一次 1 變動的話,可以用 step
# Step
0.step(10,2) do |num|
print num
end
繰り返しの処理 loop、break、next
如果要使用無窮迴圈的寫法,可以用 loop 搭配 break、next 做控制。
index = 0
loop do
index += 1
# next 相當於 Continue
next if index % 2 == 0
# 終止條件
break if index > 10
print "[#{index}]"
end
- 這裡的 if 條件是單行的寫法。
方法
使用 def 關鍵字宣告。 關於 return 在最後一行會自動返回,不需要明確加上 return 關鍵字。
def say_hello(name)
message = "hello ,#{name}"
puts message
message.size
end
puts say_hello("John")
印出方法比較:puts、print、p、pp
參考以下表格:
有返回值 | 沒有返回值(nil) | |
---|---|---|
有換行 | pp | |
無換行 | p | puts |
如果要看效果,用 IRB 在 console 上看會比較清楚。
配列
陣列的部分其實大概可以跟 kotlin 等語言差不多的建立法,有 Range 物件等等的寫法。
其他一些針對陣列的排序、加入/移除等操作也都有對應的方法可用。
# frozen_string_literal: true
# Array 的方法
array = ["a","b","c"]
print array
puts ""
puts "Element B => #{array.include?('b')}"
print array.reverse
puts ""
puts "Empty? => #{array.empty?}"
puts "Shuffle => #{array.shuffle}"
# Array 的建立:透過 Range 物件
# to.a 為轉 Array
puts (0..1).class # Range
print (0..10).to_a
puts ""
print ('a'..'z').to_a
puts ""
# Array 的元素加入/移除類
target = (0..5).to_a
# 加入
target << 6
target.push(7)
print target
puts ""
# 移除
target.pop
print target
puts ""
target.shift
print target
puts ""
Hash
也就是 Map。
前面提到的陣列可以用中括號宣告,而 Map 則是使用大括號搭配箭頭符號來寫。
而在 Key – Value 的分隔寫法上會有兩種寫法
- 箭頭符號 =>
- 傳統寫法
- 冒號 :
- 這是稱為符號(Symbol)分隔符號。
# 舊寫法
hash = { "key" => "value" }
# 新寫法:ruby 1.9 後支援
hash = { key: "value" }
這兩種方式在功能上是等價的,都可以用於宣告 Hash。 但使用冒號的方式更加簡潔和直觀,並且在某些情況下可以提高可讀性。但如果 Key 需要使用特殊符號或包含空格等情況,則必須使用箭頭符號的方式。 總結來說 => 符號和 : 符號可以互換使用,但冒號會更加簡潔和常見。
# 大括號宣告初始值,兩種寫法
my_map = {
'a' => 100,
'b' => 200,
'c' => 300
}
my_map2 = {
a: 10,
b: 20,
c: 30
}
print my_map
puts ""
print my_map2
puts ""
# 操作 Map
puts "key = a => #{my_map['a']}"
puts "key = a => #{my_map2[:a]}"
my_map['d'] = 400
print my_map
puts ""
print my_map2.keys
puts ""
puts my_map.has_key?('a')
- 這裡注意使用冒號來分隔的話,冒號必須緊鄰 key,中間不能有空格。
類別 Class
針對類別的定義,基本上也就是寫法的問題而已了:
class Cat
# 常數定義,不可再改變
TAG = 'Animal'
# 類別變數宣告
@@cat_index = 0
# 快速 Getter/Setter 宣告
# 可以不用額外自訂 Getter/Setter
attr_accessor :name
# 建構子,new 方法會直接調用
def initialize(name, weight = 10)
@name = name
@weight = weight
@@cat_index += 1
end
# Getter 方法 (weight)
def weight
@weight
end
# Setter 方法 (weight)
def weight=(value)
@weight = value
end
# 一般方法
def shout(name = "??")
puts "hello #{name}, my name is #{@name}"
end
# 相當於靜態方法
def self.index
@@cat_index
end
end
my_cat = Cat.new("King")
my_cat.shout
puts my_cat.weight
my_cat.name = "John"
my_cat.shout("Jack")
your_cat = Cat.new("Queen")
puts Cat.index
継承
繼承也跟其他語言差不多,注意一下語法即可:
class User
def initialize(name)
@name = name
end
def say_hello()
puts "Hello, my name is #{@name}."
end
end
class AdminUser < User
# 可以直接 override
def say_hello
super
puts "(*AdminUser*)"
end
end
admin = AdminUser.new("God")
admin.say_hello
這裡補充一下,Integer 的類別繼承關係:
- Integer
- Numberic
- Object
- BasicObject:最上層的類別
另外查詢該類別的父類別可以直接使用 superclass 方法。
puts AdminUser.superclass
Module
這裡簡單介紹 Module 的概念:
特性 | Class | Module |
---|---|---|
定義 | 用於創建物件的藍圖 | 用於組織和封裝代碼 |
繼承 | 支持繼承,可以繼承其他 Class 的屬性和方法 | 不能被繼承,但可以被包含到 Class 中 |
實例化 | 可以實例化為具體的物件,擁有自己的狀態和行為 | 不能被實例化,只能被包含到 Class 中 |
命名空間 | 可用於創建命名空間,將相關的方法和常量組織在一起 | 可用於命名空間管理,可以被包含到其他 Module 或 Class 中 |
module Greeting
def say_hello
puts "Hello!"
end
end
class Person
# 直接引用進 Class 中使用,減少 Code 重複。
include Greeting
end
person = Person.new
person.say_hello
例外と例外処理
這裡稍微整理了 Ruby 與 Kotlin/Java 在一些語法上的差異:
Ruby | Kotlin | |
---|---|---|
拋出異常 | 使用 raise 關鍵字 | 使用 throw 關鍵字 |
捕獲異常 | 使用 begin – rescue – end 區塊 | 使用 try – catch – finally 區塊 |
異常類別 | 繼承自 Exception 或其他父類別 | 繼承自 Throwable 或其他父類別 |
預設捕獲 | 所有異常都可以被捕獲,除非明確重新拋出 | 只有被宣告的異常類別或其父類別的異常才能被捕獲 |
多重捕獲 | 可以使用多個 rescue 區塊來捕獲不同類型的異常 | 可以使用多個 catch 區塊來捕獲不同類型的異常 |
自定義異常 | 可以創建自定義的異常類別 | 可以創建自定義的異常類別 |
def check_age(age)
raise ArgumentError, "年齡不能為負數" if age < 0
raise TypeError, "年齡必須是整數" unless age.is_a?(Integer)
puts "年齡正確:#{age}"
end
begin
check_age(-5)
rescue ArgumentError => e
puts "發生 ArgumentError:#{e.message}"
rescue TypeError => e
puts "發生 TypeError:#{e.message}"
ensure
puts "END=="
end
- ensure 是 finally 的用法。
Coding Rule
官方其實沒有一個 Coding 規則,但是用於檢核 Coding Style 的工具 RuboCop 的作者,有提供一個名為『The Ruby Style Guide』的指引提供作為參考。
Link:
這一個指引寫的規則就算不遵守也沒差,但是這能統一提高程式可讀性,建議還是可以參考。
メソードの公開範囲
private、protected 和 public 關鍵字的用法,基本上與其他語言一樣。
寫法上是使用類似區塊的方式,同一區的方法都會是 protected 等等,而預設的方法都會是 public。
class MyClass
def public_method
puts "This is a public method"
end
protected
def protected_method
puts "This is a protected method"
end
private
def private_method
puts "This is a private method"
end
end
obj = MyClass.new
obj.public_method # 可以被訪問
obj.protected_method # 無法直接訪問,會出現錯誤
obj.private_method # 無法直接訪問,會出現錯誤
結語
至此簡易的 Ruby 學習已經完成了。 RubyOnRails 將於新的筆記中再行記述。