2019/05/03
今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程
を
取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)
全体通した取り組みの詳細については、前回までの記事をご参照ください。
【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装
ここからは、今日1日で取り組んだ作業内容をご説明します。
6日目でテストコードの実装を行いましたが、そのコードに対して、伊藤さん(@jnchito)からコメントでフィードバックを頂いたので、その修正を行いました。
(ちなみに、これでQiitaでの初コメントも初アドバイスも、両方とも伊藤さんから頂いた事になります。なんとも有難い話)
具体的なフィードバックの内容は、主に下記の3点です。
詳しくは下記の動画で解説して下さっています。
Qiitaに載っていたRSpecのコードを勝手にコードレビューしてみた
なお、I18nについては、まだフロントのイメージが固まっていないので、一旦そのままにして、フロント実装後に、テストも表示の文言に書き換えようと思います。
かなり耳が痛いご指摘でしたが、おかげでRspecに対する理解が今まで以上に深まりました。
改めて、Qiitaに投稿して良かったと感じています!
検索機能の実装には、gemのransack
を利用しています。
gem 'ransack'
また、どのページからでも検索出来る様に、ヘッダーに検索フォームを埋め込む事にしました。
その為、検索のロジックは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
に、下記のコードを記述して、検索フォームを実装しました。
= 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ガードに変更しています。
def index
@posts ||= Post.page(params[:page]).per(10)
end
こうして出来上がったフォームは、こちらです。
ログイン・ログアウト両方表示されてしまってますが、フロントの実装を本格的に開始したら、ここも切り替えが出来る様にする予定です。
続いて、記事に対する「いいね」機能を実装して行きます。
まずは、「いいね」を記録する為の、PostLikeのモデルとコントローラーを作成します。
rails g model PostLikes
rails g controller post_likes
一人のユーザーが同じ記事に何度もいいねが出来ない様に、バリデーションを設定します。
class PostLike < ApplicationRecord
belongs_to :user
belongs_to :post
validates_uniqueness_of :post_id, scope: :user_id
end
コントローラーでは、いいねを付けるアクションと、消すアクションの二つを定義します。
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にネストさせます。
resources :posts do
resources :post_comments, except: [:index, :show]
resources :post_likes, only: [:create, :destroy]
end
最後にビューにいいねのリンクを追加します。
ログインしているか?いいね済みか?で、表示が切り替わる様にしています。
.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
これで一先ず完成です。
ここからは今日の失敗をまとめます。
※追記(5/4):コメントにて、伊藤さん(@jnchito)からアドバイスを頂いています。本当に有り難過ぎる
いいね機能については、先程のコードで一先ず狙い通りの動作をしてくれる様になりましたが、完全に我流で考えたコードなので、正直これが最適解では無いのではないか?という疑念を抱いています。
特に気になっているのが、下記のコードです。
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"}
ルーティングは以下の通り。
resources :posts do
resources :post_comments, except: [:index, :show]
resources :post_likes, only: [:create, :destroy]
end
結局原因が特定出来なかった為、直接パスを指定する方法で実装しています。
こちらについては、後日調査して、より効果的な実装方法が無いか探ります。
他にも、「いいねの有無で表示を切り分ける部分」や「いいねを作成・削除するコントローラーのロジック」など、もっと良い書き方がありそうなので、他の方の実装例も参照して、修正を加えて行きたい。
現在実装されている機能は、単体テスト・統合テストともに記述したのですが、削除機能のテストコードが漏れていたりなど、テストの抜け漏れがいくつか見つかりました。
正直、テスト設計的な知見が現状無い為、開発がひと段落したら、そのあたりの知見も取り入れて行きたい。
明日にフロント実装に入ることが出来れば、デプロイで大きく躓かない限り、GW中に最低限の仕様の実装は終えられると考えている。
後、3日間、なんとか乗りきろう!
※追記:八日目を投稿しました
【10日間でポートフォリオ作成に挑戦】8日目:記事ストック機能〜ユーザーフォロー機能の実装