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的用途很多,以下列出三种含有&符号的使用用法

  • 处理ProcBlock ➡️ 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的基本语法

参考资料


<<:  网路是怎样连接的(一) 网路是甚麽

>>:  @Day2 | C# WixToolset + WPF 帅到不行的安装包 [使用参考专案打包的方式]

[Day07] - 新拟物风按钮(五) - 参数改变 & 监听变化

Day05 时 , 我们制作了一个可传入参数的 neuomorphic-button <neu...

倒数第4天

最近有银行在更新 似乎有灾情 来看看C#是否可以写出 定义银行帐户类型 您可以从建立能定义该行为之类...

学习历程救援事件(灾难复原实例)

本次的事件起因是一名教育部委外厂商在搬移资料的过程中误删档案,导致虚拟硬碟被重新设定,重开机後,学生...

iOS APP 开发 OC 第十天,NSObject

tags: OC 30 day NSObject 是什麽? 是Foundation 框架中的类,在这...

JavaScript Day10 - 函式

函式(function) 可参考:Day08 - 函数(01) 重复的内容会以函式来定义,来减少重工...