鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起大致看過 ActiveSupport 包含了哪些 magic,今天就來看一下 ActiveSupport 的這些 magic 大概都是怎麼實作的,夠夠~
RailsGuide 在 ActiveSupport 這篇,有把每小節相關的 source code 以上圖紅框的形式提供出來,點進去連結後,會連到 Github rails v7.0.3.1
版本的 source code
其實 ActiveSupport 針對 Ruby 所強化的一些 magic method,實作上並不複雜,大部分都只是把常用的 method 用原本 Ruby 的方式實作而已,舉例來說 Rails 有提供 blank?
和 present?
的好用東東,可以檢查物件是不是空的、是不是存在的,而這兩個 method 的實作方法如下:
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 的正規判斷
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
是怎麼實作的:
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。
我這也是第一次看到 inquiry
的用法,覺得很酷,跟大家分享
"production".inquiry.production? # => true
"active".inquiry.inactive? # => false
"active".inquiry.hello? # => false
就是能像是 rails enum 會產生 enum 的 xxx?
method,不過 inquiry
不太一樣,它是可以用任何字串接問號的,我們來看一下怎麼實作的
接著再去 ActiveSupport::StringInquirer
看看
respond_to_missing?
當你呼叫 str.respond_to?(:xxx)
時,如果 xxx 不是 string 有的 method 的話,那就會去執行 respond_to_missing?
這個 method,例如你去跑 '123'.respond_to?(:hello)
,String 沒有定義 hello
這個 method,就會去執行 respond_to_missing?
裡面的內容
method_missing
當你對 String 呼叫不存在的 method 的時候,就會去執行 method_missing
內的內容
我很好奇這個 method 的運作,所以用 Integer
做了個實驗
看 source code 又學到了神奇的東西 XD
回過頭來看一下 inquiry
的原理,其實就是把對 inquiry 呼叫的 method_name 去掉最後一個問號,再拿去跟字串本身的值做比較,於是就有了以下功能
"production".inquiry.production? # => true
"active".inquiry.inactive? # => false
"active".inquiry.hello? # => false
# inquiry 就是把問號前面的字,跟字串本身的字做 == 判斷而已
我對 ActiveSupport 的 source code 只有大概看過去而已,並沒有很深入、也沒有每個都看,大概整理出這三種類型
empty?
→ blank?
, present?
)mattr_accessor
是模仿原有的 attr_accessor
)inquiry
)最後補充,我看的這篇 RailsGuide ActiveSupport,其實只有講 core_extension 而已,也就是說還有超多 magic 沒有提到的 XD,真是由衷佩服這些貢獻者,太強了
我們明天見~