1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

コーディング未経験のPO/PdMのためのRails on Dockerハンズオン、Rails on Dockerハンズオン vol.12 - TDDでPost機能をコーディング part1 -

Last updated at Posted at 2020-03-24

はじめに

お待たせしました!第12回にしてやっとつぶやき機能を実装してまいります!ここではPost機能といいますね。

前回のソースコード

前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。

どんなの実装するの?

最初に今回のゴールを見据えておきましょう。

画面遷移

screen_transition_diagram.png
新たにポストページを作ります。
ポストページはポストを投稿するフォームと、今までのユーザー全員のポストが投稿日時降順で表示される機能を具備しています。
ポストページはサインイン済のユーザーしかアクセスできません。
ポストの投稿ユーザーをクリックしたらそのポストのプロフィールページに遷移できます。
ユーザーのプロフィールページでは、そのユーザーの過去のポストが投稿日時降順で表示されています。

ER図

entity_relation_diagram.png
user:post = 1:nの関係です。

テストシナリオ

さて、これを踏まえて今回のテストシナリオを考えてみましょう。

  1. 未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること
  2. サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること
  3. 未サインインのユーザーは、トップページでヘッダーにポストページへのリンクを見つけられないこと
  4. 未サインインのユーザーは、サインアップページでヘッダーにポストページへのリンクを見つけられないこと
  5. 未サインインのユーザーは、サインインページでヘッダーにポストページへのリンクを見つけられないこと
  6. 未サインインのユーザーは、ユーザー詳細ページでヘッダーにポストページへのリンクを見つけられないこと
  7. サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
  8. サインイン済のユーザーが、ユーザー詳細ページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
  9. サインイン済のユーザーが、ポストページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
  10. サインイン済のユーザーは、ポストページでポストを入力できること
  11. ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
  12. ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること
  13. ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
  14. サインイン済のユーザーは、ポストページで全ユーザーのポストを投稿日時降順で閲覧できること
  15. サインイン済のユーザーが、ポストページでポストのユーザー名をクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること
  16. 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
  17. 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
  18. サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
  19. サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
  20. サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること
  21. サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと

こんな感じでしょう。
では元気にデベロップしてまいりましょう!今回はTDDで開発をしていくので、テストコードを書いて、アプリコードを書いて、を繰り返していきます。

開発スタート

まずは、それぞれのテストシナリオをテストコードに落とし込みましょう。
開発はTDDで進めるので

  1. テストをコーディングする(Red)
  2. テストコードがパスするようにアプリケーションをコーディングする(Green)
  3. 非効率な記述があれば、Greenをキープしながらコーディングし直す(Refectoring)

です。

ではコンテナを起動しておきましょう!

$ docker-compose up -d
$ docker-compose exec web ash

未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること

ポストページについては以下の仕様で実装していきます。ポストページのURLパスは/postsとします。

まずはテストコードを書いていきます。今回のテストシナリオ用に新しいテストシナリオファイルを作りましょう。

# touch spec/system/07_posts_spec.rb
07_posts_spec.rb
feature "ユーザーとして、ポストを投稿したい", type: :system do
  scenario "未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること" do
    # ポストページにアクセスする
    visit posts_path
    
    # 現在のパスがトップページのパスであることを検証する
    expect(current_path).to eq root_path
  end
end

ポストページへのルーティングの名前付きルートをposts_pathとしています。
今までも何度か書いてきましたが、posts_pathにアクセスしようとしたのに現在のパスはroot_pathです、というリダイレクトの検証です。

この時点ではアプリケーションのコーディングを行っていないのでテストは失敗します。

# rspec spec/system/07_posts_spec.rb
...
Failures:

  1) ユーザーとして、ポストを投稿したい 未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること
     Failure/Error: visit posts_path

     NameError:
       undefined local variable or method `posts_path' for #<RSpec::ExampleGroups::Nested:0x0000555f60b1ccf8>

     # ./spec/system/07_posts_spec.rb:3:in `block (2 levels) in <main>'

Finished in 2.41 seconds (files took 8.4 seconds to load)
1 example, 1 failure
...

RSpecではこのような形でテストの失敗の理由が示されるので、それが解消されるようにアプリケーションをコーディングしていきましょう。
今回はposts_pathという変数もしくはメソッドがアプリケーションに定義されていないことがテスト失敗の理由として示されているので、まずはルーティングの設定をする必要がありそうです。

rails g controllerコマンドでポストページに必要な設定・ファイルを揃えましょう。

# rails g controller posts index

今回はポストページのためのアクションとしてposts#indexを用意することにしました。
ルーティングを定義します。

config/routes.rb
  Rails.application.routes.draw do
-   get 'posts/index'
    root 'static_pages#home'

    get   '/sign_up', to: 'users#new',    as: :sign_up
    post  '/sign_up', to: 'users#create', as: :create_user
    resources :users, only: [:show]

    get     '/sign_in',   to: 'sessions#new',     as: :sign_in
    post    '/sign_in',   to: 'sessions#create',  as: :create_session
    delete  '/sign_out',  to: 'sessions#destroy', as: :sign_out
+
+   get '/posts', to: 'posts#index', as: :posts
  end

これで名前付きルートposts_pathが定義されたのでテスト結果が変わるはずです。もう一度テストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい 未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること
     Failure/Error: expect(current_path).to eq root_path

       expected: "/"
            got: "/posts"

       (compared using ==)



     # ./spec/system/07_posts_spec.rb:5:in `block (2 levels) in <main>'

Finished in 7.94 seconds (files took 16.58 seconds to load)
1 example, 1 failure

以前テストは失敗していますが、エラー理由が変わっていますね。
今回は/にリダイレクトされることが期待されていたけど/postsにアクセスできてしまっていることがテスト失敗の理由のようです。
今コントローラーで何も制御をしていないので誰でもポストページにアクセスできてしまいますね。
では、未サインインのユーザーがposts#indexにルーティングされた場合、root_pathにリダイレクトするようにアプリをコーディングしていきましょう!

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    # 未サインインの場合、トップページにリダイレクトする
+   redirect_to root_path unless signed_in?
  end
end

以前、サインイン済ならプロフィールページにリダイレクトさせる、という機能を作りましたね。今回はそれの条件が逆バージョンです。
今回使っているunlessifの逆の分岐です。つまり、trueの場合は何もなし、falseの場合に実行する、という挙動をとります。

またテストを実行してみましょう!

# rspec spec/system/07_posts_spec.rb

Finished in 4.35 seconds (files took 7.28 seconds to load)
1 example, 0 failures

テストがパスしました!
では次のテストシナリオの実装に移りましょう!

サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること

まずはテストシナリオをコーディングします。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること" do
      # テストシナリオ用のユーザーを作成
+     user = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
+
      # サインインページにアクセスする
+     visit sign_in_path
      # サインインページで作成したユーザーのメールアドレスを入力する
+     fill_in :user_email, with: user.email
      # サインインページで作成したユーザーのパスワードを入力する
+     fill_in :user_password, with: user.password
      # サインインボタンをクリックする(サインインする)
+     click_on :sign_in_button
+     
      # ポストページにアクセスする
+     visit posts_path
+ 
      # 現在のページがポストページであることを検証する
+     expect(current_path).to eq posts_path
+   end
  end
# rspec spec/system/07_posts_spec.rb

Finished in 6.5 seconds (files took 13.64 seconds to load)
2 examples, 0 failures

テストがパスしていますね。ちゃんとサインイン前後でリダイレクト機能の出しわけができているようです。

未サインインのユーザーは、トップページでヘッダーにポストページへのリンクを見つけられないこと

こちらもテストから書き始めます。ヘッダーのポストページへのリンクはheader_posts_linkidを付与することにします。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "未サインインのユーザーは、トップページでヘッダーにポストページへのリンクを見つけられないこと" do
      # トップページにアクセスする
+     visit root_path
+     
      # ページ内に"header_posts_link"をid属性に持つ要素がないことを検証する
+     expect(page).not_to have_selector "#header_posts_link"
+   end
  end
# rspec spec/system/07_posts_spec.rb

Finished in 7.61 seconds (files took 6.77 seconds to load)
3 examples, 0 failures

まだリンクをコーディングしていないので当然見つからないですね。テストをパスできています。

未サインインのユーザーは、サインアップページでヘッダーにポストページへのリンクを見つけられないこと

一つ前とほぼ同じテストですね。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "未サインインのユーザーは、サインアップページでヘッダーにポストページへのリンクを見つけられないこと" do
      # サインアップページにアクセスする
+     visit sign_up_path
+     
      # ページ内に"header_posts_link"をid属性に持つ要素がないことを検証する    
+     expect(page).not_to have_selector "#header_posts_link"
+   end
  end
# rspec spec/system/07_posts_spec.rb

Finished in 6.83 seconds (files took 6.03 seconds to load)
4 examples, 0 failures

未サインインのユーザーは、サインインページでヘッダーにポストページへのリンクを見つけられないこと

また、ほぼ同じです。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "未サインインのユーザーは、サインインページでヘッダーにポストページへのリンクを見つけられないこと" do
      # サインインページにアクセスする
+     visit sign_in_path
+ 
      # ページ内に"header_posts_link"をid属性に持つ要素がないことを検証する    
+     expect(page).not_to have_selector "#header_posts_link"
+   end    
  end
# rspec spec/system/07_posts_spec.rb

Finished in 9.04 seconds (files took 6.57 seconds to load)
5 examples, 0 failures

どんどん進みましょう。

未サインインのユーザーは、ユーザー詳細ページでヘッダーにポストページへのリンクを見つけられないこと

ほぼ同じテストケース最後です。ユーザー詳細ページなので、Userモデルが1つ必要なのですが、前のテストでもJohn Smithcreateしたテストケースがありました。
これを簡易に使いまわせるようにJohn Smithを作成するコードをメソッド化してみましょう。メソッド化はとてもシンプルなRubyコードでdefを使うだけです。メソッド外で変数を使うことになるのでインスタンス変数を使う必要があります。

spec/system/07_posts_spec.rb
  # ユーザー「John Smith」をDBに作成する
+ def create_john
+   User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
+ end

  feature "ユーザーとして、ポストを投稿したい", type: :system do
  ...
    scenario "サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること" do
      # テストシナリオ用のユーザーを作成
-     user = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
+     user = create_john
      ...
    end
  end

これでテストがパスするか一度確認しておきましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 7.8 seconds (files took 6.24 seconds to load)
5 examples, 0 failures

これでJohn Smithのユーザー作成を単純なメソッド
呼び出しで必要なテストシナリオからのみ呼び出すことができるようになりました!
では今回のテストシナリオを追加していきます。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "未サインインのユーザーは、ユーザー詳細ページでヘッダーにポストページへのリンクを見つけられないこと" do
      # テスト用のユーザーを作成する
+     user = create_john
+
      # テストユーザーのユーザー詳細ページにアクセスする
+     visit user_path(user)
+
      # ページ内に"header_posts_link"をid属性に持つ要素がないことを検証する
+     expect(page).not_to have_selector "#header_posts_link"
+   end
  end
# rspec spec/system/07_posts_spec.rb

Finished in 8.91 seconds (files took 6.91 seconds to load)
6 examples, 0 failures

今回もテストをパスできていることがわかります。メソッド
の呼び出しもうまくいっていますね。

サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること

次はサインイン後にヘッダーにポストページへのリンクが表示されており、クリックするとポストページに行けるようになる機能を作っていきます。今までと同様に、テストからコーディングしていきましょう。
サインイン済のユーザーでテストをするシナリオは前にもあったので、まずはサインイン済の状態にする操作をメソッド化してみます。

spec/system/07_posts_spec.rb
  ...
  # 与えられたユーザーでサインインする
+ def sign_in(user)
    # サインインページにアクセスする
+   visit sign_in_path
    # サインインページで作成したユーザーのメールアドレスを入力する
+   fill_in :user_email, with: user.email
    # サインインページで作成したユーザーのパスワードを入力する
+   fill_in :user_password, with: user.password
    # サインインボタンをクリックする(サインインする)
+   click_on :sign_in_button
+ end
  ...
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
    scenario "サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること" do
      user = create_john
-     visit sign_in_path
-     fill_in :user_email, with: user.email
-     fill_in :user_password, with: user.password
-     click_on :sign_in_button
+     sign_in(user)
      ...
    end
    ...
  end

再度、メソッド化してもテストがパスするかを確認します。

# rspec spec/system/07_posts_spec.rb

Finished in 8.96 seconds (files took 8 seconds to load)
6 examples, 0 failures

メソッド化成功です!
ではこのメソッドを使って、今回のテストコードを記述してみます。

spec/system/07_posts_spec.rb
feature "ユーザーとして、ポストを投稿したい", type: :system do
  ...
+   scenario "サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること" do
      # テスト用のユーザーを作成する
+     user = create_john
      # テスト用のユーザーでサインインする
+     sign_in(user)
+
      # プロフィールページにアクセスする
+     visit user_path(user)
      # "header_posts_link"をid属性に持つ要素(=ヘッダーのポストリンク)をクリックする
+     click_on :header_posts_link
+
      # 現在のページがポストページであることを検証する
+     expect(current_path).to eq posts_path
+   end
  end

はい。これでテストを回してみましょう。まだヘッダーにポストリンクを作っていないのでRedになるはずです。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
     Failure/Error: click_on :header_posts_link

     Capybara::ElementNotFound:
       Unable to find link or button :header_posts_link

Finished in 11.92 seconds (files took 6.78 seconds to load)
7 examples, 1 failure

header_posts_linkをクリックしようとしたけどそんな要素見つからなかった、という失敗理由が表示されていますね。
サインイン後のリンクにポストリンクを追加してあげましょう。

app/views/layouts/application.html.erb
  <% if signed_in? %>
+   <li class="nav-item"><%= link_to "Posts", posts_path, class: "nav-link", id: :header_posts_link %></li>
    <li class="nav-item"><%= link_to "Profile", current_user, class: "nav-link", id: :header_profile_link %></li>
    <li class="nav-item"><%= link_to "Sign out", sign_out_path, method: :delete, class: "nav-link", id: :header_sign_out_link %></li>
  <% else %>

1行、ポストページへのリンクを追加しました。

# rspec spec/system/07_posts_spec.rb

Finished in 10.38 seconds (files took 6.17 seconds to load)
7 examples, 0 failures

今回はテストがパスしています。今まで実装もしていなかったのでパスしてた、未サインインユーザーにはポストページへのリンクがヘッダーに表示されないテストもパスしているのでデグレなく機能実装ができましたね。

ちょっとViewを確認してみましょう。
image.png
Viewを確認してみても、Postsリンクが追加されたことがわかりますね。

サインイン済のユーザーが、ユーザー詳細ページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること

サインインしているユーザーとは別のユーザーを作成し、そのユーザーのユーザー詳細ページからのポストページへの遷移をテストしてみましょう。
別のユーザーも他のテストシナリオでも使えるようにメソッド化しておきたいところです。create_johnメソッドを少し改良して、引数によって異なるユーザーをDB作成できるように改良して使ってみましょう。

spec/system/07_posts_spec.rb
- def create_john
-   User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
- end
  # user_typeに応じて、ユーザーをDBに作成する
  # 1: John Smith
  # 2: Taro Tanaka
+ def create_user(user_type = 1)
+   case user_type
+   when 1
+     User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
+   when 2
+     User.create(name: "Taro Tanaka", email: "taro@sample.com", password: "taro1234")
+   end
+ end
  ...
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ... 
    scenario "サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること" do
-     user = create_john
+     user = create_user(1)
      ...
    end
    ...
    scenario "未サインインのユーザーは、ユーザー詳細ページでヘッダーにポストページへのリンクを見つけられないこと" do
-     user = create_john
+     user = create_user(1)
      ...
    end
    ...
    scenario "サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること" do
-     user = create_john
+     user = create_user(1)
      ...
    end
  end

create_userメソッドではcase文を使ってみました。case文はある変数の値に応じて動作を変える分岐を作ることができます。

case target
when value1
  # target == value1 の場合のコード
when value2
  # target == value2 の場合のコード
...
else
  # どれにも当てはまらなかった場合のコード
end

では、メソッドがうまく置き換われたかを確認してみます。

# rspec spec/system/07_posts_spec.rb

Finished in 9.62 seconds (files took 6.84 seconds to load)
7 examples, 0 failures

ちゃんとテストがパスしていますので、新しいメソッドは機能しています。
ではこのメソッドを使って二人のユーザーを作成して実行するテストコードを記述してみましょう。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーが、ユーザー詳細ページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること" do
      # テスト用のユーザーを2人作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
      # user1でサインインする
+     sign_in(user1)
+
      # user2のユーザー詳細ページにアクセスする
+     visit user_path(user2)
      # "header_posts_link"をid属性に持つ要素(=ヘッダーのポストリンク)をクリックする
+     click_on :header_posts_link
+    
      # 現在のページがポストページであることを検証する
+     expect(current_path).to eq posts_path
+   end
    ...
  end

テストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 12.04 seconds (files took 5.41 seconds to load)
8 examples, 0 failures

問題なくGreenです。

サインイン済のユーザーが、ポストページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること

これも一つ前と同じようなケースです。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーが、ポストページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること" do
      # テスト用のユーザーを1人作成する
+     user = create_user(1)
      # userでサインインする
+     sign_in(user)
+
      # ポストページにアクセスする
+     visit posts_path
      # "header_posts_link"をid属性に持つ要素(=ヘッダーのポストリンク)をクリックする    
+     click_on :header_posts_link
+
      # 現在のページがポストページであることを検証する
+     expect(current_path).to eq posts_path
+   end
    ...
  end
# rspec spec/system/07_posts_spec.rb

Finished in 13.63 seconds (files took 6.56 seconds to load)
9 examples, 0 failures

遷移系はここまでですね。全てGreenをキープしています。

サインイン済のユーザーは、ポストページでポストを入力できること

いよいよポストページの機能に入っていきます。
でもやり方は変わりません。まずはテストをコーディングしましょう!

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーは、ポストページでポストを入力できること" do
      # テスト用のユーザーを作成する
+     user = create_user(1)
      # このテストシナリオで使うポスト内容を定義する
+     content = "Hello world."
      # userでサインインする
+     sign_in(user)
+ 
      # ポストページにアクセスする
+     visit posts_path
      # ポスト入力欄(#post_content)にcontentを入力する
+     fill_in :post_content, with: content 
+ 
      # ポスト入力欄(#post_content)にcontentが入力されていることを検証する
+     expect(find("#post_content").value).to eq content
+   end
    ...
  end

前回のハンズオンでも出てきた入力してちゃんと入力できているかを確認するテストコードですね。
今回はポストを投稿するための入力エリアであるpost_contentに「Hello world.」を入力できるかをチェックしています。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in サインイン済のユーザーは、ポストページでポストを入力できること
     Failure/Error: fill_in :post_content, with: content

     Capybara::ElementNotFound:
       Unable to find field :post_content that is not disabled

Finished in 20.46 seconds (files took 7.82 seconds to load)
10 examples, 1 failure

このテストは失敗します。なぜならまだポストを投稿するための入力エリアであるpost_contentを作っていないからです。

post_contentはPostモデルオブジェクトを作るためのフォームです。
なのでまずはPostモデルを作成しましょう。

# rails g model post content:string user:references
# rm -rf spec/models

ここで見かけたことのないreferences型が出てきました。
これは外部キーを定義するための型です。今回のようにuser:referencesとするとuser_idという項目が定義され、ここにはUserモデルの主キーであるidが入るようになります。

Postモデルファイルをみてみましょう。

app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

今まではclass定義しかされていませんでしたが、今回はbelongs_to :userというコードがデフォルトで記述されています。
これはモデルの関連付けです。(参考: Active Record の関連付け - Railsガイド

belongs_toは指定するモデルを1つに特定できることを表します。つまり、あるPostから見ると紐づくUserが一意に決まることを示しています。

逆にUserは複数のPostを行います。これを表す関連付けがhas_manyです。
UserモデルにはまだPostモデルとの関連付けがコーディングされていないので、自分で記述しておきましょう。

app/models/user.rb
  class User < ApplicationRecord
    ...
+   has_many :posts
    ...
  end

ちなみにマイグレーションファイルの中身もみておきましょう。

db/migrate/YYYYMMDDhhmmss_create_posts.rb
class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :content
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end

t.references :user, null: false, foreign_key: trueuser_idを外部キーとして利用できるようにDBにSQLを発行してくれます。

では、マイグレーションファイルを適用します。

# rails db:migrate

== YYYYMMDDhhmmss CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.1530s
== YYYYMMDDhhmmss CreatePosts: migrated (0.1536s) =============================```

Postモデルの準備ができたので、今までと同じようにPostモデルを作成するためのフォームを作っていきましょう。
Userモデルの時と同じように、コントローラーで空のPostモデルオブジェクトを作成し、Viewでform_withヘルパーを使ってフォームを作ってみます。

app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    def index
      redirect_to root_path unless signed_in?
+     @post = Post.new
    end
  end
app/views/posts/index.html.erb
- <h1>Posts#index</h1>
- <p>Find me in app/views/posts/index.html.erb</p> %>
+ <div class="container my-5">
+   <%= form_with model: @post, url: nil, local: true do |form| %>
+     <div class="form-group">
+       <%= form.text_area :content, class: "form-control", placeholder: "いまどうしてる?", autofocus: true %>
+     </div>
+   <% end %>
+ </div>

まだリクエスト先のルーティングを決めていないのでurlにはnilを定義しています。
また、今までと違う点としてはform.text_areaを使っています。text_areaヘルパーは<textarea>タグを生成するヘルパーです。
ポストは今までのように1行の短い文字列ではなく、改行などを含んだ140文字の文章になるのでそちらを選択してます。
さらに、placeholderを使っています。これはHTML5の技術ですが、inputtextareaに何も入力がない時に限り、そのフォームの補助の役割でどういうものを入力すればいいかを表示してあげる機能です。
autofocus: trueはページが表示された時に自動的にフォーカスされるフィールドを指定できるHTML5の機能です。便利なのでつけときます。

今回の場合は、以下のようなフォームができあがっています。
image.png

ここまでで再度テストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 16.43 seconds (files took 6.9 seconds to load)
10 examples, 0 failures

これでテストをパスすることができました!

今回はこの辺りで時間切れなので、残りのテスト&コーディングは次回に回したいと思います!

まとめ

今回はポスト機能をTDD/BDDでコーディングしてきました。なんかいよいよコーディングしている感じが高まってきましたね。
次回は残りのユーザー詳細ページ側でそのユーザーの投稿に絞ってポストを確認できる機能をコーディングしていきます。
次回以降もBDDで実装を進めていくので、是非ともテストコードも振り返っておいてくださいね。

後片付け

いつものようにコンテナを落としておきます。

# exit
$ docker-compose down

本日のソースコード

Other Hands-on Links

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?