為什麼 Hash 好像有不同的寫法?
Ruby 裡的 Hash 的寫法,是用一個大括號,裡面是一堆 key 跟 value 的配對組合,一個蘿蔔一個坑,就像這樣:
profile = { :name => "見龍", :age => 18, :title => "紅寶石鑑定商" }
在 Ruby 1.9 之後,Ruby 提供了另一款類似 JSON 格式的 Hash 寫法,上面這行可以改寫成這樣:
profile = { name: "見龍", age: 18, title: "紅寶石鑑定商" }
少了 =>
,改用 :
並且把 key 前面的冒號也移掉了,看起來好像比較清爽一點。但不管是舊式或是新式的 Hash 寫法,本質上並沒有改變,新式寫法其實只是語法糖衣而已,不信可試著把它印出來看看:
puts profile # => {:name=>"見龍", :age=>18, :title=>"紅寶石鑑定商"}
會得到跟舊式寫法一樣的結果。 所以,如果想要取得 profile
這個 Hash 的內容,還是得用 Symbol 來存取,使用字串會取得不正確結果:
puts profile[:name] # => 見龍
puts profile["name"] # => nil
puts profile[:title] # => 紅寶石鑑定商
puts profile["title"] # => nil
但我在寫 Rails 的時候好像都可以耶...
隨便打開一個 Rails 專案的 Controller,你可能會看過像這樣的程式碼:
class ProductionController < ApplicationController
def show
@product = Product.find(params[:id])
end
end
這裡不管你是用 params[:id]
或是 params["id"]
,都可以拿到你要的結果。
怎麼辦到的? 我們直接在 show
action 裡放個中斷點看看,順便來練習怎麼追程式碼:
[9, 19] in /private/tmp/blog/app/controllers/products_controller.rb
9:
10: # GET /products/1
11: # GET /products/1.json
12: def show
13: @product = Product.find(params[:id])
=> 14: debugger
15: end
16:
17: # GET /products/new
18: def new
19: @product = Product.new
# 先看一下 params 的內容
(byebug) params
<ActionController::Parameters {"controller"=>"products", "action"=>"show", "id"=>"1"} permitted: false>
# 很好,這個 params 看起來像是一個 Hash
# 而且它是用字串當做 key,讓我們試著用字串來取值...
(byebug) params["id"]
"1"
# 用 Symbol 也可以
(byebug) params[:id]
"1"
# 來看看這個 params 是什麼東西...
(byebug) params.class
ActionController::Parameters
# 繼續來挖看看這個取值的 [] 方法放在哪兒...
(byebug) params.method("[]".to_sym)
#<Method: ActionController::Parameters#[]>
(byebug) params.method("[]".to_sym).source_location
["/Users/user/.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.beta3/lib/action_controller/metal/strong_parameters.rb", 400]
抓到了! 就讓我們打開這個 strong_parameters.rb
檔案的第 400 行,繼續追:
def [](key)
convert_hashes_to_parameters(key, @parameters[key])
end
嗯...這個 @parameters
是怎麼來的? 發現在 initialize
方法裡有寫到:
def initialize(parameters = {})
@parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters
end
原來,@parameters
是呼叫了 Hash 類別的 with_indifferent_access
方法所做出來的。但這個方法並不是 Ruby 內建的,而是 Rails 幫 Hash 做的擴充功能。
Rails 幫 Ruby 的 Hash 加了一個 ActiveSupport::HashWithIndifferentAccess
類別,讓開發者在取存 Hash 的時候可以使用 Symbol (例如:params[:id]) 也可以使用字串 (例如:params["id"]
),畢竟在其它程式語言不見得有 Symbol 的設計,這樣的擴充可以讓從別的程式語言轉過來的新朋友比較能習慣。
如果對 ActiveSupport::HashWithIndifferentAccess
的內容有興趣,可以點這裡看看它是怎麼做到的。
我們開 irb
來玩一下:
>> require "active_support/core_ext/hash/indifferent_access"
=> true
# profile 是一般的 Hash
>> profile = {name: "見龍", age: 18, title: "紅寶石鑑定商"}
=> {:name=>"見龍", :age=>18, :title=>"紅寶石鑑定商"}
# 用字串拿不到
>> profile["name"]
=> nil
# 要用 Symbol 才可以
>> profile[:name]
=> "見龍"
# 用 with_indifferent_access 做一個新的 new_profile
>> new_profile = profile.with_indifferent_access
=> {"name"=>"見龍", "age"=>18, "title"=>"紅寶石鑑定商"}
# 用字串可以
>> new_profile["name"]
=> "見龍"
# 用 Symbol 也可以
>> new_profile[:name]
=> "見龍"
所以,下回在存取 Hash 裡的東西的時候,要記得要拿正確的 key 喔 :)
工商服務
實體課程:Ruby on Rails 實戰課程
線上課程:五倍學院線上課程