はじめに
お待たせしました!第12回にしてやっとつぶやき機能を実装してまいります!ここではPost機能といいますね。
前回のソースコード
前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。
どんなの実装するの?
最初に今回のゴールを見据えておきましょう。
画面遷移
新たにポストページを作ります。
ポストページはポストを投稿するフォームと、今までのユーザー全員のポストが投稿日時降順で表示される機能を具備しています。
ポストページはサインイン済のユーザーしかアクセスできません。
ポストの投稿ユーザーをクリックしたらそのポストのプロフィールページに遷移できます。
ユーザーのプロフィールページでは、そのユーザーの過去のポストが投稿日時降順で表示されています。
ER図
テストシナリオ
さて、これを踏まえて今回のテストシナリオを考えてみましょう。
- 未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること
- サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること
- 未サインインのユーザーは、トップページでヘッダーにポストページへのリンクを見つけられないこと
- 未サインインのユーザーは、サインアップページでヘッダーにポストページへのリンクを見つけられないこと
- 未サインインのユーザーは、サインインページでヘッダーにポストページへのリンクを見つけられないこと
- 未サインインのユーザーは、ユーザー詳細ページでヘッダーにポストページへのリンクを見つけられないこと
- サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
- サインイン済のユーザーが、ユーザー詳細ページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
- サインイン済のユーザーが、ポストページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
- サインイン済のユーザーは、ポストページでポストを入力できること
- ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
- ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること
- ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
- サインイン済のユーザーは、ポストページで全ユーザーのポストを投稿日時降順で閲覧できること
- サインイン済のユーザーが、ポストページでポストのユーザー名をクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること
- 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
- 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
- サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
- サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
- サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること
- サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
こんな感じでしょう。
では元気にデベロップしてまいりましょう!今回はTDDで開発をしていくので、テストコードを書いて、アプリコードを書いて、を繰り返していきます。
開発スタート
まずは、それぞれのテストシナリオをテストコードに落とし込みましょう。
開発はTDDで進めるので
- テストをコーディングする(Red)
- テストコードがパスするようにアプリケーションをコーディングする(Green)
- 非効率な記述があれば、Greenをキープしながらコーディングし直す(Refectoring)
です。
ではコンテナを起動しておきましょう!
$ docker-compose up -d
$ docker-compose exec web ash
未サインインのユーザーが、ポストページにアクセスしようとしたとき、トップページにリダイレクトされること
ポストページについては以下の仕様で実装していきます。ポストページのURLパスは/posts
とします。
まずはテストコードを書いていきます。今回のテストシナリオ用に新しいテストシナリオファイルを作りましょう。
# touch spec/system/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
を用意することにしました。
ルーティングを定義します。
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
にリダイレクトするようにアプリをコーディングしていきましょう!
class PostsController < ApplicationController
def index
# 未サインインの場合、トップページにリダイレクトする
+ redirect_to root_path unless signed_in?
end
end
以前、サインイン済ならプロフィールページにリダイレクトさせる、という機能を作りましたね。今回はそれの条件が逆バージョンです。
今回使っているunless
はif
の逆の分岐です。つまり、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
テストがパスしました!
では次のテストシナリオの実装に移りましょう!
サインイン済のユーザーが、ポストページにアクセスしようとしたとき、ポストページにアクセスできること
まずはテストシナリオをコーディングします。
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_link
のid
を付与することにします。
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
まだリンクをコーディングしていないので当然見つからないですね。テストをパスできています。
未サインインのユーザーは、サインアップページでヘッダーにポストページへのリンクを見つけられないこと
一つ前とほぼ同じテストですね。
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
未サインインのユーザーは、サインインページでヘッダーにポストページへのリンクを見つけられないこと
また、ほぼ同じです。
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 Smith
をcreate
したテストケースがありました。
これを簡易に使いまわせるようにJohn Smith
を作成するコードをメソッド化してみましょう。メソッド化はとてもシンプルなRubyコードでdef
を使うだけです。メソッド外で変数を使うことになるのでインスタンス変数を使う必要があります。
# ユーザー「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
のユーザー作成を単純なメソッド
呼び出しで必要なテストシナリオからのみ呼び出すことができるようになりました!
では今回のテストシナリオを追加していきます。
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
今回もテストをパスできていることがわかります。メソッド
の呼び出しもうまくいっていますね。
サインイン済のユーザーが、プロフィールページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
次はサインイン後にヘッダーにポストページへのリンクが表示されており、クリックするとポストページに行けるようになる機能を作っていきます。今までと同様に、テストからコーディングしていきましょう。
サインイン済のユーザーでテストをするシナリオは前にもあったので、まずはサインイン済の状態にする操作をメソッド化してみます。
...
# 与えられたユーザーでサインインする
+ 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
メソッド化成功です!
ではこのメソッドを使って、今回のテストコードを記述してみます。
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
をクリックしようとしたけどそんな要素見つからなかった、という失敗理由が表示されていますね。
サインイン後のリンクにポストリンクを追加してあげましょう。
<% 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を確認してみましょう。
Viewを確認してみても、Posts
リンクが追加されたことがわかりますね。
サインイン済のユーザーが、ユーザー詳細ページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
サインインしているユーザーとは別のユーザーを作成し、そのユーザーのユーザー詳細ページからのポストページへの遷移をテストしてみましょう。
別のユーザーも他のテストシナリオでも使えるようにメソッド化しておきたいところです。create_john
メソッドを少し改良して、引数によって異なるユーザーをDB作成できるように改良して使ってみましょう。
- 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
ちゃんとテストがパスしていますので、新しいメソッドは機能しています。
ではこのメソッドを使って二人のユーザーを作成して実行するテストコードを記述してみましょう。
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です。
サインイン済のユーザーが、ポストページでヘッダーのポストリンクをクリックしたとき、ポストページに遷移すること
これも一つ前と同じようなケースです。
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をキープしています。
サインイン済のユーザーは、ポストページでポストを入力できること
いよいよポストページの機能に入っていきます。
でもやり方は変わりません。まずはテストをコーディングしましょう!
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モデルファイルをみてみましょう。
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モデルとの関連付けがコーディングされていないので、自分で記述しておきましょう。
class User < ApplicationRecord
...
+ has_many :posts
...
end
ちなみにマイグレーションファイルの中身もみておきましょう。
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: true
がuser_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
ヘルパーを使ってフォームを作ってみます。
class PostsController < ApplicationController
def index
redirect_to root_path unless signed_in?
+ @post = Post.new
end
end
- <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の技術ですが、input
やtextarea
に何も入力がない時に限り、そのフォームの補助の役割でどういうものを入力すればいいかを表示してあげる機能です。
autofocus: true
はページが表示された時に自動的にフォーカスされるフィールドを指定できるHTML5の機能です。便利なのでつけときます。
ここまでで再度テストを実行してみましょう。
# 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