iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0
Modern Web

初階 Rails 工程師的養成系列 第 2

Day2. Ruby 的基本介紹 - 讓大家認識並愛上Ruby

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的發布影片

  • DHH 講述 Rails7
  • Drifting Ruby 講述兩篇與Rails7相關文章:連結1, 連結2

程式語言特色

我們在Rails以前,會先花時間介紹Ruby程式語言。Day1-Day17 會著重介紹Ruby程式語言,在Day18以後才會從畫面開始切入Rails應用程式。

在正式切入主題以前,首先想要跟讀者們介紹Ruby程式語言的特色。

優雅 - Elegant

Ruby發明的宗旨就是希望程式設計師能夠用人類的語言寫程式。比起一般的程式語言,Ruby的寫法更加風雅,舉凡下面的例子來的寫法都很風雅,也不失死板,漢漢老師自己也是看上這一點才決定入坑Rails

1.day.from_now          # 從現在開始往前推1天
Datetime.now.tomorrow   # 明天
Order.first             # 第一張訂單
Product.last            # 最後一個商品

2.even?   # 2是偶數嗎?
nil.nil?  # nil是nil嗎?

強型別 - Strongly Typed

由於大家對於Ruby的刻板印象就是寫法很奔放,就會誤會Ruby微弱型別的語言,但其實Ruby 是一個強型別的語言。我們拿Javascript的某例與Ruby 做對比。

Javascript的世界中,不同型別的2個東西可以互加!而由於Javascript 弱型別的特性,使得開發過程中常會延伸出難以找錯的問題,因此後來衍伸了Typescript嚴格定義了型別。不過TypeScript最後也會被編譯成Javascript,仍不會改變JS為弱型別語言的事實。

null + 1       // 1
undefined + 1  // NaN

Ruby的世界中,可不允許兩個不同型別的物件互加。舉例來說,當我打 1 + nilRails 會 告訴我不同型別的不能加起來。

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錯誤狀態等等。

單一繼承 - Single inheritance

多重繼承會使物件複雜度上升,然而這些狀況在Ruby程式語言不會遇到。

Ruby為單一繼承的語言,若要實現多重繼承,可以使用mixin的方式來達成多模組的繼承,而我們會在Day14講解關於mixin的繼承方式。

任何東西都是物件 - Everything is an object

Ruby 的世界裡面,除了block以外,所有的東西都是物件。 包括nil也是在NilClass 產生的物件。

Block

BlockRuby語言的一大特色。因為Block的緣故,使得Ruby容易發展出DSL語言,例如Railsroutes, RSpec,以及Sinatra,都是因為Ruby特有的block 特性衍生出其DSL語言。由於這系列的文章的對象是針對初階工程師,因此不會講關於DSL的細節,讀者們若有興趣可以參閱這篇Toptal發表的文章

我們會在 Day8 正式介紹blockblock是漢漢老師非常喜歡的主題,也是許多工程師拿來做面試或討論的議題。

常見用法

在詳細的介紹Ruby 程式語言以前,先介紹Ruby 的一些概念和基本語法

is_a? & kind_of? & instance_of?

我們要介紹的第一個用法是用來判斷類別的方法。首先我們先講is_a?, kind_of?

is_a?kind_of? 的用法一樣,都是判斷是哪一種類別。舉例來說,若A類別繼承陣列,則 A.is_a?(Array) 會回傳true。被includemixin 也會被 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? 則不會判斷上層被繼承或mixintrue,舉例來說,如果A繼承了Array,擁有了陣列的特性,但instance_of? 不會判斷A為陣列

A.is_a? A            # true
A.is_a? Array        # true
A.instance_of? Array # false

Logic Operator

以下用實例介紹基本的 || 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

nil? and present?

實務上,漢漢老師比較少使用 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? & presence & presence_in

接著我們討論上面三者用法的差別

# 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扮演的角色

& 在Ruby的用途很多,以下列出三種含有&符號的使用用法

  • 處理Proc Block ➡️ Day8介紹
  • safe navigation operator
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

object_idRuby語言為重要概念,雖然沒那麼實用,但object_id可以用來解釋Ruby存取記憶體的位置。

a = "a"#=> "a"b = a#=> "a"b.object_id#=> 70126864284020a.object_id#=> 70126864284020a.object_id == b.object_id#=> true

利用object_id,我們可以發現symbolRuby程式語言中,佔著同個記憶體位置,而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佔去的記憶體會比較小,效能會比較高。

由上述介紹的freezesymbol,我們可以發現freeze 的原理跟 symbol 很像!

結論

雖然在後面的Rails版本似乎已經不用寫freeze方法了,但如果要面試Rails工程師的讀者們,記得要看freeze的概念。

Day2介紹了Ruby的概念以及基本用法。在Day3-7的四天裡面,會介紹其他關於Ruby的基本語法

參考資料


上一篇
Day1. 參賽
下一篇
Day3. Ruby的數字、字串,以及 ===
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言