# 為什麼 Hash 好像有不同的寫法？

> 

Published: 2016-04-23
URL: https://kaochenlong.com/different-hash-format

---

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


