mapメソッドの実践的な使い方


課題

皆さんは、ポートフォリオ等でオリジナルアプリを作成する際に下記のようなことで困ったことはありませんか?

  • マイページで口コミを投稿した店舗(商品)を一覧で表示したいが、店舗(商品)を取得する方法がわからない。

イメージとしては以下のような感じです。※paramsではユーザーidのみ取得可能

histories_controller.rb
  def index
    user = User.find(params[:user_id])
    @reviews = Review.where(user_id: user.id)
  end

DB図

ここから、ビューファイルに店舗一覧を表示したいと思うと以下のように無理やりになってしまいます。
※一部抜粋

views/histories/index.html.erb
    <% @reviews.each do |review| %>
      店舗名:<%= review.shop.name%>
      店舗投稿された口コミ数:<%= review.shop.reviews.count%>
      口コミの全内容:<% review.shop.reviews.each do |review|%>
        口コミのタイトル<%= review.title%>
        口コミ投稿者<%= "by #{review.user.name}"%>
                  <% end %>
    <% end %>

これ見づらいだけでなく、同じ店舗に口コミを2回している場合、一覧でも店舗が2回登場してしまいます。
どうにかして、特定のユーザーのマイページでそのユーザーが口コミを投稿した店舗を取得したい、、
しかし、reviews変数が配列で要素を所持しているから扱いづらい、、

結論

その時にmapメソッドが非常に役に立ちます!
結論から言うと下記の書き方で口コミした店舗データのみを取得できます。

histories_controller.rb
  def index
    user = User.find(params[:user_id])
    reviews = Review.where(user_id: user.id)
    @shops = Shop.where(id: reviews.map{|review| review.shop_id})
  end

mapメソッドは公式リファレンスによると以下の説明が書いています。

各要素を順番にブロックに渡して評価し、その結果で要素を置き換えます。

つまり、reviews変数がもつ配列内のすべての要素に対して、whereメソッドで検索したことになります。
reviews内の各要素を順番にreview変数にいれた後にshop_idを参照しているということです。

これで、口コミしたことがある店舗データをshops変数内に取得しました。
しかもwhereメソッドにはdistinctの処理が自動的に入っているので、店舗データの重複はしないようになっています。

その結果ビューファイルも以下のようにスッキリ記載することができます。

views/histories/index.html.erb
    <% @shops.each do |shop| %>
      店舗名:<%= shop.name%>
      店舗投稿された口コミ数:<%= shop.reviews.count%>
      口コミの全内容:<% shop.reviews.each do |review|%>
        口コミのタイトル<%= review.title%>
        口コミ投稿者<%= "by #{review.user.name}"%>
                  <% end %>
    <% end %>

こんな感じでスッキリしました!
Arrayクラスを扱うときはmapメソッド、めちゃくちゃおすすめなので、ぜひ使ってみてください。