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"

参考资料


<<:  GitHub 基本功能介绍 - 开始建立第一个储存库

>>:  Day 3. Hashicorp Nomad: Server configuration for production

Day 25 进入开发者模式

Odoo选单选择 [设定] -> 画面右下角有个 [启用开发者模式], 选下去! 这就完成啦!...

[区块链&DAPP介绍 Day22] Dapp 实战 安装 metamask

今天开始到结束,要进入到实际 Dapp 的应用了,但在应用之前要先会安装 metamask。 因为要...

SC-900 转储 - Microsoft SC-900 考试简介

您准备好了解 Microsoft 安全合规性和身份基础知识了吗? 由於 IT 行业处於永无止境的进步...

Day 17 - Event Handling

使用大括号与驼峰式命名来处理 DOM 是它与原生 jS 的差别。 //jS 写法 <butto...

Day 4 - 介绍Laravel Eloquent ORM

前一篇介绍了如何运用 Laravel 框架设计模式规划大型专案,当中有提到Model,今天就来介绍这...