Ruby 是一種物件導向、指令式、函數式、動態的通用程式語言。在20世紀90年代中期由日本電腦科學家松本行弘(Matz)設計並開發。 本筆記參考以下課程:[はじめてのRuby on Rails入門-RubyとRailsを基礎から学びウェブアプリケーションをネットに公開しよう](https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/) 初期先以 Syntax 為主要學習目標。 # Installation * 安裝連結:[Download Archives](https://rubyinstaller.org/downloads/archives/) 可以看到有一個 Devkit 的東西在,這個為了讓 Ruby 可以去使用以 C 寫的第三方 Library 用的,如果您要引入的 library 是使用純 Ruby 寫的,那麼就不需要安裝 devkit。 Ruby 跟 Java 一樣都有一個 VM,稱為 RVM,儘管實現方式有所不同。 # Document * 官方文件:[Ruby - 程式設計師的摯友](https://www.ruby-lang.org/zh_tw/) * 課程中有使用到的 Sample Code:[「よくわかるRuby on Rails入門」サポートサイト](https://blog.proglus.jp/support-the-ultimate-ruby-on-rails-bootcamp/) # Ruby 入門 要執行 Ruby 程式有兩種方法: 1. 使用 irb (Interactive Ruby),類似互動式的方法 * 直接在 CMD 輸入 irb 就可以進入環境。 2. 執行以檔案形式保存的程式 在 Ruby 中, nil 是一個物件,表示『沒有值』或『未定義的值』,可以用來代表一個變數未被賦值或一個方法沒有返回值。 在 IRB 中,有以下快捷鍵可以使用: | 快捷鍵 | 作用 | 說明 | | -------- | -------- | -------- | | Ctrl + L | 畫面清除 || | Ctrl + D | 離開 IRB | 輸入 exit 也有一樣的效果 | ## Hello world 直接建立一個檔案:sample.rb ```ruby= # puts 是 Ruby 中的一個方法,用於將字符串或其他數據輸出到控制台。 # 它的作用類似於 print 方法,但是在輸出完數據之後會自動換行。 puts "Hello world." =begin 也可以像 python 直接輸入運算式 =end puts 10+10 ``` 然後執行 ruby sample.rb 就可以看到結果。 * 可以注意到單行註解是用井字號來表示的,跟 yaml 一樣。 * 多行註解則是要使用等號 begin/end 來表示,但大部分的情況下還是使用井號來表示比較好看一點。 # 変数/定数 變數宣告不用加上任何單字,只要使用等於即可: * 変数名 =式 ```ruby= # 前面不需要寫 var 等關鍵字做宣告 message = "hello ruby." puts message ``` 而常數的部分,基本上是將變數名稱改大寫開頭就行,但是在 ruby 中,大寫開頭的常數宣告一樣是可以去改變的,只是會報警告而已不會出錯,這點跟 python 一樣。 例如這樣的寫法: ```ruby= VARIABLE = 1 VARIABLE = 2 puts VARIABLE ``` 執行後會看到報警告: ```terminal= "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 ``` 另外關鍵字的部分可以參考官方文件:[字句構造](https://docs.ruby-lang.org/ja/latest/doc/spec=2flexical.html) ## Literal リテラル 所謂 literal 指的是可以直接寫在 Code 中表示的文字,舉例子比較好理解,有以下種類: 1. String - "message" 2. 數字 - 123 3. 陣列 - ['a','b','c'] 4. 關聯陣列 - 連想配列:也就是 Map,在 Ruby 中也被稱作 Hash - { "name" => "John", "age" => 30 } # 數值型別 在 Ruby 中,數字相關的類別有以下幾種: 1. Integer:整數類別,用於表示整數值。 2. Float:浮點數類別,用於表示帶小數的數值。 3. BigDecimal:高精度十進制類別,用於表示需要高精度計算的數值。 4. Rational:有理數類別,用於表示分數形式的數值。 5. Complex:複數類別,用於表示複數形式的數值。 由於 Ruby 在宣告變數時就能自動判定型別,關於整數與小數的部分只有插在有沒有小數點而已。 想要查看物件的類別與其擁有的方法,可以使用 class 與 methods: ```ruby= # 列出型別 puts 1.class # 列出 Interger 的方法 puts 1.methods.join "," ``` # String 字串宣告分成兩種,雙括號與單括號,這與 Groovy 的 GString/String 是一樣的。 如果要將變數、換行特殊符號等置入字串中,需要使用雙括號才可以: ```ruby= name = "John" puts "Hello, #{name}!" ``` 而且 String 在 Ruby 中是 Mutable 跟 Java 不一樣,是可以直接修改原本字串內容而不是新建字串: ```ruby= str = "Hello, world!" str[0] = "J" puts str # => "Jello, world!" ``` 而針對多行字串的寫法,Ruby 使用像是 Linux 中的here document 寫法: ```ruby= text = <<~EOF This is a multi-line text. It can contain multiple lines. EOF puts text ``` ## 字串與數字之間的轉換 Ruby 對於型別間無法像 Javascript 一樣自動轉換,需要使用轉換方法才可以: ```ruby= # 需要使用 to_? 系列方法才能統一型別 puts 'Number is '+ 1.to_s puts 1 + '2'.to_i ``` # Exclamation mark methhod 可以參考以下文章:[[ Ruby ] Methods 後面有!驚嘆號 (exclamation marks)](https://medium.com/@chaowu.dev/ruby-methods-%E5%BE%8C%E9%9D%A2%E6%9C%89-%E9%A9%9A%E5%98%86%E8%99%9F-exclamation-marks-c0468875a4bd) Ruby 中可以發現有些方法成對的出現,分為有無驚嘆號在方法後面。 有驚嘆號的方法代表的是『危險版本』方法,提示使用者該方法作用會改變物件本身,也就是 mutable 的方法操作。 反之沒有驚嘆號的表示結果會回傳的是新的物件,也就是 immutable 的方法。 驚嘆號本身並不會改變方法的行為。它只是一個命名慣例而已,作為方法名稱的一部分。 ```ruby= # 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 有什麼作用 !?](https://ithelp.ithome.com.tw/articles/10214518) ## 含有問號在方法名後面 在 Ruby 中,方法名後面加上問號(?)通常表示該方法返回的是一個 Boolean(true 或 false)。這種命名慣例用於表達方法的返回值是一個問題或條件的答案。 例如,如果有一個方法名為 empty? ,它可能用於檢查物件是否為空,返回值為 true 或 false。 # 演算子 參考官方文件:[演算子式](https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html) 大部分應該都與其他語言沒什麼差異。 運算子可以複寫,但也不是全部都可以,詳細看文件。 ## i++、i-- 不存在 Ruby 沒這種寫法,請改用 i += 1 之類的寫法。 # 流程控制 先說明 Ruby 中,那些物件是屬於 True、那些又是 False。 基本上原則是 false 與 nil 屬於 false,其他都是 true 屬於 True 的物件有以下: 1. true 2. 所有數字,包含 -1、0 等 3. 所有的 String,包含像空字串等皆是。 ## and/or/not 與 &&/||/! 的差異 Ruby 的運算子有多了 and/or/not 可以使用,這三個比起一般的符號相比優先度會**比較低**。兩類最好不要混用以免悲劇,請用括號。 詳細用法文件有寫。 ## 条件分岐 if ```ruby= 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 如果很難懂就不要應用。 ```ruby= num = (rand * 10).to_i unless num > 5 puts "num<=5" else puts "num>5" end ``` ## 条件分岐 case ```ruby= num = (rand * 10).to_i isZero = case num when 0 true else false end puts "is zero ? => #{isZero}" ``` ## 繰り返しの処理 for / each ![](https://hackmd.io/_uploads/H1UvX-2dh.png) ```ruby= array = ('a'..'z').to_a # 一般 each 寫法 array.each do |letter| puts letter end # 單行 each 簡潔性寫法,要用大括號 array.each { |letter| puts letter } ``` * 兩種寫法根據可讀性來選擇 ```ruby= # マップの場合 map = { a: 100, b: 200 } puts "" # 改成兩個參數:key, Value map.each do |letter, weight| puts "#{letter} is #{weight}" end ``` ![](https://hackmd.io/_uploads/BJ56Vb3un.png) ```ruby= array = ('a'..'z').to_a # for 寫法 for letter in array do print letter end ``` 兩種方法在功能上是一樣的,但是原則上 Ruby 官方指南表示能用 each 就用 each,for 基本上不用除非你有明確的理由,但這也不是強制性的。 ## 繰り返しの処理 times 如果今天要做的迴圈,跟 Collection 沒有任何關係,單存的指定迴圈次數則可以使用 times 來做迴圈。 ```ruby= # times 直接使用要執行的次數來表示,後面的 num 可以省略,會是從 0 開始。 5.times do |num| print num end ``` * |num| 可以省略 ## 繰り返しの処理 while ![](https://hackmd.io/_uploads/S1vJcWnOh.png) ```ruby= # while numArray = (0..5).to_a while !numArray.empty? do print numArray.pop end ``` ## 繰り返しの処理 upto / downto 前面提到 times 方法可以在從 0 開始去做迴圈,但是如果今天要從一個非零開始去做的話,可以使用 upto 或 downto 來處理。 ![](https://hackmd.io/_uploads/S14jj-nOn.png) ```ruby= # upto -2.upto(2) do |num| print "[#{num}]" end puts "" # downto 2.downto(-2) do |num| print "[#{num}]" end ``` ## 繰り返しの処理 step 如果不要一次 1 變動的話,可以用 step ![](https://hackmd.io/_uploads/Hyh8pb2On.png) ```ruby= # Step 0.step(10,2) do |num| print num end ``` ## 繰り返しの処理 loop、break、next 如果要使用無窮迴圈的寫法,可以用 loop 搭配 break、next 做控制。 ![](https://hackmd.io/_uploads/Bkp_CZ2dn.png) ```ruby= index = 0 loop do index += 1 # next 相當於 Continue next if index % 2 == 0 # 終止條件 break if index > 10 print "[#{index}]" end ``` * 這裡的 if 條件是單行的寫法。 # 方法 使用 def 關鍵字宣告。 關於 return 在最後一行會自動返回,不需要明確加上 return 關鍵字。 ```ruby= def say_hello(name) message = "hello ,#{name}" puts message message.size end puts say_hello("John") ``` # 印出方法比較:puts、print、p、pp 參考以下表格: | | 有返回值 | 沒有返回值(nil) | | ------ | -------- | --------------- | | **有換行** | pp | print | | **無換行** | p | puts | 如果要看效果,用 IRB 在 console 上看會比較清楚。 # 配列 陣列的部分其實大概可以跟 kotlin 等語言差不多的建立法,有 Range 物件等等的寫法。 其他一些針對陣列的排序、加入/移除等操作也都有對應的方法可用。 ```ruby= # 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 的分隔寫法上會有兩種寫法 1. 箭頭符號 => - 傳統寫法 2. 冒號 : - 這是稱為符號(Symbol)分隔符號。 ```ruby= # 舊寫法 hash = { "key" => "value" } # 新寫法:ruby 1.9 後支援 hash = { key: "value" } ``` 這兩種方式在功能上是等價的,都可以用於宣告 Hash。 但使用冒號的方式更加簡潔和直觀,並且在某些情況下可以提高可讀性。但如果 Key 需要使用特殊符號或包含空格等情況,則必須使用箭頭符號的方式。 總結來說 => 符號和 : 符號可以互換使用,但冒號會更加簡潔和常見。 ```ruby= # 大括號宣告初始值,兩種寫法 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 針對類別的定義,基本上也就是寫法的問題而已了: ```ruby= 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 ``` ## 継承 繼承也跟其他語言差不多,注意一下語法即可: ```ruby= 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 方法。 ```ruby= puts AdminUser.superclass ``` # Module 這裡簡單介紹 Module 的概念: | 特性 | Class | Module | |------|--------------------------|-------------------------------------| | 定義 | 用於創建物件的藍圖 | 用於組織和封裝代碼 | | 繼承 | 支持繼承,可以繼承其他 Class 的屬性和方法 | 不能被繼承,但可以被包含到 Class 中 | | 實例化 | 可以實例化為具體的物件,擁有自己的狀態和行為 | 不能被實例化,只能被包含到 Class 中 | | 命名空間 | 可用於創建命名空間,將相關的方法和常量組織在一起 | 可用於命名空間管理,可以被包含到其他 Module 或 Class 中 | ```ruby= 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 區塊來捕獲不同類型的異常 | | 自定義異常 | 可以創建自定義的異常類別 | 可以創建自定義的異常類別 | ```rubu= 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: * [The Ruby Style Guide](https://github.com/rubocop/ruby-style-guide) * [The Ruby Style Guide (JP)](https://github.com/fortissimo1997/ruby-style-guide/blob/japanese/README.ja.md) 這一個指引寫的規則就算不遵守也沒差,但是這能統一提高程式可讀性,建議還是可以參考。 # メソードの公開範囲 private、protected 和 public 關鍵字的用法,基本上與其他語言一樣。 寫法上是使用類似區塊的方式,同一區的方法都會是 protected 等等,而預設的方法都會是 public。 ```ruby= 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 將於新的筆記中再行記述。