iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0
Modern Web

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

Day3. Ruby的數字、字串,以及 ===

Day3 要來介紹Ruby字串、數字,還有Ruby 的 ===

Number

數字比起其他類別,還要來的單純。不過身為Rails的工程師,必須知道Ruby提供的語法

num = 10

num.even?        # 是否為偶數
num.odd?         # 是否為奇數
num.positive?    # 是否為正數
num.negative?    # 是否為負數
num.zero?        # 是否為零
num.integer?     # 是否為正數

⭐️ Ruby的目的為寫得出讓人愉悅的語法,所以除了上述的口語化語法以外,數字本身也有小巧思。

1000  #=> 1000
1_000 #=> 1000
10_00 #=> 1000

Ruby語言中,我們可以把_當成是逗點, 若數字的位數太多,則可以加註底下下去。不過Ruby底線的位置不會限制,因此寫在哪裡都可以。

⭐️ Ruby 使用隨機數字的語法為rand,而我們也可以透過+/-某個數字來做平移的效果。

rand(10)        #=> 數字1-10
rand(10) + 10   #=> 數字10-20

⭐️ 處理除法方面,Ruby 必須小心整數相除的狀況。在Ruby程式語言中,所有皆為物件,而Integer, Float為不同物件,表現的行為也會不一樣要注意。

1/2             #=> 0
1.0/2.0         #=> 0.5
1.to_f/2.to_f   #=> 0.5
1.class         #=> Integer
1.0.class       #=> Float

⭐️ 如果想要了解線性代數,或者公職/研究所有用到線性代數的話,也可以使用下列的matrix 玩玩看。

 require 'matrix'

⭐️ 如果要取概數,可以使用round

2.3465.round(2)  #=> 2.35

⭐️ 可以使用format,但型別就為字串。小心不要用字串來做數字的運算

format("%.2f", 2.3465)       #=> "2.35"
format("%.2f", 2.3465).class #=> String

⭐️ 和Rails數字有關的 helper 也有一些值得介紹的,像是如果要顯示金額的話,可以使用(可參考這篇):

helper.number_to_currency(42.50)                    #=> "NT$42.50"
helper.number_to_currency(nil)                      #=> nil
helper.number_to_currency(42.50, unit: "Richard$")  #=> "Richard$42.50"

String

Ruby的字串有很多實用的寫法,以下跟大家介紹一些目前自己常用的!

"admin/sub_order".camelize   #=> "Admin::SubOrder"
"Admin::SubOrder".underscore #=> "admin/sub_order"
"admin/sub_order".dasherize  #=> "admin/sub-order"
"book".pluralize             #=> "books"
"person".pluralize           #=> "people"
"fish".pluralize             #=> "fish"
"books".singularize          #=> "book"
"people".singularize         #=> "person"
"book_and_person".pluralize    #=> "book_and_people"
"book and person".pluralize    #=> "book and people"
"BookAndPerson".pluralize      #=> "BookAndPeople"
"books_and_people".singularize #=> "books_and_person"
"books and people".singularize #=> "books and person"
"BooksAndPeople".singularize   #=> "BooksAndPerson"
"admin/sub_orders".humanize #=> "Admin/sub orders"
"admin/sub_orders".titleize #=> "Admin/Sub Orders"
"admin/sub_order".classify  #=> "Admin::SubOrder"
"admin/sub_orders".classify #=> "Admin::SubOrder"
"admin/sub_order".tableize  #=> "admin/sub_orders"
"Admin::SubOrder".tableize  #=> "admin/sub_orders"
"Module".constantize        #=> Module
"A::b::C".deconstantize     #=> "A::b"
"A::b::C".demodulize        #=> "C"

上述的這些用法如果搭配Ruby on Rails的慣例,可以寫出非常漂亮的語法。我們在對controllers, models 命名的時候基本上都會按慣例,而只要命名符合規則,就可以用動態的方式寫。以下是自己專案中的程式片段,依照慣例後就可以使用動態寫法,因此可以精簡化程式碼。

下列為自己寫過的某一支讀取controller的程式碼。我們可以看到,以下使用了camelize, singularize, constantize, classify 來對被includedcontroller做操作,達到程式碼精簡化的效果。

# controller.camelize.singularize.constantize
define_method("pre_query_#{controller}") do
  # descent_exposure
  return send(controller_name.to_sym) if respond_to?(controller_name.to_sym)
  # instance_variable
  instance_variable_set("@#{controller}", controller.camelize.singularize.constantize)
end

# Admin::#{controller.classify}Serializer
define_method(:default_viewable_lists) do |filtered_sub_orders, keys|
  data_lists(viewable(filtered_sub_orders), "Admin::#{controller.classify}Serializer".constantize, keys)
end

我們不用懂上面的程式碼在做什麼,我們把焦點放在兩處:

  • controller.camelize.singularize.constantize
  • "Admin::#{controller.classify}Serializer".constantize

這裡使用動態的寫法,讓被included的檔案都可以被讀取到。至於#included的話,我們會在Day14講到

in? && include?

in?include? 的用法差別在以下的例子!兩者用法的差別就是倒過來放而已。

'a'.in?(['Cat', 'Dog', 'Bird'])       # => false
['Cat', 'Dog', 'Bird'].include?('a')  # => false

include? 為陣列的方法,照理說應該要放在Day4,但由於放在這裡剛剛好,提前先提到。

start_with? && end_with?

有了start_with?, end_with?,可以少寫很多正則表達式!

"popping_hoan".start_with? "popping" #=> true
"jquery".end_with? "query"           #=> true

Strip white space

strip 為去除頭尾的扣白,可以應用在填寫表單。當使用者前後不小心加了空白,我們就可以使用strip

"   I have leading and trailing white space   ".strip 
#=> "I have leading and trailing white space"

Regex

正規表達式是大部分的工程師,只要每用一次就要查一次的頭痛地雷,正因為這樣,對於一些常用的正則,我們可以把它放在心上。Ruby, Rails對一些常用的正則已經囊括進來,像是email 的驗證便不用從頭寫過,ruby的函式庫也有很多正則表達的替代寫法,例如上面提到的start_with?, end_with?

我們來講一下一些關於字串的用法

⭐️ 在字串中判斷數字

def number?(obj)
  obj = obj.to_s unless obj.is_a? String
  
  /\A[+-]?\d+(\.[\d]+)?\z/.match(obj)
end

# /         start of the regex
# \A        start of the string to be matched
# [+-]?     zero or one of '+' or'-'
# \d+       one or more of digit
# (\.\d+)?  zero or one of 'one dot and 'one or more of digit'' 
# \z        end of the string to be matched
# /         end of the regex

⭐️ 字串判斷A-Z(不是疫苗,是A到Z)

/\A[A-Z]+?\z/.match("ABC") # => #<MatchData "AZ">/\A[A-Z]+?\z/.match("A123") # => nil

⭐️ 字串判斷A-Z和.-+

/\A[A-Z+-\.]+?\z/.match("AZZR.-+")

⭐️ 字串判斷手機載具:第一個字元/,後7個字元包含 A-Z, .-+

/\A[\/][A-Z+-\.]{7}\z/.match("/AAA++++")

⭐️ 字串判斷自然人憑證:2位大寫+14位數字

/\A[A-Z]{2}[0-9]{14}\z/.match("AA22222222222222")
/\A[A-Z]{2}[0-9]{14}\z/ =~ ("AA22222222222222")

Replace

⭐️ 字串的取代也是很重要的語法,在Ruby我們可以使用gsub 來取代字串,下列為一些取代的例子。

"chenhanting".gsub(/[aeiou]/, '*')                  #=> "ch*nh*nt*ng"
"chenhanting".gsub(/([aeiou])/, '<\1>')             #=> "ch<e>nh<a>nt<i>ng"
"chenhanting".gsub(/./) {|s| s.ord.to_s + ' '}      #=> "99 104 101 110 104 97 110 116 105 110 103 "
"chenhanting".gsub(/(?<foo>[aeiou])/, '{\k<foo>}')  #=> "ch{e}nh{a}nt{i}ng"
'chenhanting'.gsub(/[eo]/, 'e' => 3, 'o' => '*')    #=> "ch3nhanting"

"Me & You".gsub(/[&]/, 'and')                 #=> "Me and You"

ActiveSupport StringInquirer && ArrayInquirer

ActiveSupportRails 的寶庫,很多東西都可以在 ActiveSupport 找,ActiveSupportrails的地位就相當於 Javascriptlodash,只不過不一樣的地方是 lodash 為外部庫、ActiveSupport 則為Rails的內建庫。

vehicle = ActiveSupport::StringInquirer.new('car')
vehicle.car?   # => true
vehicle.bike?  # => false

variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])

variants.phone?    # => true
variants.tablet?   # => true
variants.desktop?  # => false

variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])

variants.any?                      # => true
variants.any?(:phone, :tablet)     # => true
variants.any?('phone', 'desktop')  # => true
variants.any?(:desktop, :watch)    # => false

Case equality operator ===

Ruby的等於一共有這幾種:equal?, ==, eql?。等於的方法,常用的為後面兩種,eql?==還嚴格,不過基本上沒太多嚴格定義,兩者可以混用。

5 == 5.0     # true
5.eql? 5.0   # false

但跟===的概念完全不一樣,===Ruby的概念為case when,跟等於一點關係都沒有。我們實際舉一些範例,跟附上說明:

# 1 是不是符合在1到10裡面
(1..10) === 1
# 'abcdef'字串是不是符合 /abc/ 的正則表達
/abc/ === 'abcdef'
# 'abcdef' 是不是 string
String === 'abcdef'

然而===的寫法實在不好看懂,Ruby有很多寫法可以語法糖可以替代 ===,以下是推薦的好寫法

# Bad
(1..10) === 1
/abc/ === 'abcdef'
String === 'abcdef'

# Good, uses synonym method
(1..10).include?(1)
/abc/ =~ 'abcdef'
'abcdef'.is_a?(String)

接著,我們來比較case when vs === 。以下為使用case when,與使用=== 的比對

def display_installment(v)
  case v
  when 0 then '一次付清'
  when 3 then '3期'
  when 6 then '6期'
  end
end
   
# 被比較的放在右側    
def display_installment(v)
  if 0 === v
    '一次付清'
  elsif 3 === v
    '3期'
  elsif 6 === v
    '6期'
  end
end
    
display_installment(6)

會拿case when===當例子,是因為case when 使用 === 實作

%Q

%q, %Q 都可以包字串,但 %Q 代表雙引號,%q代表單引號。雙引號的字串裡面可以放變數、放特殊字元。因此若要使用,就使用%Q,而不要使用%q

%Q(han react
ruby
vue it2021)

#=> "han react\nruby\nvue it2021"
str = 'apple'
%Q[#{str} is "DELICIOUS"]
#=> "apple is \"DELICIOUS\"

實際上,我們可以在helper裡面,可以使用%Q回傳html

def get_form(action, options = {})
  # ......
      
  %Q(
    <form name='xxxx' method='post' action='#{url}'>
      #{params.map do |k,v|
          "<input name='#{k}' type='hidden' value='#{v}'/>"
        end.join("")
      }
      <input type='submit'/>
    </form>
  )
end

我們在helper使用%Q,來引入 recaptcha的樣式。

module ApplicationHelper
  RECAPTCHA_SCORE_SITE_KEY = ENV['RECAPTCHA_SCORE_SITE_KEY']
  RECAPTCHA_CHECKBOX_SITE_KEY = ENV['RECAPTCHA_CHECKBOX_SITE_KEY']

  def include_recaptcha_js
    raw %Q{<script src="https://www.google.com/recaptcha/enterprise.js?render=#{RECAPTCHA_SCORE_SITE_KEY}"></script>}
  end

  def recaptcha_execute(action)
    raw %Q{
      <input name="recaptcha_token" type="hidden" id="__recaptcha_token__"/>

      <script>
        grecaptcha.enterprise.ready(function() {
          grecaptcha.enterprise.execute('#{RECAPTCHA_SCORE_SITE_KEY}', {action: '#{action}'})
            .then((token) => {
              console.log('reCAPTCHA score action', '#{action}');
              console.log('reCAPTCHA score token:', token);
              document.getElementById("__recaptcha_token__").value = token;
            })
            .catch((error) => {
              console.log(`The unknown error has occurred: ${error}`);
            });
        });
      </script>
    }
  end
end

其他方法

  • chomp:移除結尾字元
  • chop
  • concat
  • range
'kitty'[1..2] #=>it
  • ljust
  • reverse

應用情境

⭐️ 顯示子訂單的出貨來源

# 有廠牌 & 店櫃資訊: iphone-新莊店
# 只有廠牌資訊: iphone
#
[sub_order.brand.title, sub_order.store.title_zh].compact.join('-')

⭐️ 將We have lunch at 12 o'clock句子的英文字轉成數字,其餘特殊符號如空白、引號剔除,並用底線連接。以下為對應表跟實際內容!

A => 1
B => 2
C => 3
...
Z => 26
str = "We have lunch at 12 o'clock"
str.downcase.chars.select { |s| /\A[A-Z|a-z]+?\z/.match(s) }.map{|n| n.ord - 'a'.ord}.join('_')

#=> "22_4_7_0_21_4_11_20_13_2_7_0_19_14_2_11_14_2_10"

參考資料


上一篇
Day2. Ruby 的基本介紹 - 讓大家認識並愛上Ruby
下一篇
Day4. 一起精通 Rails Array,處理更複雜的問題
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言