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 後,擁有 gettersetter 的 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: falseinstance_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) }}
直播中

尚未有邦友留言

立即登入留言