iT邦幫忙

2022 iThome 鐵人賽

DAY 5
0
Modern Web

Rails,我要進來囉系列 第 5 篇

第五天:稍微深入 ActiveSupport 一點點,一起來看點 source code

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起大致看過 ActiveSupport 包含了哪些 magic,今天就來看一下 ActiveSupport 的這些 magic 大概都是怎麼實作的,夠夠~

本篇的 source code 來源

https://github.com/shrimp509/my-img-host/blob/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-1.png?raw=true

RailsGuide 在 ActiveSupport 這篇,有把每小節相關的 source code 以上圖紅框的形式提供出來,點進去連結後,會連到 Github rails v7.0.3.1 版本的 source code

大部分的 magic method 的實作方式

其實 ActiveSupport 針對 Ruby 所強化的一些 magic method,實作上並不複雜,大部分都只是把常用的 method 用原本 Ruby 的方式實作而已,舉例來說 Rails 有提供 blank? 和 present? 的好用東東,可以檢查物件是不是空的、是不是存在的,而這兩個 method 的實作方法如下:

https://github.com/shrimp509/my-img-host/blob/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-2.png?raw=true

blank? 就只是拿原本 Ruby 的 empty? 過來改,如果該物件沒有 empty? 這個 method 的話,就改以自己本身當作判斷標準

而 present? 也是把 blank? 在反向而已

題外話…

blank? 這邊的實作我覺得怪怪的,如果是 " " 的話," ".empty? 還是 false 啊,那 " ".blank? 是怎麼變成 true 的?!

我還做了個實驗,我把 String 新增一個叫做 is_blank? 的 method,用 " ".is_blank? 出來的結果還是一樣是 false

class String
	def is_blank?
		respond_to?(:empty?) ? !!empty? : !self
	end
end

'  '.is_blank?  # false
'  '.blank?     # true

不知道我實驗哪裡錯了,希望大神知道答案的話,再麻煩留言告訴我 QQ

[微補充]

在我寫完文章之後,我問了 Rails 社群的大大,過沒多久就得到解答,感謝 Yan Jun 大神!

原來是我只有看 Object 的 blank? 但漏看了 String 的 blank?,原來除了 empty? 之外,還有多做 blank 的正規判斷

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-3.png

ActiveSupport 對 Module 的擴充 (RailsGuide 3.1.3)

mattr_reader, mattr_writer, mattr_accessor, cattr_reader, cattr_writer, cattr_accessor

我第一次看到 mattr_* 這種酷東東,我們先從常用的 attr_accessor 出發:

class User
	attr_accessor :name, :email
end

user = User.new
user.name = 'Handsome Sam' # Handsome Sam
user.email = 'sam.ho@relacs-studio.com'
user # <User:0x00000001233f6be0 @email="sam.ho@relacs-studio.com", @name="Handsome Sam">

attr_accessor 可以讓 Class 被實體化變成 instance 後,擁有 getter 和 setter 的 attribute,相當於 attr_reader + attr_writer;而 mattr_accessor 功能就跟 attr_accessor 幾乎一樣,只是 mattr_accessor 是給 module 使用的,所以前面多加了個 m,我們直接來看官方範例:

module HairColors
	mattr_reader :hair_colors
end

HairColors.hair_colors # => nil
HairColors.class_variable_set("@@hair_colors", [:brown, :black])
HairColors.hair_colors # => [:brown, :black]

mattr_reader 讓 Module 方便擁有 class variable,同時替這個 class variable 新增 getter

再看一個例子,使用 module 的 class 也會有相對應的 instance methods

module HairColors
	mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
end

class Person
	include HairColors
end

Person.new.hair_colors # => [:brown, :black, :blonde, :red]

也有 options 可以決定要不要產生 instance method

module HairColors
	mattr_reader :hair_colors, instance_reader: false
end

class Person
	include HairColors
end

Person.new.hair_colors # => NoMethodError

讓我們來偷看一下 ActiveSupport 的 mattr_reader 是怎麼實作的:

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-4.png

rails/attribute_accessors.rb at v7.0.3.1 · rails/rails

整個 mattr_reader 才 20 行,其實不難理解,就是利用 ruby 有 Open Class 的特性,可以事後對 Class 進行修改,所以透過 mattr_reader 把新增的 class methods 加進去,如果有指定 instance_reader: false 或 instance_accessor: false 的話,就不新增 instance methods。

ActiveSupport 對 String 的擴充 (Rails Guide 5.7)

我這也是第一次看到 inquiry 的用法,覺得很酷,跟大家分享

"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false
"active".inquiry.hello?          # => false

就是能像是 rails enum 會產生 enum 的 xxx? method,不過 inquiry 不太一樣,它是可以用任何字串接問號的,我們來看一下怎麼實作的

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-5.png

接著再去 ActiveSupport::StringInquirer 看看

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-6.png

  1. respond_to_missing?

    當你呼叫 str.respond_to?(:xxx) 時,如果 xxx 不是 string 有的 method 的話,那就會去執行 respond_to_missing? 這個 method,例如你去跑 '123'.respond_to?(:hello),String 沒有定義 hello 這個 method,就會去執行 respond_to_missing? 裡面的內容

  2. method_missing

    當你對 String 呼叫不存在的 method 的時候,就會去執行 method_missing 內的內容

我很好奇這個 method 的運作,所以用 Integer 做了個實驗

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-7.png

看 source code 又學到了神奇的東西 XD

回過頭來看一下 inquiry 的原理,其實就是把對 inquiry 呼叫的 method_name 去掉最後一個問號,再拿去跟字串本身的值做比較,於是就有了以下功能

"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false
"active".inquiry.hello?          # => false

# inquiry 就是把問號前面的字,跟字串本身的字做 == 判斷而已

總結

我對 ActiveSupport 的 source code 只有大概看過去而已,並沒有很深入、也沒有每個都看,大概整理出這三種類型

  1. 對原生 Ruby 有的 method 包裝 (e.g. empty? → blank?, present?)
  2. 對類似概念進行擴充 (e.g. mattr_accessor 是模仿原有的 attr_accessor)
  3. 對原生 Ruby Class 擴充出新奇的功能 (e.g. String 的 inquiry)

最後補充,我看的這篇 RailsGuide ActiveSupport,其實只有講 core_extension 而已,也就是說還有超多 magic 沒有提到的 XD,真是由衷佩服這些貢獻者,太強了

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day5-8.png

我們明天見~


上一篇
第四天:Ruby + ActiveSupport = Ruby 穿全身+9神裝!
下一篇
第六天:躲在 Rails 背後默默付出的幕後功臣 - ActiveJob
系列文
Rails,我要進來囉 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言