LoginSignup
23
41

More than 3 years have passed since last update.

【Rails】投稿一覧をプルダウンボックスを使い、日付やいいね数でソートする

Last updated at Posted at 2020-06-07

始めに

オリジナルアプリに投稿を日時やいいねの数によってソートする機能が欲しくて、結構ハマりつつ何とか実装できましたので、備忘録として残しておきます。

前提

●環境
 ・Ruby 2.6系
 ・Rails 5.2系
●使用ライブラリ
 ・Slim
 ・devise
●いいね機能が実装してあること
 ・【Rails】いいね機能の実装(この記事でSlimとdeviseの導入も行ってます)

↓実装後はこんな感じです↓

(見栄え悪くてゴメンナサイ…)
ezgif.com-video-to-gif.gif

実装

1. まずはプルダウン用のフォームを作成

app/views/posts/index.html.slim
h1 投稿一覧
 
= form_with model: @post, url: search_path, method: :get, local: true do |form|
  = form.select :keyword, [ ['投稿が新しい順', 'new'],
                            ['投稿が古い順', 'old'],
                            ['いいねが多い順', 'likes'],
                            ['いいねが少ない順', 'dislikes'],
                          ]
  = form.submit
 
#================元から実装されている部分========================
- @posts.each do |post|
  p
    = link_to post.user.email, user_path(post.user.id)
    br
    = link_to post.title, post_path(post.id)
    br
    = post.created_at
    br
    - if user_signed_in?
      - if post.favorited_by?(current_user)
        = link_to "いいね: #{post.favorites.count}", post_favorites_path(post.id), method: :delete
      - else
        = link_to "いいね: #{post.favorites.count}", post_favorites_path(post.id), method: :post
    - else
      = post.favorites.count
#================元から実装されている部分========================

・それぞれ何が起きてるか分解して見てみます。

= form_with model: @post, url: search_path, 
            method: :get, local: true do |form|

・フォームで使用するモデルとしてPostモデルを指定してます。これによりparams[:post]という値でデータが送信されます。

・検索後は検索結果表示用のページを用意するつもりので、パスはsearch_pathして、GETリクエストとして送信します。

local: true。form_withではこれを指定しないとAjax通信と見なされます。JSを使用しない場合は必ず付けないと、レンダリングされなくなってしまいます。

= form.select :keyword, [ ['投稿が新しい順', 'new'],
                          ['投稿が古い順', 'old'],
                          ['いいねが多い順', 'likes'],
                          ['いいねが少ない順', 'dislikes'],
                        ]
= form.submit

・selectメソッドを使用することによってプルダウンフォームを作成することができます。
 使い方はselect("object", "method", "choice")といった感じで使います。

第一引数であるobjectpostオブジェクトに当たりますが、form_withを使う場合は省略してください。

第二引数であるmethodコントローラへパラメータとして送信される物になるので、自由に記述してください。
 この場合、keywordを指定していますので、params[:keyword]として送信されます。

第三引数であるchoice部分では、
 [ [プルダウンで表示する値その1, アクションへ送信されるパラメータ],
 [プルダウンで表示する値その2, アクションへ送信されるパラメータ],
 [プルダウンで表示する値その1, アクションへ送信されるパラメータ] ]
を指定します。

2. ルーティングを編集

config/routes.rb
Rails.application.routes.draw do
#================元から実装されている部分========================
  devise_for :users
  resources :users
 
  root to: 'posts#index'
  resources :posts, only: [:index, :new, :create, :show, :edit, :update] do
    resource :favorites, only: [:create, :destroy]
  end
#================元から実装されている部分========================
 
  #検索結果をpostsコントローラのsearchアクションへ送信
+ get 'search' => 'posts#search'
 
end

3. postsコントローラを編集

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  .
  .
  .
 def search
    selection = params[:keyword]
    @posts = Post.sort(selection)
 end
  .
  .
  .
end

・searchアクションを作成します。params[:keyword]ではソートフォームで選択した値を受け取って、selectionというローカル変数へ格納してます。
 
sortはPostに対するインスタンスメソッドです、この後Postモデルクラスに、selectionの値によってソートする順番で投稿が取得するように定義します。

4. Postモデルにsortインスタンスメソッドを定義する。

app/models/user.rb
class Post < ApplicationRecord
  .
  .
  .
  def self.sort(selection)
    case selection
    when 'new'
      return all.order(created_at: :DESC)
    when 'old'
      return all.order(created_at: :ASC)
    when 'likes'
      return find(Favorite.group(:post_id).order(Arel.sql('count(post_id) desc')).pluck(:post_id))
    when 'dislikes'
      return find(Favorite.group(:post_id).order(Arel.sql('count(post_id) asc')).pluck(:post_id))
    end
  end
  .
  .
  .
end

・インスタンスメソッドの定義なのでselfを付与してます。

・case文によって、selectionに渡ってきた値によって、ソートする条件を定義しています。

↓日付によるソート

when 'new'
   return all.order(created_at: :DESC)
when 'old'
   return all.order(created_at: :ASC)

↓いいね数によるソート

when 'likes'
   return find(Favorite.group(:post_id).order(Arel.sql('count(post_id) desc')).pluck(:post_id))
when 'dislikes'
   return find(Favorite.group(:post_id).order(Arel.sql('count(post_id) asc')).pluck(:post_id))

・Favorite.group(:post_id)で、post_idが同じ投稿をグループ分けします。

・order(Arel.sql('count(post_id) desc'))で、グループ分けされた投稿へどれだけuser_idが格納されているかをカウントして多いものから並べます(つまりどれだけいいねされているかを集計してます)。
 Arel.sql()はSQLインジェクション対策です。

・pluck(:post_id)では、post_idそのものの取得を行っています。これを記述しないと、Postへ対して呼び出しているfindに該当するものが見つからず、エラーになってしまいます。

※いいねの数が0の投稿が表示されない問題に関しては、現状では未対応です。申し訳ありません…。解決出来次第追記します。

5. 検索結果のビューを表示

app/views/posts/search.html.slim
h1 検索結果
 
= form_with url: search_path, method: :get, local: true do |form|
  = form.select :keyword, [ ['投稿が新しい順', 'new'],
                            ['投稿が古い順', 'old'],
                            ['いいねが多い順', 'likes'],
                            ['いいねが少ない順', 'dislikes'],
                          ]
  = form.submit
 
- @posts.each do |post|
  p
    = link_to post.user.email, user_path(post.user.id)
    br
    = link_to post.title, post_path(post.id)
    br
    = post.created_at
    br
    - if user_signed_in?
      - if post.favorited_by?(current_user)
        = link_to "いいね: #{post.favorites.count}", post_favorites_path(post.id), method: :delete
      - else
        = link_to "いいね: #{post.favorites.count}", post_favorites_path(post.id), method: :post
    - else
      = post.favorites.count

・app/views/postsディレクトリ内に、seatch.html.slimファイルを作成して、上記のように記述します。
 
・ここに記述されている内容は、posts/index.html.slimに記述されている内容と全く同じ内容なので、パーシャル化してしまいます。

6. ビューのパーシャル化

6-1. 検索フォームのパーシャル化

app/views/posts/_select_form.html.slim
= form_with model: @post, url: search_path, method: :get, local: true do |form|
  = form.select :keyword, [ ['投稿が新しい順', 'new'],
                            ['投稿が古い順', 'old'],
                            ['いいねが多い順', 'likes'],
                            ['いいねが少ない順', 'dislikes'],
                          ]
  = form.submit

・app/views/postsディレクトリ内に、_select_form.html.slimファイルを作成して、プルダウンメニューのコードを丸ごと移植します。

6-2. 投稿一覧&検索結果一覧のパーシャル化

app/views/posts/_result.html.slim
- @posts.each do |post|
  p
    = link_to post.user.email, user_path(post.user.id)
    br
    = link_to post.title, post_path(post.id)
    br
    = post.created_at
    br
    - if user_signed_in?
      - if post.favorited_by?(current_user)
        = link_to "いいね: #{post.favorites.count}", post_favorites_path(post.id), method: :delete
      - else
        = link_to "いいね: #{post.favorites.count}", post_favorites_path(post.id), method: :post
    - else
      = post.favorites.count

・app/views/postsディレクトリ内に、_result.html.slimファイルを作成して、投稿一覧表示用のコードを丸ごと移植します。

6-3. それぞれのビューへレンダリング

app/views/posts/index.html.slim
h1 投稿一覧
 
= render 'select_form'
 
= render 'result'
app/views/posts/index.html.slim
h1 検索結果
 
= render 'select_form'
 
= render 'result'

以上で、完了です。

最後に

以上で、ソート機能実装の完了です!
正直、色んな部分で自信がありませんので、どこか間違っているところなどありましたらおっしゃって頂けるととても嬉しいです!

とっても参考にさせて頂いた記事

gem無しで検索機能を実装するには?複数テーブルにも対応!
【Rails】完全理解 formでセレクトボックスをつくるselectの使い方
select (ActionView::Helpers::FormOptionsHelper) - APIdock

23
41
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
41