2019/05/04
今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程
を取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)
全体通した取り組みの詳細については、前回までの記事をご参照ください。
【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装
【10日間でポートフォリオ作成に挑戦】7日目:検索機能〜いいね機能の実装
【10日間でポートフォリオ作成に挑戦】8日目:記事ストック機能〜ユーザーフォロー機能の実装
ここからは、今日1日で取り組んだ作業内容をご説明します。
まず、記事のストック機能を実装していきます。
中間テーブルがPostLike
からPostStock
に切り替わるだけなので、サーバーサイドのロジックは、PostLikeとほぼ同様の内容となります。
$ rails g model PostStocks
$ rails g controller PostStocks
ルーティングは、PostLike同様に、Postにネストさせています。
resources :posts do
resources :post_comments, except: [:index, :show]
resources :post_likes, only: [:create, :destroy]
resources :post_stocks, only: [:create, :destroy]
end
モデルも全く一緒です。
class PostStock < ApplicationRecord
belongs_to :user
belongs_to :post
validates_uniqueness_of :post_id, scope: :user_id
end
コントローラーについても、遜色ありません。
(ちなみに、redirect_to
のパスの指定方法については、先日に引き続き伊藤さん(@jnchito)からコメントでアドバイス頂いたので、その方法を採用しています。まさかこんなにアドバイス頂けると思っていなかったので、かなり驚いています!!有り難過ぎる)
class PostStocksController < ApplicationController
def create
post_stock = current_user.post_stocks.build(post_id: params[:post_id])
post_stock.save!
redirect_to post_stock.post
end
def destroy
post_stock = current_user.post_stocks.find_by(post_id: params[:post_id])
post_stock.destroy!
redirect_to post_stock.post
end
end
ストックボタンは、記事の詳細画面で表示させますが、非ログイン時には表示しない様に、devise
のヘルパーであるuser_signed_in?
とIF文で、表示を切り替える様にしています。
.content-stock
- if user_signed_in?
- if @post_stock.nil?
= link_to t('common.button.stock'), post_post_stocks_path(@post), method: :post
- else
= link_to t('common.button.unstock'), post_post_stock_path(@post, @post_stock), method: :delete
そうして出来上がったものが、こちらです。
次にユーザーのフォロー機能を実装します。
下記のER図で示した様に、DB設計は行っています。
$ rails g model Relationships
$ rails g controller Relationships
カラムには外部キーとなるfollowing_id
とfollower_id
をinteger型で設定します。
class CreateRelationships < ActiveRecord::Migration[5.2]
def change
create_table :relationships do |t|
t.integer :following_id, null: false
t.integer :follower_id, null: false
t.timestamps
end
end
end
子モデルのrelationship
には、親モデルのuser
を、フォローするユーザーのfollowing
と、フォローされるユーザーのfollower
で区別出来る様に、アソシエーションを記述します。
class Relationship < ApplicationRecord
belongs_to :following, class_name: "User"
belongs_to :follower, class_name: "User"
end
親モデルのuser
には、子モデルのrelationship
を、フォローするを側が参照するactive_relationship
と、フォローされる側が参照するpassive_relationships
で区別出来る様に、アソシエーションを記述します。
それぞれ、follower
とfollower
、どちらの外部きーを参照するのかも明記します。
また既にフォロー済みか判定するfollowed_by?
メソッドも記述します。
has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
has_many :followings, through: :active_relationships, source: :follower
has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
has_many :followers, through: :passive_relationships, source: :follower
def followed_by?(user)
passive_relationships.find_by(following_id: user.id).present?
end
ルーティングは、usersにネストさせて、フォローの作成・削除の他に、フォローしているユーザーと、フォローされているユーザーの情報を取得して一覧表示させる為のルートも設定しています。
resources :users , only: [:index, :show] do
resource :relationships, only: [:create, :destroy]
get :post_stocks
get :follows, on: :member
get :followers, on: :member
end
そして、フォローの記録を行うコントローラーについては、いいねやストックと、ほぼ同様の内容となります。
class RelationshipsController < ApplicationController
def create
follow = current_user.active_relationships.build(follower_id: params[:user_id])
follow.save
redirect_to user_path(follow.following_id)
end
def destroy
follow = current_user.active_relationships.find_by(follower_id: params[:user_id])
follow.destroy
redirect_to user_path(follow.following_id)
end
end
フォローボタンは、ユーザーの詳細画面に表示させる様にしています。
.content-follow
- if user_signed_in?
- if @user.followed_by?(current_user)
= link_to t('common.button.unfollow'), user_relationships_path(@user), method: :delete
- else
= link_to t('common.button.follow'), user_relationships_path(@user), method: :post
これで完成したものが、こちらです。
これで、必須としていた機能サーバーサイドの実装は概ね完了しました。
ここからは今日の失敗をまとめます。
いいね・ストック・フォロー機能は、いずれも簡単に取り消して、再度登録も出来る様になっています。
その内部での処理は「レコード作成→削除→新たにレコード作成→削除→・・・(ループ)」という流れになっています。
DBはレコードが削除されても、既に割り当てた事があるIDを使い回す事は原則無いので、これを繰り返すと、ID番号が非常に膨大な数になってしまい、それがパフォーマンスに何らかの影響を及ぼさないか?危惧しています。
繰り返しの登録→取り消しで、IDを際限なく増えない様にするには、statusカラムなどを設けて、そこで取り消されたか判定する事で、都度新しいレコードを生成される事を防げますが、不要なデータが大量に残る要因になるので、そちらの方がパフォーマンスへの悪影響があると考えています。
他に良い方法も見つからなかったので、一旦これは容認する事にしました。
正直、SQLなど土台の知識が不十分な為、この辺りをどこまで考慮すれば良いのかが、まだ判断難しい部分ではあります。
明日にデプロイ出来れば、予定通りGW中に完成させられそうです。
残り2日間、なんとか完成させたいと思います。
※追記:九日目を投稿しました
【10日間でポートフォリオ作成に挑戦】9日目:フロントエンドの実装〜各種機能の修正