記事を1件表示する: 小林研 Rails Tips (57)

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

https://github.com/hkob/hkob_blog

はじめに

Rails Tips の 57 回目です。index による記事一覧が表示できたので、記事を1件表示する部分をテスト・実装します。

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

7.1 記事を1件表示する

こちらも先にテストを記述しましょう。object は後で削除に使えるように削除可能なオブジェクトである can_delete fixture を使います。ここではページタイトルにタイトルが表示されるので、その文言が含まれていることを確認します。

  let!(:article) { articles :can_delete }
  let!(:object) { article }

  #(中略 index のテスト)

  describe "GET #show" do
    subject { -> { get article_path(object) } }
    it_behaves_like "レスポンスコード確認", 200
    it_behaves_like "描画結果に文字列が含まれている?", %w[This is the second article]
  end

この結果、 undefined methodarticle_path'` というエラーになります。route.rb に index のものしか用意していないからです。ガイドでは show だけの routing を追加していますが、面倒なので 7.2 で紹介される resources を設定してしまいます。これによりテストは文字列が表示されていないというだけになります。

  resources :articles

まず、app/controllers/articles_controller.rb@article を取得します。ここでは、objects_from_params 他2件: 小林研 Rails Tips (19)object_from_params_id を使います。object_from_params_id については application_controller.rb に記載します。

  def show
    @article = object_from_params_id Article
  end

取得したコンテンツを app/views/articles/show.html.haml で描画します。

- content_for :title do
  - @page_title = @article.title

%p= @article.body

これでテストが通過しました。それでは、このページへのリンクを index の記事一覧に追加しましょう。ここでは、lotfp ヘルパー : 小林研 Rails Tips (38)で紹介した lotfp を使います。こちらはパスを書くだけでタイトルなどを自動的に設定してくれるものです。

        %td= lotfp article_path(article)

lotfp は本来権限に応じてリンクの可否を判定するのですが、今回は権限周りは実装していないので、権限周りを省略したものを準備します。なお、この中で controller_action_and_paramscontroller_and_action ヘルパー : 小林研 Rails Tips (32) に、link_title_from_caplink_title_from_path ヘルパー : 小林研 Rails Tips (35) に、その中で呼ばれている title_from_captitle_from_path ヘルパー : 小林研 Rails Tips (34) で紹介しているので、そちらから転記します。lotfpapplication_helper.rb に記載します。元々権限周りがあったので、テストはヘルパ自体の記述していませんでした(権限に関わる部分は request_spec でテストできるため)。

  # @param [String] path
  # @param [Symbol, nil] method
  # @return [ActiveSupport::SafeBuffer] リンクまたは文字列
  # @note lotfp -> link_or_title_from_path
  def lotfp(path, method = nil, cond: true, title: nil, option: {})
    cap = controller_action_and_params(path, method)
    # 権限周りはスキップ
    hp = true # controller.acp(privileges_from_cap(cap), cap)
    title ||= link_title_from_cap(cap)
    cond && hp ? link_to(title, path, {data: {turbo_method: method}}.merge(option)) : title
  end

application_helper_spec.rb のテストは今回の Article に合わせて書き換えてみました。controller_action_and_params については、ちょうど resources の routes のテストにもなっていますね。

require "rails_helper"

describe ApplicationHelper, type: :helper do
  subject { helper }

  describe "common test" do
    it_behaves_like "単一メソッド呼び出し" do
      let(:article) { articles :can_delete }
      let(:test_set) do
        {
          controller_action_and_params: [
            articles_path, {controller: "articles", action: "index"},
            article_path(article), {controller: "articles", action: "show", id: article.id.to_s},
            new_article_path, {controller: "articles", action: "new"},
            [articles_path, :post], {controller: "articles", action: "create"},
            edit_article_path(article), {controller: "articles", action: "edit", id: article.id.to_s},
            [article_path(article), :patch], {controller: "articles", action: "update", id: article.id.to_s},
            [article_path(article), :delete], {controller: "articles", action: "destroy", id: article.id.to_s},
          ],
          controller_and_action: [
            articles_path, %w[articles index],
            article_path(article), %w[articles show],
            new_article_path, %w[articles new],
            [articles_path, :post],  %w[articles create],
            edit_article_path(article),  %w[articles edit],
            [article_path(article), :patch],  %w[articles update],
            [article_path(article), :delete],  %w[articles destroy],
          ],
          key_from_cap: [
            [{controller: "articles", action: "index"}, :title], "記事一覧",
            [{controller: "articles", action: "show"}, :link_title], "表示",
          ],
          link_title_from_cap: [
            [{controller: "articles", action: "index"}], "記事一覧",
            [{controller: "articles", action: "show"}], "表示",
          ],
          link_title_from_path: [
            articles_path, "記事一覧",
            article_path(article), "表示",
          ],
          link_title_with_path: [
            articles_path, ["記事一覧", articles_path],
            article_path(article), ["表示", article_path(article)],
          ],
          title_from_cap: [
            [{controller: "articles", action: "index"}], "記事一覧",
            [{controller: "articles", action: "show"}], "記事詳細",
          ],
          title_from_path: [
            articles_path, "記事一覧",
            article_path(article), "記事詳細",
          ],
          t_ars: [
            [Article], "記事",
            [Article, :title], "タイトル",
            [Article, :body], "本文",
            [Article, %i[title body]], %w[タイトル 本文],
          ]
        }
      end
    end

    it_behaves_like "単一メソッド呼び出し(キーワード引数あり)" do
      let(:test_set) do
        {
          labels: [
            [Article, %i[title body]], {}, "<tr><th>タイトル</th><th>本文</th></tr>",
            [Article, %i[title]], {add_control: true}, "<tr><th>タイトル</th><th>制御</th></tr>",
          ],
        }
      end
    end
  end
end

このままだと show の翻訳文字列が設定されていないのでエラーになります。こちらも show のものを追加しました。

ja:
  articles:
    index:
      title: 記事一覧
    show:
      title: 記事詳細
      link_title: 表示

  common:
    control: 制御

application_helper.rb に該当するメソッドを追加していくとテストが全て通過しました。request_spec の描画でもテストが成功しています。

ArticlesController
  GET #index
    behaves like レスポンスコード確認
      レスポンスのステータスが 200 であること
    behaves like 描画結果に文字列が含まれている?
      レスポンスの文字列が「["記事一覧", "タイトル"]」を含むこと
  GET #show
    behaves like レスポンスコード確認
      レスポンスのステータスが 200 であること
    behaves like 描画結果に文字列が含まれている?
      レスポンスの文字列が「["This", "is", "the", "second", "article"]」を含むこと

おわりに

今回は show の中身よりも lotfp を実現するための application_helper の実装がメインになってしまいました。ただ、このおかげでこの先は lotfp だけでリンク描画ができるので楽になります。