[Rails]カラムを絞るだけの目的でselectメソッドは使うな！

# ActiveRecordのselectメソッドについて

ActiveRecordでデータを取得すると基本的に該当するテーブルのすべての項目が取得されます。
発行するSQLを見ていただくと分かる通り、*で全項目取得しています。
全項目取得しているので、以降の処理ではどの項目でも参照することができます。

```pry
pry(main)> user = User.first
  User Load (0.7ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 1, name: "ham", created_at: "2020-03-10 01:03:37", updated_at: "2020-06-16 02:18:39">
pry(main)> user.id
=> 1
pry(main)> user.name
=> "ham"
```

ただ、全カラム使うわけではないので必要なカラムだけ取得したほうがいいのでは？という考え方もあると思います。
そんなときにselectというメソッドを使うことで取得するカラムを絞ることができます。
selectについて詳しくは[Railsガイド](https://railsguides.jp/active_record_querying.html#%E7%89%B9%E5%AE%9A%E3%81%AE%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%A0%E3%81%91%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99) をご覧ください。

selectを指定することで必要なカラムだけ取得することができます。
取得していないカラムは当然以降の処理では参照できません。

```pry
pry(main)> user = User.select(:id, :created_at).first
  User Load (0.7ms)  SELECT `users`.`id`, `users`.`created_at` FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 1, created_at: "2020-03-10 01:03:37">
pry(main)> user.id
=> 1
pry(main)> user.name
ActiveModel::MissingAttributeError: missing attribute: name
from /usr/local/bundle/gems/activemodel-6.0.3.2/lib/active_model/attribute.rb:221:in `value'
```

# カラムを絞るだけのselectは使うな！

あくまで私個人の考えではあるのですが、チーム開発など複数人で開発を行っている場合はカラムを絞るだけのselectは使わないほうがいいと考えています。

## なぜなのか？

下記のコードを見てください。

```ruby
def hoge(user_id)
  # selectでid, nameだけ取得
  user = User.select(:id, :name).find(user_id)

  ...(様々な処理)

  generate_response(user)
end

private

def generate_response(user)
  { id: user.id, name: user.name }
end
```

後にhogeメソッドのレスポンスにemailを追加することになったらどうでしょうか？
Userモデルはemailカラムを持っていることとします。

おそらく該当箇所を見つけて、generate_responseにemail足せばいいだけだな！
と思い、下記のように修正すると思います。

```diff
def generate_response(user)
-  { id: current_user.id, name: current_user.name }
+  { id: current_user.id, name: current_user.name, email: current_user.email }
end
```

よし修正終わり！1行で出来たぜ！！テスト実行！！！

```pry
pry(main)> { id: user.id, name: user.name, email: user.email }
ActiveModel::MissingAttributeError: missing attribute: email
from /usr/local/bundle/gems/activemodel-6.0.3.2/lib/active_model/attribute.rb:221:in `value'
```

あれ？？動かないぞ・・・
受け取っているuserがおかしいのか？
たどってたどって、、、

そうです。selectで取得カラムを絞っているのでそちらにemailを足す必要があります。
下記も修正すれば動くようになります。

```diff
def hoge(user_id)
  # selectでid, nameだけ取得
-  user = User.select(:id, :name).find(user_id)
+  user = User.select(:id, :name, :email).find(user_id)
```

テストもうまくい通りました！

```pry
pry(main)> { id: user.id, name: user.name, email: user.email }
=> {:id=>1, :name=>"hoge", :email=>"hoge@example.com"}
```

## どう思いますか？

RailsのActiveRecordを使うと基本全カラムを取得すると思うので上記のように一度はハマる人が多いと思います。

1回1回の手間はそこまでではないかもしれませんが、継続的に開発されていくシステムであれば毎回同じ事が起きます。
これは結構なコストです。また最悪の場合、気づかずにバグを生む可能性もあります。

今回のselectは開発コストやバグのリスクを上げてまで実装する必要があるのでしょうか？
私は多少最適ではなかったとしても他の人が勘違いしづらいコードのほうが良いと思っています。
これが私がカラムを絞るだけで使うselectは使わないほうが良いと思っている理由です。

# selectの使いどころ

selectの存在を全否定している記事になってしまったのですが、もちろん使いどころもあります。
下記のように集計関数を使ったときです。

```ruby
users_group_by_name = User.select('name, count(*) AS cnt').group(:name)
users_group_by_name.each do |u|
  p u.name
  # u.cntでカウントが取得できる
  p u.cnt
end
```

ただ、この場合も変数名を`users`などにしてしまうと勘違いさせてしまう可能性が高いので、それとわかる変数名にしたほうが良いでしょう。

あと、たまにjoinした先のテーブルをselectを使って直接アクセスできるようにしていることがありますが、これもとても分かりづらいのでやめたほうが良いと思います。

```ruby
review = Review.select('reviews.id, users.name').joins(:user).find_by(id: 1)
# これでuser.nameにアクセスできる
review.name
```

普通にアソシエーション経由でアクセスするかdelegateを実装しておきましょう。

```ruby:app/models/review.rb
review = Review.find_by(id: 1)
# アソシエーション経由でアクセス
review.user.name
# もしくはReviewモデルにdelegateを定義しておく (delegate :name, to: :user, prefix: true)
review.user_name
```

# まとめ

この記事ではselectに焦点を当てましたが、複数人が同じコードを触るチーム開発では他人が理解しやすい（勘違いしづらい）コードを書くことが重要だと思います。
読みやすい（勘違いしづらい）コードを書くことで開発スピードが上がり、バグも減ると思います。