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

【10日間でポートフォリオ作成に挑戦】8日目:記事ストック機能〜ユーザーフォロー機能の実装

概要

今回は、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にネストさせています。

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

モデルも全く一緒です。

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

コントローラーについても、遜色ありません。
(ちなみに、redirect_toのパスの指定方法については、先日に引き続き伊藤さん(@jnchitoからコメントでアドバイス頂いたので、その方法を採用しています。まさかこんなにアドバイス頂けると思っていなかったので、かなり驚いています!!有り難過ぎる:sob:

controller/post_stocks_controller.rb
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文で、表示を切り替える様にしています。

views/posts/show.html.haml
.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

そうして出来上がったものが、こちらです。

0c37b61ebbc236e51230b24d5c98a894.gif

ユーザーフォロー機能の実装

次にユーザーのフォロー機能を実装します。
下記のER図で示した様に、DB設計は行っています。

Screen Shot 2019-05-04 at 22.50.29.png

$ rails g model Relationships
$ rails g controller Relationships

カラムには外部キーとなるfollowing_idfollower_idをinteger型で設定します。

maigrate/create_relationships.rb
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で区別出来る様に、アソシエーションを記述します。

models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :following, class_name: "User"
  belongs_to :follower, class_name: "User"
end

親モデルのuserには、子モデルのrelationshipを、フォローするを側が参照するactive_relationshipと、フォローされる側が参照するpassive_relationshipsで区別出来る様に、アソシエーションを記述します。
それぞれ、followerfollower、どちらの外部きーを参照するのかも明記します。

また既にフォロー済みか判定するfollowed_by?メソッドも記述します。

models/user.rb
  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にネストさせて、フォローの作成・削除の他に、フォローしているユーザーと、フォローされているユーザーの情報を取得して一覧表示させる為のルートも設定しています。

config/routes.rb
  resources :users , only: [:index, :show] do
    resource :relationships, only: [:create, :destroy]
    get :post_stocks
    get :follows, on: :member
    get :followers, on: :member
  end

そして、フォローの記録を行うコントローラーについては、いいねやストックと、ほぼ同様の内容となります。

controllers/relationships_controller.rb
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

フォローボタンは、ユーザーの詳細画面に表示させる様にしています。

views/users/show.html.haml
.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

これで完成したものが、こちらです。

2a610e2af70ed1babb5f6c194e13df5f.gif

これで、必須としていた機能サーバーサイドの実装は概ね完了しました。

今日の失敗

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

IDが際限なく増える事を容認

いいね・ストック・フォロー機能は、いずれも簡単に取り消して、再度登録も出来る様になっています。
その内部での処理は「レコード作成→削除→新たにレコード作成→削除→・・・(ループ)」という流れになっています。

DBはレコードが削除されても、既に割り当てた事があるIDを使い回す事は原則無いので、これを繰り返すと、ID番号が非常に膨大な数になってしまい、それがパフォーマンスに何らかの影響を及ぼさないか?危惧しています。

繰り返しの登録→取り消しで、IDを際限なく増えない様にするには、statusカラムなどを設けて、そこで取り消されたか判定する事で、都度新しいレコードを生成される事を防げますが、不要なデータが大量に残る要因になるので、そちらの方がパフォーマンスへの悪影響があると考えています。

他に良い方法も見つからなかったので、一旦これは容認する事にしました。
正直、SQLなど土台の知識が不十分な為、この辺りをどこまで考慮すれば良いのかが、まだ判断難しい部分ではあります。

明日の予定

  • フロントサイドの実装
  • AWSへのデプロイ

明日にデプロイ出来れば、予定通りGW中に完成させられそうです。
残り2日間、なんとか完成させたいと思います。

※追記:九日目を投稿しました
【10日間でポートフォリオ作成に挑戦】9日目:フロントエンドの実装〜各種機能の修正

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