らくだ🐫にもできるRailsチュートリアル|13.2

13.2 マイクロポストを表示する

マイクロポストの表示予定図はこんな感じ RoRT本文参照です

ここでは、Twitterのような独立したマイクロポストのindexページは作らずに、図 13.4のモックアップに示したように、ユーザーのshowページで直接マイクロポストを表示させることにします。ユーザープロフィールにマイクロポストを表示させるため、最初に極めてシンプルなERbテンプレートを作成します。次に、10.3.2のサンプルデータ生成タスクにマイクロポストのサンプルを追加して、画面にサンプルデータが表示されるようにしてみます。

13.2.1 マイクロポストの描画

ユーザーのプロフィール画面 (show.html.erb) に、そのユーザーのマイクロポストを表示させたり、そのユーザーが投稿した総数も表示させたりしていく →一旦データベースをリセット&サンプルデータの再生成

$ rails db:migrate:reset
$ rails db:seed

そしてコントローラを生成 (いろいろ生成される中でここではviewのみ使う。コントローラは次の13.3から使用)

 $ rails generate controller Microposts
Running via Spring preloader in process 11058
      create  app/controllers/microposts_controller.rb
      invoke  erb
      create    app/views/microposts
      invoke  test_unit
      create    test/controllers/microposts_controller_test.rb
      invoke  helper
      create    app/helpers/microposts_helper.rb
      invoke    test_
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/microposts.coffee
      invoke    scss
      create      app/assets/stylesheets/microposts.scss

ユーザーごとにすべてのマイクロポストを描画

# _user.html.erbパーシャルを使って自動的に@users変数内のそれぞれのユーザーを出力していたユーザー一覧のコードを参考に
    <%= render @users %>
# _micropost.html.erbパーシャルを使ってマイクロポストのコレクションを表示しようとするとこう↓
    <%= render @microposts %>

ユーザー一覧と違いulタグではなくolタグを使っている →マイクロポストが特定の順序に依存しているため

1つのマイクロポストを表示するパーシャル

一つのマイクロポストを表示するためのパーシャル →/sample_app/app/views/microposts/に_micropost.html.erbファイルを生成


  • <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <%= link_to micropost.user.name, micropost.user %> <%= micropost.content %> Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  • time_ago_in_wordsとは何ぞ

    ここではtime_ago_in_wordsというヘルパーメソッドを使っています。これはメソッド名の表すとおりですが、「3分前に投稿」といった文字列を出力します。具体的な効果について13.2.2で説明します。

    ページネーションを使って表示

    一度にすべてのマイクロポストが表示されてしまうことを防ぐ為に ユーザー一覧ページと同じくページネーションを導入

    # ユーザー一覧ページで使ったページネーションのコード
    <%= will_paginate %>
    
    # 今回使うページネーションのコード
    <%= will_paginate @microposts %>
    上のコードは引数なしで動作していました。これはwill_paginateが、Usersコントローラのコンテキストにおいて、@usersインスタンス変数が存在していることを前提としているためです。

    ActiveRecord::Relationクラスな@usersインスタンス変数はUsersコントローラのコンテキスト(Usersコントローラに含まれる情報)なので、同じくUsersコントローラのコンテキスト(であるところのViewのテンプレート上)からwill_paginateを呼び出しているため(@usersが存在するという前提で)will_paginateがよしなにやってくれていた 今回はUsersコントローラのコンテキストから、異なるコンテキストのマイクロポストをページネーションしたいので (明示的に)@microposts変数をwill_paginateに渡す必要がある

    したがって、そのようなインスタンス変数をUsersコントローラのshowアクションで定義しなければなりません
    ・
    ・
    ・
      def show
        # @userにUserテーブルから(params[:id])のデータを取り出して代入
        @user = User.find(params[:id])
        # @micropostに @userのmicropostsのページネーションの指定ページ(params[:page])を代入
        @microposts = @user.microposts.paginate(page: params[:page])
    ・
    ・
    ・

    belongs_to/has_manyの関連付けを行っているので paginateメソッドでmicropostテーブルから必要な(user_idが@user.idと等しい)マイクロポストのページを引き出せる

    コンテキストとはなんぞ

    Usersコントローラのコンテキスト ↓ Usersコントローラの制御情報 ↓ Usersコントローラに含まれる各種アクションや設定など諸々

    コンテキストとは、プログラムの実行に必要な各種情報のことである。 「context」(コンテキスト)は、「文脈」、「前後関係」などと訳されるが、IT用語としては意味がイメージしづらく、単にコンテキストとある場合は、何らかの制御情報と考える方がわかりやすいことが多い。

    [blogcard url=”https://www.sophia-it.com/content/%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88”]

    マイクロポストの投稿数の表示

    関連付けをとおしてcountメソッドを呼び出せる

    user.microposts.count

    要素が出そろったので実装!

    <% provide(:title, @user.name) %>
    
    <% if @user.microposts.any? %>

    Microposts (<%= @user.microposts.count %>)

      <%= render @microposts %>
    <%= will_paginate @microposts %> <% end %>

    新しくなったユーザー詳細ページをブラウザで確認! しても、マイクロポストがないので表示が変わりません →次でマイクロポストのサンプルを追加する その前に演習!

    演習

    1. 7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.agoや6.months.agoを実行してみましょう。
    2. helper.time_ago_in_words(1.year.ago)と実行すると、どういった結果が返ってくるでしょうか?
    3. micropostsオブジェクトのクラスは何でしょうか? ヒント: リスト 13.23内のコードにあるように、まずはpaginateメソッド (引数はpage: nil) でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。
    >> helper.time_ago_in_words(3.weeks.ago)
    => "21日"
    >> helper.time_ago_in_words(6.months.ago)
    => "6ヶ月"
    >> helper.time_ago_in_words(1.year.ago)
    => "約1年"
    # 取ってくるユーザーは誰でもよい
    >> user = User.find(1)
      User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
    => #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-05-03 08:10:09", updated_at: "2020-05-03 08:10:09", password_digest: "$2a$10$/PI2i0b1VZTXANJl8YJ0U.WCtj3VdrqDGbpOkiXwWU/...", remember_digest: nil, admin: true, activation_digest: "$2a$10$lAYqQwkllJQAKs0445gLY.noAS2ByqtI/KgOVlfb5aV...", activated: true, activated_at: "2020-05-03 08:10:09", reset_digest: nil, reset_sent_at: nil>
    # mに userのmicropostsのページネーションの最初のページ(page: nil)を代入
    >> m = user.microposts.paginate(page: nil)
      Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["user_id", 1], ["LIMIT", 11], ["OFFSET", 0]]
    => #<ActiveRecord::AssociationRelation []>
    >> m.class
    => Micropost::ActiveRecord_AssociationRelation

    2の”約1年”が”1年”じゃないのは仕様だから!という事

    13.2.2 マイクロポストのサンプル

    マイクロポストのサンプルも追加して表示を確認できるようにしたい!

    すべてのユーザーにマイクロポストを追加しようとすると時間が掛かり過ぎるので、takeメソッドを使って最初の6人だけに追加します。(中略)この6人については、1ページの表示限界数 (30) を越えさせるために、それぞれ50個分のマイクロポストを追加するようにしています。
    User.order(:created_at).take(6)

    orderメソッドとは何ぞ

    [blogcard url=”https://pikawaka.com/rails/order”] 上の例は

    # Userモデルを created_atの順に並び替えて 上から6個を(配列として)返す
    User.order(:created_at).take(6)

    Faker gemでサンプルを追加

    Faker gemのLorem.sentenceというメソッドを使ってsampleを追加する。

    ・
    ・
    ・
    # マイクロポストのサンプルを追加
    # usersに Userモデルを created_atの順に並び替えて 上から(6個を)配列として 代入
    users = User.order(:created_at).take(6)
    # 50回繰り返す
    50.times do
      # contetに Faker::Loremで作ったサンプルを代入(Faker::Loremから文章を5個取り出す)
      content = Faker::Lorem.sentence(5)
      # usersを順番に取り出してブロック内を実行
      # 取り出した要素をuserに代入 userに紐づいたmicropostを作成(content属性に変数contentの値)
      users.each { |user| user.microposts.create!(content: content) }
    end

    「user1のマイクロポスト×50、user2のマイクロポスト×50・・・」ではなく 「user1のマイクロポスト1・・・user6のマイクロポスト1、user1のマイクロポスト2・・・user6のマイクロポスト2」といった繰り返しになっている →タイムラインを実装するときの見栄えが良い

    # DBをリセットからの再生成
    $ rails db:migrate:reset
    $ rails db:seed
    
    # サーバー再起動
    $ rails s
    /* スタイルを整える */
    /* microposts */
    
    .microposts {
      list-style: none;
      padding: 0;
      li {
        padding: 10px 0;
        border-top: 1px solid #e8e8e8;
      }
      .user {
        margin-top: 5em;
        padding-top: 0;
      }
      .content {
        display: block;
        margin-left: 60px;
        img {
          display: block;
          padding: 5px 0;
        }
      }
      .timestamp {
        color: $gray-light;
        display: block;
        margin-left: 60px;
      }
      .gravatar {
        float: left;
        margin-right: 10px;
        margin-top: 5px;
      }
    }
    
    aside {
      textarea {
        height: 100px;
        margin-bottom: 5px;
      }
    }
    
    span.picture {
      margin-top: 10px;
      input {
        border: 0;
      }
    }

    演習

    1. (1..10).to_a.take(6)というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。
    2. 先ほどの演習にあったto_aメソッドの部分は本当に必要でしょうか? 確かめてみてください。
    3. Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に大学名や電話番号、Hipster IpsumやChuck Norris facts (参考: チャック・ノリスの真実) を画面に出力してみましょう。(訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力するfaker-okinawaもあります。ぜひ遊んでみてください。)
    # 1-10を 配列にして 先頭から6個を取り出す
    >> (1..10).to_a.take(6)
    => [1, 2, 3, 4, 5, 6]
    
    # takeが「要素を配列にして先頭から引数個返す」ためto_aは不要
    >> (1..10).take(6)
    => [1, 2, 3, 4, 5, 6]
    >> Faker::University.name
    => "太田医科大学"
    >> Faker::PhoneNumber.phone_number
    => "081-529-2436"
    >> Faker::Hipster.sentence
    => "Beard kombucha retro fingerstache carry."
    >> Faker::ChuckNorris.fact
    => "When Chuck Norris' code fails to compile the compiler apologises."
    # 沖縄のやつは別途GEMが必要なので割愛

    13.2.3 プロフィール画面のマイクロポストをテストする

    プロフィール画面が正しく描画されるかのテストはアカウントの有効化テストで行っている →ここではプロフィール画面で表示されるマイクロポストに対しての統合テストを書いていく

    テストの準備

    # 統合テストを作成
     $ rails generate integration_test users_profile
    Running via Spring preloader in process 10674
          invoke  test_unit
          create    test/integration/users_profile_test.rb
    プロフィール画面におけるマイクロポストをテストするためには、ユーザーに紐付いたマイクロポストのテスト用データが必要になります。
    # users.ymlにあるユーザー名
    michael:
      name: Michael Example
      email: michael@example.com
      .
      .
      .
    
    # microposts.ymlのデータにユーザーを紐付ける
    orange:
      content: "I just ate an orange!"
      created_at: <%= 10.minutes.ago %>
      user: michael

    userにuserのnameの値を渡すと、Railsがfixtureファイル内の対応するユーザーを探して、見つかったらマイクロポストに関連付けてくれる スゴイ! また、埋め込みRubyを使ってテストデータを追加する

    orange:
      content: "I just ate an orange!"
      created_at: <%= 10.minutes.ago %>
      user: michael
    
    tau_manifesto:
      content: "Check out the @tauday site by @mhartl: http://tauday.com"
      created_at: <%= 3.years.ago %>
      user: michael
    
    cat_video:
      content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
      created_at: <%= 2.hours.ago %>
      user: michael
    
    most_recent:
      content: "Writing a short test"
      created_at: <%= Time.zone.now %>
      user: michael
    
    # user: michaelのマイクロポストを30個
    <% 30.times do |n| %>
    micropost_<%= n %>:
      content: <%= Faker::Lorem.sentence(5) %>
      created_at: <%= 42.days.ago %>
      user: michael
    <% end %>

    テストを実装

    今回のテストでは、プロフィール画面にアクセスした後に、ページタイトルとユーザー名、Gravatar、マイクロポストの投稿数、そしてページ分割されたマイクロポスト、といった順でテストしていきます。
    require 'test_helper'
    
    class UsersProfileTest < ActionDispatch::IntegrationTest
      # ApplicationHelperを読み込む→full_titleヘルパーが利用できる
      include ApplicationHelper
    
      def setup
        @user = users(:michael)
      end
    
      test "profile display" do
        # user_path(@user)にgetのリクエスト
        get user_path(@user)
        # テンプレートが描画される 'users/show
        assert_template 'users/show'
        # 特定のHTMLタグが存在する→ title, full_title(@user.name)→@user.name | Ruby on Rails Tutorial Sample App
        assert_select 'title', full_title(@user.name)
        # 特定のHTMLタグが存在する→ h1, text: @user.name
        assert_select 'h1', text: @user.name
        # 特定のHTMLタグが存在する→ h1のタグに含まれるimg.gravatar
        assert_select 'h1>img.gravatar'
        # 描画されたページに @userのマイクロポストのcountを文字列にしたものが含まれる 
        assert_match @user.microposts.count.to_s, response.body
        # 特定のHTMLタグが存在する→ class = "pagination"を持つdiv
        assert_select 'div.pagination'
        # @user.micropostsのページネーションの1ページ目の配列を1個ずつ取り出してmicropostに代入
        @user.microposts.paginate(page: 1).each do |micropost|
          # このページにmicropost.contentが含まれる
          assert_match micropost.content, response.body
        end
      end
    end

    response.bodyとはなんぞ

    前にも少し書いてましたが改めて。

    response.bodyにはそのページの完全なHTMLが含まれています (HTMLのbodyタグだけではありません)

    bodyの内容だけではなくてページ内のすべてのHTMLが含まれるという事

    演習

    1. リスト 13.28にある2つの'h1'のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが green から redに変わることを確認してみてください。
    2. リスト 13.28にあるテストを変更して、will_paginateが1度のみ表示されていることをテストしてみましょう。ヒント: 表 5.2を参考にしてください。

    1. 動作確認のみにて省略

    ・
    ・
    ・
        # 描画されたページに @userのマイクロポストのcountを文字列にしたものが含まれる 
        assert_match @user.microposts.count.to_s, response.body
        # 特定のHTMLタグが存在する→ class = "pagination"を持つdivが1個
        assert_select 'div.pagination', count: 1
        # @user.micropostsのページネーションの1ページ目の配列を1個ずつ取り出してmicropostに代入
        @user.microposts.paginate(page: 1).each do |micropost|
          # このページにmicropost.contentが含まれる
          assert_match micropost.content, response.body
        end
      end
    end

    まとめとか感想

    細かいところに?!っとなりつつ 全体はスムーズに進められた、と、思います! 見た目が整っていくの楽しい✨

    らくだ🐫にもできるRailsチュートリアルとは

    「ド」が付く素人のらくだ🐫が勉強するRailsチュートリアルの学習記録です。 自分用に記録していますが、お役に立つことがあれば幸いです。

    調べたとはいえらくだ🐫なりの解釈や説明が含まれます。間違っている部分もあるかと思います。そんな所は教えて頂けますと幸いなのですが、このブログにはコメント機能がありません💧お手数おかけしますがTwitterなどでご連絡いただければ幸いです

    タグ: Railsチュートリアル、らくだにも出来るRailsチュートリアル