Ruby 有一個好用的 fetch
方法。可以用來取 Hash
、Array
的值。
假設今天有一個 Oauth Hash
長這樣:
oauth = {
'uid' => 123456789,
'data' => {
'email' => '[email protected]',
'first_name' => nil,
'last_name' => 'Bar'
},
'credentials' => {
'token' => "Token-3345678"
}
}
利用上面的資料給使用者設定 Email、名字、姓氏等,可以這麼寫:
User = Struct.new(:email, :name, :last_name, :token)
juan = User.new
juan.email = oauth['data']['email']
juan.name = oauth['data']['first_name']
juan.last_name = oauth['data']['last_name']
juan.token = oauth['credentials']['token']
歡迎一下使用者:
welcome = "Welcome! #{juan.name.capitalize} #{juan.last_name.capitalize}"
這時因為 Oauth Hash
的 first_name
是 nil
,就會看到這個惱人的錯誤(這也是 Ruby 最常見的錯誤):
> welcome
=> NoMethodError: undefined method `capitalize' for nil:NilClass
這個錯誤訊息非常差,尤其是在大的 codebase,很難找到錯誤發生的地方,到處都有可能發生。
這時候就可以使用 Hash
的 fetch
。
fetch
在找不到鍵時,便會拋一個錯誤:
> oauth.fetch('does-not-exist')
=> KeyError: key not found: "does-not-exist"
from (pry):1:in `fetch'
會明確的告訴我們這是一個 KeyError
。
fetch
還可以連鎖使用:
> oauth.fetch('data').fetch('b')
=> KeyError: key not found: "b"
from (pry):2:in `fetch'
把之前的賦值改成這樣,就會在找不到資料時告訴你:
juan.email = oauth.fetch('data').fetch('email')
juan.name = oauth.fetch('data').fetch('name')
juan.last_name = oauth.fetch('data').fetch('last_name')
juan.token = oauth.fetch('credentials').fetch('token')
從 Hash
fetch
的官方文件,看一下 fetch
接受的參數有什麼:
fetch(key [, default] )
fetch(key) {| key | block }
可看出來有兩種形式,「一個參數+可選參數」,或是「單一參數+區塊」。
這個“可選參數”與“區塊”就是用來設定,fetch
抓不到給定 key
的值時,用來回傳的預設值。
以下繼續使用上面的 oauth
Hash 來示範。
> oauth.fetch('dataaaaaa', 42)
=> 42
> oauth.fetch('dataaaaaa') { 42 }
=> 42
都可以設定預設值,這兩者有什麼區別呢?看下例:
假設今天預設值不是 42
這麼簡單,是個“昂貴的計算”,耗時三秒:
# 電腦會睡 3 秒
def expensive_computation
sleep 3
end
在 key
不存在時,兩種形式都會睡 3 秒:
> oauth.fetch('dataaaaaa', expensive_computation)
=> # 會睡 3 秒
> oauth.fetch('dataaaaaa') { expensive_computation }
=> # 會睡 3 秒
但是,在 key
存在時:
> oauth.fetch('data', expensive_computation)
=> # 會睡 3 秒,再返回值。
=> {"email"=>"[email protected]", "first_name"=>"Foo", "last_name"=>"Bar"}
> oauth.fetch('data') { expensive_computation }
=> # 直接返回值
=> {"email"=>"[email protected]", "first_name"=>"Foo", "last_name"=>"Bar"}
上例可看出,不管 key
存不存在,參數形式每次都會對預設值求值;
而區塊形式只在 key
不存在時,才做求值。
記得使用 fetch
時,預設值用“區塊形式”!
> arr = [*1..5] # 用來建構 [1,2,3,4,5] 的簡寫
=> [1, 2, 3, 4, 5]
> arr[6]
=> nil
> arr.fetch(6)
=> IndexError: index 6 outside of array bounds: -5...5
from (pry):33:in `fetch'
在找不到元素時,會明確的告訴你“索引”錯誤。
提供預設值的方法同上,參數形式與區塊形式。
fetch
區塊預設會傳入找不到的 key
(Hash
)或是索引(Array
)給區塊。
arr = [*1..5]
hash = {}
arr.fetch(6) do |index|
puts "Given index #{index} out of range, please provide a value:"
gets.chomp # chomp 用來消掉輸入的“換行符”
end
hash.fetch('not-exist') do |key|
puts "Missing key #{key}, please provide a value:"
gets.chomp # chomp 用來消掉輸入的“換行符”
end
區塊還可以這麼傳,去除重複:
get_default_value = ->(key_or_index) do
puts "#{key_or_index} not found, please enter it: "
gets.chomp
end
arr.fetch(6, &get_default_value)
hash.fetch('not-exist', &get_default_value)
這種區塊形式是 Ruby 1.9 的 lambda 語法。
有人說,為什麼不用 ||
就好了?請見下例:
> {}[:foo] || :default
=> :default
> {foo: nil}[:foo] || :default
=> :default
> {foo: false}[:foo] || :default
=> :default
# 使用 fetch
> {}.fetch(:foo) { :default }
=> :default
> {foo: nil}.fetch(:foo) { :default }
=> nil
> {foo: false}.fetch(:foo) { :default }
=> false
fetch
分的出是“沒有 key
”,還是 key
的值是 nil
或 false
。
:)