0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フォロー、タイムライン機能 (実務未経験状態でLaravelを使用した共同開発 タスク6)

Last updated at Posted at 2025-05-06

0.背景

本記事は、記事「実務未経験状態でLaravelを使用した共同開発」で取り組んだタスクのうち、フォロー、タイムライン機能についてまとめたもの

1.仕様内容

・自身以外のユーザ詳細ページからフォロー可能
・フォロー中かどうかがひと目で分かるUI
・各ユーザの投稿をタイムラインタブへ
・各ユーザのフォローリスト/フォロワーリストを表示
・各リストにページネーション(10件ごと)

UI(デプロイしたもの)

スクリーンショット (24).png
スクリーンショット (22).png
スクリーンショット (23).png
スクリーンショット (21).png

2.タスクの見積もり

期間

・14日

理由

・フォロー、フォロワーの設計のイメージが出来なかった
・上記に加えて未知のタスクのためエラー、イレギュラー発生及びレビュー、修正期間を想定

実績

・仕様作成完了 4日
・PR完了 4日
・PRレビュー修正 5日
・マージ完了 6日

3.実装

①Routing
・web.php
・認証済ユーザのみ可能な処理

Route::group(['middleware' => 'auth'], function () {

・上記の(['middleware' => 'auth'])下に記載
・フォロー、フォロー解除処理

Route::post('follow/{id}','FollowsController@follow')->name('user.follow'); // フォロー処理
Route::delete('unfollow/{id}','FollowsController@unfollow')->name('user.unfollow'); // フォロー解除処理

・ユーザ認証不要の処理

Route::prefix('/users')->group(function(){

・上記のprefix('/user')下に記載
・フォロー、フォロワーリスト閲覧処理

Route::get('following/{id}','FollowsController@followingList')->name('list.following'); // フォローリスト表示
Route::get('follower/{id}','FollowsController@FollowerList')->name('list.follower'); // フォロワーリスト表示

②Model
・User.php
・フォロー、フォロワーのリレーション(多対多)
・フォロー、フォロー解除、フォローチェックのメソッドを記載(コントローラで呼び出し)

    // フォローリレーション(フォローしているユーザを取得)
    public function following()
    {
        return $this->belongsToMany(User::class, 'follows', 'follower_id', 'following_id');
    }

    // フォロワーリレーション(フォローされているユーザを取得)
    public function followers()
    {
        return $this->belongsToMany(User::class, 'follows', 'following_id', 'follower_id');
    }

    // フォローチェック
    public function isFollowing($userId)
    {
        return $this->following()->where('following_id', $userId)->exists();
    }

    // フォローメソッド
    public function follow($userId)
    {
        //自分をフォローした場合処理中断
        if(Auth::id() === $userId){
            return false;
        }

        //存在しないユーザをフォローした場合処理中断
        if(!User::find($userId)){
            return false;
        }

        if(!$this->isFollowing($userId)){
            $this->following()->attach($userId);
            return true;
        }
    }

    // フォロー解除メソッド
    public function unFollow($userId)
    {
        if($this->isFollowing($userId)){
            $this->following()->detach($userId);
            return true;
        }
    }

③Migration
・create_Follows_Tableを作成

 public function up()
    {
        Schema::create('follows', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('follower_id')->unsigned()->index();
            $table->bigInteger('following_id')->unsigned()->index();
            $table->timestamps();

            //外部キー制約
            $table->foreign('follower_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('following_id')->references('id')->on('users')->onDelete('cascade');
            $table->unique(['follower_id', 'following_id']);
        });
    }

④Controller
・FolloesController.php
・フォロー処理
・フォロー解除処理
・フォローリスト表示
・フォロワーリスト表示

    // フォロー処理
    public function follow($id)
    {
        Auth::user()->follow($id);

        return back();
    }

    // フォロー解除処理
    public function unFollow($id)
    {
        Auth::user()->unFollow($id);

        return back();
    }

    // フォローリスト表示
    public function followingList($id)
    {
        $user = User::findOrFail($id);
        $following = $user->following()->paginate(10);

        return view('users.follows.following', compact('user', 'following'));
    }

    // フォロワーリスト表示
    public function followerList($id)
    {
        $user = User::findOrFail($id);
        $followers = $user->follower()->paginate(10);

        return view('users.follows.followers', compact('user', 'followers'));
    }

⑤View
・users/follows/following.blade.php
・フォローリスト取得

@if ($user->following->isEmpty())
    <p class="text-muted">フォローしているユーザーがいません</p>
@else
    <ul>
        @foreach ($user->following as $follow)
            <li class="d-flex justify-content-start my-4">
                <img class="mr-2 rounded-circle" src="{{ Gravatar::src($follow->email, 50) }}" alt="ユーザのアバター画像">
                <a class="mx-3" href="{{ route('user.show', ['id' => $follow->id]) }}">{{ $follow->name }}</a>
            </li>
        @endforeach
    </ul>
@endif

・users/follows/followers.php
・フォロワーリスト取得

@if ($user->followers->isEmpty())
    <p class="text-muted">フォロワーがいません</p>
@else
    <ul>
        @foreach ($user->followers as $follow)
            <li class="d-flex justify-content-start my-4">
                <img class="mr-2 rounded-circle" src="{{ Gravatar::src($follow->email, 50) }}" alt="ユーザのアバター画像">
                <a class="mx-3" href="{{ route('user.show', ['id' => $follow->id]) }}">{{ $follow->name }}</a>
            </li>
        @endforeach
    </ul>
@endif

・users/detail.blade.php
・ナビタブ部分に各bladeファイルをinclude
・タイムライン部分は他メンバータスクのposts/postsをinclude

    <section class="col-md-8">
        <div class="card text-center">
            <div class="card-header bg-white">
                <nav>
                    <div class="nav nav-tabs" id="nav-tab" role="tablist">
                        <button class="nav-link active bg-white text-primary" id="nav-post-tab" data-bs-toggle="tab" data-bs-target="#nav-post" type="button" role="tab" aria-controls="nav-post" aria-selected="true">タイムライン</button>
                        <button class="nav-link bg-white text-primary" id="nav-follow-tab" data-bs-toggle="tab" data-bs-target="#nav-follow" type="button" role="tab" aria-controls="nav-follow" aria-selected="false">フォロー</button>
                        <button class="nav-link bg-white text-primary" id="nav-follower-tab" data-bs-toggle="tab" data-bs-target="#nav-follower" type="button" role="tab" aria-controls="nav-follower" aria-selected="false">フォロワー</button>
                        <button class="nav-link bg-white text-primary" id="nav-favorite-tab" data-bs-toggle="tab" data-bs-target="#nav-favorite" type="button" role="tab" aria-controls="nav-favorite" aria-selected="false">お気に入り</button>
                    </div>
                </nav>
                <div class="tab-content mt-3" id="nav-tabContent">
                    <div class="tab-pane fade show active" id="nav-post" role="tabpanel" aria-labelledby="nav-post-tab" tabindex="0"> @include('posts.posts', ['posts' => $posts])</div>
                    <div class="tab-pane fade" id="nav-follow" role="tabpanel" aria-labelledby="nav-follow-tab" tabindex="0"> @include('users.follows.following', ['id' => $user->id])</div>
                    <div class="tab-pane fade" id="nav-follower" role="tabpanel" aria-labelledby="nav-follower-tab" tabindex="0">@include('users.follows.followers', ['id' => $user->id])</div>
                    <div class="tab-pane fade text-secondary" id="nav-favorite" role="tabpanel" aria-labelledby="nav-favorite-tab" tabindex="0">to be continued</div>
                </div>
            </div>
        </div>
    </section>

4.テスト実施

・フォロー/フォロー解除時のUIおよびDBの変化を確認(followsテーブルの確認)
・各ナビタブ表示内容が正しいか確認
・ページネーションが正しく動作するかを確認(1ページ10件)

5.PRレビュー

・インデント不備指摘(@if@endifの間)
 →自動生成で対応不可
 →自己確認及びaiにインデント確認を依頼し以後の不備対応

6.本実装に関して

・可読性、保守性:フォロー、フォロー解除、フォローチェックのメソッドをControllerかModelのどちらに記載するべきか悩んだ
 →結果として、ドキュメントの「Controllerはロジックを持たずシンプルにすべき」という方針に従い、Modelに集約する形で実装

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?