コメントにユーザを追加(コントローラの修正): 小林研 Rails Tips (83)

このページの内容は以下のリポジトリに1日遅れで反映されます(記事執筆前に前日分をコミットしています)。

https://github.com/hkob/hkob_blog

はじめに

Rails Tips の 83 回目です。昨日モデルにユーザを追加したので、request spec を修正し、コントローラが正しく動作するように変更します。

Rails をはじめよう - Railsガイド

ビューの修正

article と同様にビューを修正していきます。まずは、articles/comments/_form.html.haml です。commenter を削り、user_idhidden_field に設定しました。

= form_with model: [@article, @comment] do |form|
  %table
    %thead
      %tr
        %th 項目
        %th 内容
    %tbody
      - %i[body status].each do |attr|
        %tr
          %td= form_label_and_error form, @comment, attr
          %td
            - case attr
            - when :status
              = form.select attr, Comment::VALID_STATUSES
            - else
              = form.text_area attr, rows: 10, cols: 80
      %tr
        %td{colspan: 2}
          = form.hidden_field :article_id
          = form.hidden_field :user_id
          = form.submit

また、articles/comments/_comment.html.hamlcommenter の代わりに user_name を追加しました。また、編集と削除は自分の所有物の時だけリンクが発生するようにしました。

- return if comment.archived?

- if comment == @comment
  = render "articles/comments/form"
- else
  - %i[user_name body].each do |attr|
    %p= t_ars Comment, attr
    %p= comment.send attr
  - if comment.owned_by? current_user
    %p= lotfp article_path(@article, comment_id: comment.id), title: "コメントを編集"
    %p= destroy_lotfp article_comment_path(@article, comment), Comment

comments_spec の修正

comments_spec の request spec を修正します。所有しないオブジェクトとして、not_mine を用意しました。また、can_delete のユーザである can_delete ユーザでログインしておきます。

RSpec.describe Articles::CommentsController, type: :request do
  let!(:comment) { comments :can_delete }
  let!(:object) { comment }
  let!(:article) { object.article }
  let!(:attrs) { object.attributes }
  let(:not_mine) { comments :comment1 }

  let(:return_path) { article_path(article) }
  context "login by can_delete" do
    user_login :can_delete

まず、コメントの追加の修正です。こちらは commenter がなくなったので、不正なパラメータを body に変更しただけです。

    describe "POST #create" do
      before { object.destroy }
      subject { -> { post article_comments_path(article), params: {comment: attrs} } }
      context "正しいパラメータに対して" do
        it_behaves_like "レスポンスコード確認", 302
        it_behaves_like "オブジェクトが1増えるか?", Comment
        it_behaves_like "リダイレクト確認"
        it_behaves_like "Notice メッセージ確認", "コメントを登録しました。"
      end

      context "不正なパラメータに対して" do
        before { attrs["body"] = "" }
        it_behaves_like "レスポンスコード確認", 422
        it_behaves_like "オブジェクト数が変化しない?", Comment
        it_behaves_like "Alert メッセージ確認", "コメントの登録に失敗しました。"
      end
    end

こちらのテストが通過しないのは、user_id が comment_params に入っていないためです。こちらを修正することで create のテストは通過します。

  def comment_params
    params.require(:comment).permit(:user_id, :body, :article_id, :status)
  end

次に update のテストを修正します。こちらは所有権の確認があるため、article の時と同様にテストは 3 パターンになります。また、commenter の修正だったテストは status の修正に変更しています。

    describe "PATCH #update" do
      subject { -> { patch article_comment_path(article, one), params: {comment: attrs} } }
      context "when owned object" do
        let(:one) { object }
        context "正しいパラメータに対して" do
          before { attrs["status"] = "private" }
          it_behaves_like "レスポンスコード確認", 302
          it_behaves_like "オブジェクト属性が変化した?", Comment, :status, "private"
          it_behaves_like "リダイレクト確認"
          it_behaves_like "Notice メッセージ確認", "コメントを更新しました。"
        end

        context "不正なパラメータに対して" do
          before { attrs["body"] = "" }
          it_behaves_like "レスポンスコード確認", 422
          it_behaves_like "オブジェクト属性が変化しない?", Comment, :body
          it_behaves_like "Alert メッセージ確認", "コメントの更新に失敗しました。"
        end
      end

      context "when not owned object" do
        let(:one) { not_mine }
        it_behaves_like "レスポンスコード確認", 302
        it_behaves_like "rootリダイレクト確認"
      end
    end

所有権の確認は article と同様に take_one で行います。これでテストが通過しました。

  def take_one
    @comment = object_from_params_id Comment
    redirect_to root_path, alert: I18n.t("errors.messages.not_owned") unless @comment.owned_by?(current_user)
  end

当然ながら destroy のテストも修正しておきます。こちらはすでに通過するはずです。

    describe "DELETE #destroy" do
      subject { -> { delete article_comment_path(article, one) } }
      context "when owned object" do
        let(:one) { object }
        it_behaves_like "レスポンスコード確認", 303
        it_behaves_like "オブジェクトが1減るか?", Comment
        it_behaves_like "リダイレクト確認"
        it_behaves_like "Notice メッセージ確認", "コメントを削除しました。"
      end

      context "when not owned object" do
        let(:one) { not_mine }
        it_behaves_like "レスポンスコード確認", 302
        it_behaves_like "rootリダイレクト確認"
      end
    end

これで全てのテストが通過したと思いましたが、昨日の can_delete article のユーザを変更したせいで、articles_spec のテストが失敗するようになっていました。articles_spec では cannot_delete ユーザでログインする必要がありました。こちらを修正したことで全てのテストが通過しました。

  context "when login by cannot_delete" do
    user_login :cannot_delete

おわりに

これでやりたかったテスト系は大体説明できたのではないかと思います。