Ruby on Rails
為用Ruby
程式語言寫的開源網頁框架,Rails
的發明者DHH
挑選了Ruby
做為Rails
的程式語言。Rails
在2004年發布以後的短短的時間內就迅速獲得很多開發人員歡迎,這都歸功於MVC
架構,以及Rails
慣例優於設定的特性(使用Rails
應用程式的開發者不用了解太多Knowhow
,只需遵循著慣例即可開發網站)。直到其他MVC
網頁框架出現以前,Ruby on Rails
以第一個擁有MVC
框架紅極一時。
由於Ruby
為直譯語言的緣故,有速度比較慢的詬病,又加上不像Python
後期發展成多領域的語言。Ruby
發展至今僅作為網頁、外掛、韌體用途,近幾年逐漸式微。不過我並不這麼覺得Ruby on Rails
會那麼快的凋零,如人飲水;冷暖自知,只有寫過Ruby
的人才知道Ruby
的好,加上Ruby
一直有廣大的社群,加上Rails
至今仍不斷地進步。
在這個月的IT鐵人比賽進行的同時,DHH
同時發表了Rails7
,以及全新Javascript
載入方式esbuild
。明年的IT鐵人賽
也想要用全新的Rails7
介紹我的新專案。
以下為9月與Rails7
的發布影片
我們在Rails以前,會先花時間介紹Ruby
程式語言。Day1-Day17
會著重介紹Ruby
程式語言,在Day18
以後才會從畫面開始切入Rails
應用程式。
在正式切入主題以前,首先想要跟讀者們介紹Ruby
程式語言的特色。
Ruby
發明的宗旨就是希望程式設計師能夠用人類的語言寫程式。比起一般的程式語言,Ruby
的寫法更加風雅,舉凡下面的例子來的寫法都很風雅,也不失死板,漢漢老師自己也是看上這一點才決定入坑Rails
。
1.day.from_now # 從現在開始往前推1天
Datetime.now.tomorrow # 明天
Order.first # 第一張訂單
Product.last # 最後一個商品
2.even? # 2是偶數嗎?
nil.nil? # nil是nil嗎?
由於大家對於Ruby
的刻板印象就是寫法很奔放,就會誤會Ruby
微弱型別的語言,但其實Ruby
是一個強型別的語言。我們拿Javascript
的某例與Ruby
做對比。
在Javascript
的世界中,不同型別的2個東西可以互加!而由於Javascript
弱型別的特性,使得開發過程中常會延伸出難以找錯的問題,因此後來衍伸了Typescript
嚴格定義了型別。不過TypeScript
最後也會被編譯成Javascript
,仍不會改變JS
為弱型別語言的事實。
null + 1 // 1
undefined + 1 // NaN
在Ruby
的世界中,可不允許兩個不同型別的物件互加。舉例來說,當我打 1 + nil
,Rails
會 告訴我不同型別的不能加起來。
1 + nil
Traceback (most recent call last):
TypeError (nil can't be coerced into Integer)
不過,在Ruby
的世界中,下列的寫法是合法的。若太常使用型別的強制轉換,一樣會造成日後偵錯的困難。
漢漢老師就很常因此深受其困擾。
price = nil
1 + price.to_i #=> 1
題外話,寫程式久了發現,不是每一個會引發錯誤的地方都要寫Rescue/Exception
,有時候該讓它壞的地方還是要讓它壞掉,才能夠告訴使用者不能這樣操作。
當網站上了正式站以後,專案還是會有一些方式,能夠將原本會跑壞掉的程式碼救回來,例如說跳轉到500
錯誤狀態等等。
多重繼承會使物件複雜度上升,然而這些狀況在Ruby
程式語言不會遇到。
Ruby
為單一繼承的語言,若要實現多重繼承,可以使用mixin
的方式來達成多模組的繼承,而我們會在Day14講解關於mixin
的繼承方式。
Ruby
的世界裡面,除了block
以外,所有的東西都是物件。 包括nil
也是在NilClass
產生的物件。
Block
為Ruby
語言的一大特色。因為Block
的緣故,使得Ruby
容易發展出DSL
語言,例如Rails
的routes
, RSpec
,以及Sinatra
,都是因為Ruby
特有的block
特性衍生出其DSL
語言。由於這系列的文章的對象是針對初階工程師,因此不會講關於DSL
的細節,讀者們若有興趣可以參閱這篇Toptal發表的文章
我們會在 Day8 正式介紹block
。block
是漢漢老師非常喜歡的主題,也是許多工程師拿來做面試或討論的議題。
在詳細的介紹Ruby
程式語言以前,先介紹Ruby
的一些概念和基本語法
我們要介紹的第一個用法是用來判斷類別的方法。首先我們先講is_a?
, kind_of?
。
is_a?
和 kind_of?
的用法一樣,都是判斷是哪一種類別。舉例來說,若A
類別繼承陣列,則 A.is_a?(Array)
會回傳true
。被include
的mixin
也會被 is_a?
判斷為true
我們舉一個實際的例子來作為使用is_a?
的 demo,以下為簡單判斷型別,並依據型別來判斷使用何種結果的方法。
def analyse_data(data)
return unless data.is_a? Array || data.is_a? String
data.is_a?(Array) ? "用陣列的處理方法" : "用字串的處理方法"
end
analyse_data([1, 2, 3]) #=> "用陣列的處理方法"
另外,instance_of?
則不會判斷上層被繼承或mixin
為true
,舉例來說,如果A
繼承了Array
,擁有了陣列的特性,但instance_of?
不會判斷A
為陣列
A.is_a? A # true
A.is_a? Array # true
A.instance_of? Array # false
以下用實例介紹基本的 ||
and &&
的用法。
false || 1 #=> 1
false && 1 #=> false
nil || 1 #=> 1
nil && 1 #=> nil
true || 1 #=> true
true && 1 #=> 1
需注意,若回傳值的話,我們需要多留意使用&&
或||
回傳的結果為何。除此之外,邏輯運算子很好用,有些情況甚至可以代替if else
。接著我們用第二個例子來介紹 ||
and &&
的用法
def foo
'foo'
end
# nil 或 false
nil && foo #=> nil
nil || foo #=> "foo"
false && foo #=> false
false || foo #=> "foo"
# true 或存在 instance
true && foo #=> "foo"
true || foo #=> true
'abc' && foo #=> "foo"
'abc' || foo #=> "abc"
看完以上例子之後,我們來說明 ||
and &&
的使用情境
⭐️||
的使用情境:
我的房間的衣架上有掛毛巾、門口也又掛毛巾、櫃子旁也有掛毛巾,我的習慣是會優先拿衣架上的毛巾,沒有的話會去門口拿,門口再沒有的話我才會拿櫃子旁的毛巾。上述的情境用程式碼可以表示成
tower = from_hanger || from_door || from_cabinet
實際上會用的情境可以是在多元登入,當我們用帳號搜尋不到帳號,改用電子信箱搜尋,若用電子信箱搜尋不到則改成使用電話號碼之類
⭐️ &&
的使用情境:
初會學分拿到了才能修中會,中會修完才能修高會。所以如果elementary_accounting
為沒有資料( nil
),系統就被被擋修。
elementary_accounting && medium_accounting && advanced_accounting
實務上,漢漢老師比較少使用 blank?
, empty?
,大部分的問題用nil?
, present?
就可以應付,這邊先不做介紹了。我們舉下列來說明,並且舉例以前,先假設parse_data
為某一種解析資料的方法,這裡強調的是nil?
, present?
的使用方式,所以我們先不理會parse_data
的功能是在做什麼。
以下三種的寫法相等
# 反向的寫法語意較不好
unless instance.nil?
instance.parse_data
end
# 清楚明瞭
if instance.present?
instance.parse_data
end
# 清楚明瞭
instance.present? && instance.parse_data
接著我們討論上面三者用法的差別
# present? 只會回傳 true/false
'a'.present? #=> true
nil.present? #=> false
[].present? #=> false
''.present? #=> false
# presence 會將 false, [], nil, '' 轉為 nil,因此可以拿來回傳值。
false.presence #=> nil
[].presence #=> nil
nil.presence #=> nil
''.presence #=> nil
⭐️presence
於畫面上的應用:
➡️ 假設退貨單沒有填寫退貨原因,則不顯示。若用present?
則會顯示false
= tag.div return_order.failed_reason.presence
➡️ 下列例子是使用 presence
調整css,若親自取貨按鈕不在,則將列印明細按鈕寫入margin-left: auto;
。
我們會在Day19介紹margin: auto
的用法。使用presence
搭配&&
判斷的在於若顯示!sub_order.can_pickup?
為true
,則class
上面也會顯示ml-auto
,反之如果!sub_order.can_pickup?
為false
,則會顯示nil
,並且['btn btn-outline-primary mr-2', nil].compact
為['btn btn-outline-primary mr-2']
,再透過join(' ')
會變為'btn btn-outline-primary mr-2'
- if sub_order.can_pickup?
= link_to '親自取貨', '#',
class: 'btn ml-auto btn-warning mr-2',
data: { confirm: "確定嗎?" }
= link_to '列印明細', '#',
class: ['btn btn-outline-primary mr-2', (!sub_order.can_pickup?.presence && 'ml-auto')].join(' ')
⭐️ presence_in
的用法為篩選陣列的值
'q'.presence_in %w(q w e) #=> "q"
'a'.presence_in %w(q w e) #=> nil
搭配||
可以設定預設值
'a'.presence_in %w(q w e) || 'b' #=> b
&
在Ruby的用途很多,以下列出三種含有&符號的使用用法
Proc
與 Block
➡️ Day8介紹nil.name# Traceback (most recent call last):# NoMethodError (undefined method `name' for nil:NilClass)nil&.name#=> nil
我們也可以使用 try
用法來寫,不過try
表示法比較冗就是了。
nil.try(:name)#=> nilnil&.name#=> nil
有趣的是,javascript
後來衍伸了很像的寫法,表示法為?.
null.name// Uncaught TypeError: Cannot read property 'name' of nullnull?.name// undefined
⭐️ 與 &&
不一樣,a && b
的意思為,若a
為不為nil, false
,則回傳b
值
[1,3] & [1,2]#=> [1][1,3] && [1,2]#=> [1, 2]
object_id
在Ruby
語言為重要概念,雖然沒那麼實用,但object_id
可以用來解釋Ruby
存取記憶體的位置。
a = "a"#=> "a"b = a#=> "a"b.object_id#=> 70126864284020a.object_id#=> 70126864284020a.object_id == b.object_id#=> true
利用object_id
,我們可以發現symbol
在Ruby
程式語言中,佔著同個記憶體位置,而String
不是。
# Symbol 在記憶體為同個位置:hello.object_id == :hello.object_id# String 在記憶體不同位置"hello".object_id == "hello".object_idfalse
在提到 symbol
的同時,我們也講講freeze
的原理
a = "a".freeze(1..3).map { "a".object_id } #=> [70240285526560, 70240285526420, 70240285526400](1..3).map { a.object_id } #=> [70240172465500, 70240172465500, 70240172465500]
先看沒有被結凍的字串對應的object_id
,我們發現記憶體位置不一樣。使用freeze
過後,所指向的object_id
相同,因此使用freeze
佔去的記憶體會比較小,效能會比較高。
由上述介紹的freeze
和 symbol
,我們可以發現freeze
的原理跟 symbol
很像!
雖然在後面的Rails
版本似乎已經不用寫freeze
方法了,但如果要面試Rails
工程師的讀者們,記得要看freeze
的概念。
Day2介紹了Ruby
的概念以及基本用法。在Day3-7的四天裡面,會介紹其他關於Ruby
的基本語法