Ruby / Railsで、key-value なオブジェクトから値を取得するイディオム

# 環境
Ruby 2.5.1
Ruby on Rails 5.0.5

---

# きっかけ

Rails を触っていると、Hash から特定の key の値だけ取得したい時がある

---

```rb
hash = { key1: "value1", key2: "value2", key3: "value3" }

hash.slice(:key1, :key2)
=> {:key1=>"value1", :key2=>"value2"}
```

---

違う、そうじゃない

---

おれは値だけがほしいんだ

```rb
hash.slice(:key1, :key2)
=> {:key1=>"value1", :key2=>"value2"}
# ↓ 本当はこうしたい
=> ["value1", "value2"]
```

---

```rb
> hash.extract(:key1, :key2)
NoMethodError: undefined method `extract` for {:key1=>"value1", :key2=>"value2", :key3=>"value3"}:Hash
...

> hash.pluck(:key1, :key2)
TypeError: no implicit conversion of Symbol into Integer
...

> hash.attributes(:key1, :key2)
NoMethodError: undefined method `attributes` for {:key1=>"value1", :key2=>"value2", :key3=>"value3"}:Hash
...

```

---

んーなんだっけ？


といつも自分でもわからなくなるので、似たような目的のメソッドとか書き方を自分用としてまとめました

---

ちなみに、さきほどの問題に対しての回答は

```rb
hash.values_at(:key1, :key2)
=> ["value1", "value2"]
```

でした

---

# Hash

```rb
hash = { key1: "value1", key2: "value2", key3: "value3" }

# 値だけ
hash.values_at(:key1, :key2)
=> ["value1", "value2"]

# Hash として
hash.slice(:key1, :key2) # ruby 2.5.0 で ActiveSuport から輸入
=> {:key1=>"value1", :key2=>"value2"}
```

---

# Hash[]

```rb
> hashes = [{ key1: "value1-1", key2: "value2-1"}, { key1: "value1-2", key2: "value2-2"}]

# 値だけ
hash.pluck(:key1) # rails 5.0 からの Enumerable 拡張
=> ["value1-1", "value1-2"]

# Hash として
hashes.map { |h| h.slice(:key1) }
=> [{:key1=>"value1-1"}, {:key1=>"value1-2"}]
```

---

# ApplicationRecord

```rb
message = Message.take

# 値だけ
user.attributes.values_at("title", "text") # #attributes は Hash を返すので、キーの指定が文字列である必要がある
or
user.slice(:title, :text).values # Ruby 1.9 から Hash は順序を保存するようになったのでこれでも大丈夫
=> ["タイトル1", "内容1"]

# Hash として
user.slice(:title, :text)
=> {"title"=>"タイトル1", "text"=>"内容1"} # with_indifferent_access されて帰ってくるので、文字列でもシンボルでもアクセス可能
```

---

# ActiveRecord::Associations

```rb
users = User.take(2)

# 値だけ
users.pluck(:title, :text)
=> [["タイトル1", "内容1"], ["タイトル2", "内容2"]]

# Hash として
users.map { |u| u.slice(:title, :text) }
=> [{"title"=>"タイトル1", "text"=>"内容1"}, {"title"=>"タイトル2", "text"=>"内容2"}]
```

---

おまけ

似たようなメソッドがいっぱいあるぞ！

---

## Hash#extract!

最初に間違えたやつのBANメソッド
何故か `#extract` は存在しない
動作は `#slice!` と同様

```rb
hash = { key1: "value1", key2: "value2", key3: "value3" }

hash.extract!(:key1, :key2)
=> {:key1=>"value1", :key2=>"value2"}
hash
=> {:key3=>"value3"}
```

---

## Hash#except

`#slice` の逆
特定のキーとその値を除外したい時に使える

```rb
hash = { key1: "value1", key2: "value2", key3: "value3" }

hash.except(:key3)
=> {:key1=>"value1", :key2=>"value2"}
```

---

## Hash#fetch_values

`#values_at` と似たような挙動をするメソッド
キーが見つからなければ `KeyError` が発生する（`#values_at`は`nil`が返る）
ブロックを渡すと `KeyError` の代わりに、その評価値が返る

```rb
hash = { key1: "value1", key2: "value2", key3: "value3" }

hash.fetch_values(:key1, :key2)
=> ["value1", "value2"]
hash.fetch_values(:key1, :key3)
KeyError: key not found: :key3
hash.fetch_values(:key1, :key3) { :not_found }
=> ["value1", :not_found]
```

---

## Hash#assoc

Hash のキーを指定すると、引っかかった key-value の配列を返す
キーの指定は1つのみ

元々は、多次元配列に対して要素を取り出すのが目的な感じに見える（Arrayでも使えるし）

```rb
{ key1: "value1", key2: "value2" }.assoc(:key1)
=> [:key1, "value1"]
[[:key1, "value1"], [:key2, "value2"]].assoc(:key1)
=> [:key1, "value1"]
```

ちなみに、rassocというメソッドもあり、こちらはキーではなく値を引数に指定する