Help us understand the problem. What is going on with this article?

【10日間でポートフォリオ作成に挑戦】7日目:検索機能〜いいね機能の実装

概要

今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程
取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)

全体通した取り組みの詳細については、前回までの記事をご参照ください。

【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装

今日一日の作業内容

ここからは、今日1日で取り組んだ作業内容をご説明します。

テストコードの修正

6日目でテストコードの実装を行いましたが、そのコードに対して、伊藤さん(@jnchito)からコメントでフィードバックを頂いたので、その修正を行いました。
(ちなみに、これでQiitaでの初コメント初アドバイスも、両方とも伊藤さんから頂いた事になります。なんとも有難い話:sob:

具体的なフィードバックの内容は、主に下記の3点です。

  • idに依存するコードは書かない
  • ドキュメントとしての読みやすさを意識する(I18nではなく、表示そのままの文言を使用)
  • letとlet!の適切な使い分け

詳しくは下記の動画で解説して下さっています。
Qiitaに載っていたRSpecのコードを勝手にコードレビューしてみた

なお、I18nについては、まだフロントのイメージが固まっていないので、一旦そのままにして、フロント実装後に、テストも表示の文言に書き換えようと思います。

かなり耳が痛いご指摘でしたが、おかげでRspecに対する理解が今まで以上に深まりました。
改めて、Qiitaに投稿して良かったと感じています!

検索機能の実装

検索機能の実装には、gemのransackを利用しています。

gem 'ransack'

また、どのページからでも検索出来る様に、ヘッダーに検索フォームを埋め込む事にしました。

その為、検索のロジックはapplication_controller.rbに記述しています。

controllers/application_controller.rb
before_action :set_search

def set_search
  @search = Post.ransack(params[:q])
  @posts = @search.result.page(params[:page]).per(10)
end

そして、application.html.hamlに、下記のコードを記述して、検索フォームを実装しました。

views/application.html.haml
= search_form_for @search, url: posts_path do |f|
  %dt= f.text_field :title_cont ,placeholder: t('common.message.search')
  %dd= f.submit t('common.button.search')

ルーティングはpostコントローラーのindexアクションに飛ぶ様に、`posts_path`を指定しています。
これでindexと同じビューを共有します。

なお、indexアクション内でも、postインスタンスを生成しているので、検索して既にインスタンスが存在する場合は、上書きされない様に、演算子をnilガードに変更しています。

controllers/posts_controller.rb
def index
  @posts ||= Post.page(params[:page]).per(10)
end

こうして出来上がったフォームは、こちらです。

beadd4d81632544a873c946b18e4c905.gif

ログイン・ログアウト両方表示されてしまってますが、フロントの実装を本格的に開始したら、ここも切り替えが出来る様にする予定です。

いいね機能の実装

続いて、記事に対する「いいね」機能を実装して行きます。

まずは、「いいね」を記録する為の、PostLikeのモデルとコントローラーを作成します。

rails g model PostLikes
rails g controller post_likes

一人のユーザーが同じ記事に何度もいいねが出来ない様に、バリデーションを設定します。

models/post_like.rb
class PostLike < ApplicationRecord
  belongs_to :user
  belongs_to :post
  validates_uniqueness_of :post_id, scope: :user_id
end

コントローラーでは、いいねを付けるアクションと、消すアクションの二つを定義します。

controller/post_likes_controller.rb
class PostLikesController < ApplicationController
  def create
    post_like = current_user.post_likes.build(post_id: params[:post_id])
    post_like.save!
    redirect_to "/posts/#{params[:post_id]}"
  end

  def destroy
    post_like = current_user.post_likes.find_by(post_id: params[:post_id])
    post_like.destroy!
    redirect_to "/posts/#{params[:post_id]}"
  end
end

ルーティングはpostにネストさせます。

config/routes.rb
  resources :posts do
    resources :post_comments, except: [:index, :show]
    resources :post_likes, only: [:create, :destroy]
  end

最後にビューにいいねのリンクを追加します。
ログインしているか?いいね済みか?で、表示が切り替わる様にしています。

views/posts/_show.html.haml
.content-like
  - if user_signed_in?
    - if @post_like.nil?
      = @post_likes.count
      = link_to t('common.button.like'), post_post_likes_path(@post), method: :post
    - else
      = @post_likes.count
      = link_to t('common.button.unlike'), post_post_like_path(@post, @post_like), method: :delete

これで一先ず完成です。

8bbfd4f4432b460af9114d73fcad4e4e.gif

今日の失敗

ここからは今日の失敗をまとめます。

いいね機能の実装方法に確信が持てない

※追記(5/4):コメントにて、伊藤さん(@jnchito)からアドバイスを頂いています。本当に有り難過ぎる:sob:

いいね機能については、先程のコードで一先ず狙い通りの動作をしてくれる様になりましたが、完全に我流で考えたコードなので、正直これが最適解では無いのではないか?という疑念を抱いています。

特に気になっているのが、下記のコードです。

redirect_to "/posts/#{params[:post_id]}"

これはいいねの処理が完了した後に、postコントローラーに戻して、再度showアクションを読み込ませる記述ですが、パスでは無く、下記の様に指定が出来ると考えています。

redirect_to controller: 'posts', action: 'show'

しかし、これで指定すると、下記の様なエラーが発生します。

ActionController::UrlGenerationError:
 No route matches {:action=>"show", :controller=>"posts", :post_id=>"1"}

ルーティングは以下の通り。

config/routes.rb
  resources :posts do
    resources :post_comments, except: [:index, :show]
    resources :post_likes, only: [:create, :destroy]
  end

結局原因が特定出来なかった為、直接パスを指定する方法で実装しています。
こちらについては、後日調査して、より効果的な実装方法が無いか探ります。

他にも、「いいねの有無で表示を切り分ける部分」や「いいねを作成・削除するコントローラーのロジック」など、もっと良い書き方がありそうなので、他の方の実装例も参照して、修正を加えて行きたい。

テストの抜け漏れ

現在実装されている機能は、単体テスト・統合テストともに記述したのですが、削除機能のテストコードが漏れていたりなど、テストの抜け漏れがいくつか見つかりました。

正直、テスト設計的な知見が現状無い為、開発がひと段落したら、そのあたりの知見も取り入れて行きたい。

明日の予定

  • 記事のストック機能(自身の記事も可)
  • 別ユーザーのフォロー機能
  • フロントサイドの実装

明日にフロント実装に入ることが出来れば、デプロイで大きく躓かない限り、GW中に最低限の仕様の実装は終えられると考えている。
後、3日間、なんとか乗りきろう!

※追記:八日目を投稿しました
【10日間でポートフォリオ作成に挑戦】8日目:記事ストック機能〜ユーザーフォロー機能の実装

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away