2020-02-11に更新

未回答のものを絞り込む機能を追加する

前回作った受け取った質問一覧ページは未回答のものも回答済みのものも表示されていて未回答のものを探しにくいため、未回答の質問を絞り込む機能を作成します。

アクションを検索に対応

まずはアクション内の絞り込みの部分を調整します。URLにno_answer=1というパラメータが付いた場合だけ未回答の絞り込みを行うようにします。

検索条件は質問に紐づく回答が存在するかになります。SQLでいうとquestionsに紐づくanswersが存在しないもの、ということになります。具体的にはLaravelのQuestionControllerのindexメソッドを下記のように変更しました。

    public function index(Request $request)
    {
        $query = Question::with('answer')
            ->where('received_user_id', Auth::id())
            ->orderByDesc('id');

        if ($request->input('no_answer')) {
            $query->whereDoesntHave('Answer');
        }

        $questions = $query->paginate();
        return view('question.index', compact('questions'));
    }

これで例えばhttp://localhost:8000/questions?no_answer=1のようなURLにアクセスすると未回答のものだけを表示できるようになりました。

前回Debugbarを導入して、実際に発行されているSQLを簡単に見ることができるようになったため見てみると、下記のようなSQLが発行されるようになりました(整形及び一部省略しています)

select
    *
from
    `questions`
where
    `received_user_id` = 1
and not exists(
        select
            *
        from
            `answers`
        where
            `questions`.`id` = `answers`.`question_id`
    )
order by
    `id` desc

ページ分けに対応

このままだと、ページ分けされた際に別ページのリンクをクリックした際にno_answer=1のパラメータが解除されてしまいます。そのため、パラメータを維持できるようにページネーションの表示を下記のように変更します。

    {{ $questions->appends(request()->only(['no_answer']))->links() }}

このようにlinksメソッドを呼ぶ前にappendsを挟んで追加パラメータを指定することで、ページネーションのリンクにも追加のパラメータが維持されるようになります。

ちなみに$query->paginate(2)の様に表示されるデータより少ない数のlimitを指定すればデータを増やさなくても確認できます。

実際に切り替えられるようにする

あとは実際にno_answer付きと無しのURLを切り替えられるようにしておきます。テンプレートに下記のような記述を追記しました。

  <nav class="nav justify-content-center m-3">
    <a class="nav-link @if (!request()->input('no_answer')) active @endif" href="{{ url('questions') }}">
      すべて
    </a>
    <a class="nav-link @if (request()->input('no_answer')) active @endif" href="{{ url('questions?no_answer=1') }}">
      未回答のみ
    </a>
  </nav>

Bootstrapの下記のコンポーネントを利用しています。

Navs · Bootstrap

ちなみにこの横並びの表記にはFlexboxが利用されています。センタリングしておきたかったのでnavのクラスにjustify-content-centerを追加しています。

Justify content - Flex · Bootstrap

SQLの調整

これで一通り対応はできました。ただしこのサービスの場合、質問と回答のデータはメインになる、且つ一番増える可能性が高いため、上記で実装したようにリレーションを利用しての検索は後々重くなりすぎて問題になる可能性があります。そのため一旦ちょっと軽量化のための対策を行います。

動作としてはすでに出来ており、必須ではありませんので読み飛ばしても問題ありません。

具体的な対応方法として、リレーションして判断させるのではなく、回答がついた際に質問データ側に回答済みフラグを付けておくようにします。そのようにすることで質問データのみを使ったSQLで機能を実現できるようになります。

マイグレーションを作成

まずはその回答済みフラグをquestionsテーブルに追加するためのマイグレーションを追加します。

php artisan make:migration add_has_answer_to_questions --table questions

下記のようなマイグレーションを書きます。has_answerというカラムを追加します。

    public function up()
    {
        Schema::table('questions', function (Blueprint $table) {
            $table->boolean('has_answer')->default(false)->after('body');
        });
    }

    public function down()
    {
        Schema::table('questions', function (Blueprint $table) {
            $table->dropColumn('has_answer');
        });
    }

これを実行すればDB側の調整は完了です。あとは実際の処理を入れていきます。

回答された際に回答済みフラグを更新するようにする

AnswerControllerのstoreアクションの回答データ登録後に下記の処理を追加しておきます。

        $question->has_answer = true;
        $question->save();

問題なければ回答時にhas_answerフラグがオンになります。(※answersテーブルの作成マイグレーションの外部キー作成が間違っていたので修正済みです。質問に回答する機能を作る

質問に回答する機能を作る

検索クエリを変更する

先程作成した回答済みのみを抽出するSQLをフラグを使ったシンプルな形に変更します。さきほど実装したwhereDoesntHaveを変更します。

        if ($request->input('no_answer')) {
            $query->where('has_answer', true);
        }

これで一通り完了です。動作確認しておきましょう。クエリも下記のようなシンプルな形になりました。

select * from `questions` where `received_user_id` = 1 and `has_answer` = 1 order by `id` desc

まとめ

これで未回答のものだけで絞り込みができるようになりました。

次はナビゲーションなどがまだ未整備で使いづらいためそのあたりを調整していきたいと思います。

ツイッターでシェア
みんなに共有、忘れないようにメモ

view_list [連載] Laravelで質問箱みたいなサービスを作る
第5回 質問に回答する機能を作る
第6回 Twitterシェア用のOGPを作成する
第7回 受け取った質問一覧ページを作る
第8回 LaravelのDebugbarを導入する
第9回 未回答のものを絞り込む機能を追加する

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント