【153日目】【1日20分のRailsチュートリアル】【第11章】マイクロポスト作成フォームを構築するためにホーム画面を変更する
今日は「11.3.2 マイクロポストを作成する」から。
11.3.2 マイクロポストを作成する
主な違いは、別の micropost/new ページを使う代わりに、ホーム画面 (つまりルートパス) にフォームを置くという点です。図11.10のモックアップを見てください。
ユーザーの新規登録と似てるけど、新規ページを作るんじゃなくて、ホーム画面にフォームを追加するのかー。
マイクロポストのcreateアクションを作り始めましょう。
(中略)
違いは、新しいマイクロポストをbuildするためにuser/micropost関連付けを使用している点です (リスト11.34)。
user/micropost関連付けを使用することでどのユーザーのマイクロポストなのか紐付けることができる、って感じなのかな。
app/controllers/microposts_controller.rb
: def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else render 'static_pages/home' end end : private def micropost_params params.require(:micropost).permit(:content) end end
マイクロポスト作成フォームを構築するために、サイト訪問者がログインしているかどうかに応じて異なるHTMLを提供するコードを使用します (リスト11.35)。
ログイン中かどうかをチェックして、ログインしていたらユーザー情報とマイクロポストのフォームを表示して、ログインしてなかったら今までのホーム画面を表示するようにビューを変更。
app/views/static_pages/home.html.erb
<% if logged_in? %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> </div> <% else %> : <% end %>
if-else分岐を使用してコードを書き分けている点が少し汚いですが、このコードのクリーンアップは演習に回すことにします (11.6)。
え、これ駄目なんだ……。まぁまた演習で。
リスト11.35のコードを動かすためには、いくつかのPartialを作る必要があります。まずはHomeページの新しいサイドバーからです。以下のリスト11.36のようになります。
Partial作成用にファイルを作成する。
$ touch app/views/shared/_user_info.html.erb
下記のように更新。
ユーザーのプロフィールアイコン画像と名前、プロフィールページへのリンク、マイクロポストの投稿数を表示するコードを生成するPartialを追加する。
app/views/shared/_user_info.html.erb
<%= link_to gravatar_for(current_user, size: 50), current_user %> <h1><%= current_user.name %></h1> <span><%= link_to "view my profile", current_user %></span> <span><%= pluralize(current_user.microposts.count, "micropost") %></span>
今回のように “1 microposts” と表示してしまうと英語の文法上誤りになってしまいます。そこで、7.3.3で紹介したpluralizeメソッドを使って “1 micropost” や “2 microposts” と表示するように調整しています。
そういえばそんなメソッドありましたね。細かい…!!日本語で表示するならこの辺は気にしなくて良さそう。
項の途中だけどキリがいいので今日はここまで。
今日の学習時間は【21分】。
次は「11.3.2 マイクロポストを作成する」のマイクロポスト作成フォームを定義するところから。
【152日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストのアクセス制御を実装する
今日は「11.3 マイクロポストを操作する」から。
11.3 マイクロポストを操作する
データモデリングとマイクロポスト表示テンプレートの両方が完成したので、次はWeb経由でそれらを作成するためのインターフェイスに取りかかりましょう。
Twitterでいう新規ツイートするフォームかな。
Micropostsリソースへのインターフェイスは、主にProfileページとHomeページのコントローラを経由して実行されるので、Micropostsコントローラにはnewやeditのようなアクションは不要ということになります。
newも要らないのか。Micropostsを投稿する専用ページは設けないイメージかな…??
ルーティングファイルに下記を追加。
config/routes.rb
resources :microposts, only: [:create, :destroy]
11.3.1 マイクロポストのアクセス制御
関連付けられたユーザーを通してマイクロポストにアクセスするので、createアクションやdestroyアクションを利用するユーザーは、ログイン済みでなければなりません。
ふむふむ。確かにそうですね。
誰でも利用できる状態はまずいですね。
正しいリクエストを各アクションに向けて発行し、マイクロポストの数が変化していないかどうか、また、リダイレクトされるかどうかを確かめればよいのです (リスト11.30)。
createとdestroyアクションに対してテストを追加。
test/controllers/microposts_controller_test.rb
require 'test_helper' class MicropostsControllerTest < ActionController::TestCase def setup @micropost = microposts(:orange) end test "should redirect create when not logged in" do assert_no_difference 'Micropost.count' do post :create, micropost: { content: "Lorem ipsum" } end assert_redirected_to login_url end test "should redirect destroy when not logged in" do assert_no_difference 'Micropost.count' do delete :destroy, id: @micropost end assert_redirected_to login_url end end
実装してないのでテストは通りません。
$ bundle exec rake test 57 tests, 290 assertions, 0 failures, 2 errors, 0 skips
beforeフィルターのlogged_in_userメソッドを使って、ログインを要求したことについて思い出してください (リスト9.12)。
(中略)
そこで、各コントローラが継承するApplicationコントローラに (4.4.4)、このメソッドを移してしまいましょう。結果はリスト11.31のようになります。
各コントローラで使用するAPIはApplicationコントローラに定義すればいいのか…。
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper private # ユーザーのログインを確認する def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end end
コードが重複しないよう、このときUsersコントローラからもlogged_in_userを削除しておきましょう。
コメントアウトしておきました。
app/controllers/users_controller.rb
# # ログイン済みユーザーかどうか確認 # def logged_in_user # unless logged_in? # store_location # flash[:danger] = "Please log in." # redirect_to login_url # end # end
これにより、createアクションやdestroyアクションに対するアクセス制限が、beforeフィルターで簡単に実装できるようになります (リスト11.32)。
Userコントローラと一緒の実装でOKなのかー。beforeフィルターとcreate/destroyアクションを追加。
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create end def destroy end end
テスト通りました!
$ bundle exec rake test 57 tests, 294 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【30分】。
次は「11.3.2 マイクロポストを作成する」から。
【151日目】【1日20分のRailsチュートリアル】【第11章】プロフィール画面におけるマイクロポストのテストを追加する
今日は「11.2.3 プロフィール画面におけるマイクロポストのテスト」から。
11.2.3 プロフィール画面におけるマイクロポストのテスト
この項では、プロフィール画面で表示されるマイクロポストに対して、統合テストを書いていきます。まずは、プロフィール画面用の統合テストを生成してみましょう。
この段階でプロフィール画面用の統合テストを生成するのか。。。もっと早いのもありなのかな。
$ rails generate integration_test users_profile invoke test_unit create test/integration/users_profile_test.rb
プロフィール画面におけるマイクロポストをテストするためには、ユーザーに紐付いたマイクロポストのテスト用データが必要になります。
まずはテスト用データを準備する。
元々追加していたマイクロポスト用のテスト用データをmichaelに紐付けるのと追加で30個用意する。
test/fixtures/microposts.yml
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 <% 30.times do |n| %> micropost_<%= n %>: content: <%= Faker::Lorem.sentence(5) %> created_at: <%= 42.days.ago %> user: michael <% end %>
今回のテストでは、プロフィール画面にアクセスした後に、ページタイトルとユーザー名、Gravatar、マイクロポストの投稿数、そしてページ分割されたマイクロポスト、といった順でテストしていきます。
プロフィール画面の表示を順にテストしていく感じかな。
test/integration/users_profile_test.rb
require 'test_helper' class UsersProfileTest < ActionDispatch::IntegrationTest include ApplicationHelper def setup @user = users(:michael) end test "profile display" do get user_path(@user) assert_template 'users/show' assert_select 'title', full_title(@user.name) assert_select 'h1', text: @user.name assert_select 'h1>img.gravatar' assert_match @user.microposts.count.to_s, response.body assert_select 'div.pagination' @user.microposts.paginate(page: 1).each do |micropost| assert_match micropost.content, response.body end end end
したがって、そのページのどこかしらにマイクロポストの投稿数が存在するのであれば、次のように探し出してマッチできるはずです。
assert_match @user.microposts.count.to_s, response.body
ページにマイクロポストの投稿数なんて表示してたっけ。。。って思ったら表示してました。
キャプチャ見れば一目瞭然。(「Microposts」の末尾にあった)
そして、アプリケーション側のコードは実装済みなので、これらのテストは成功するはずです。
成功しました。問題なし!
$ bundle exec rake test 55 tests, 290 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【20分】。
次は「11.3 マイクロポストを操作する」から。
【150日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストのサンプルデータを追加する
今日は「11.2.2 マイクロポストのサンプル」から。
11.2.2 マイクロポストのサンプル
すべてのユーザーにマイクロポストを追加しようとすると時間が掛かり過ぎるので、takeメソッドを使って最初の6人だけに追加します。
(中略)
この6人については、1ページの表示限界数 (30) を越えさせるために、それぞれ50個分のマイクロポストを追加するようにしています。
ほほぅ…。適当な任意のユーザー1人だけ、とかじゃないのか。
そっか、自動生成するんだもんね。。。何人でも何個でもできるか。
“テストデータ"って考えると手作業で適当なデータ入れる手順を思い浮かべてしまうんだけど、自動生成できるんだもんねぇ。 そう、Ruby on Railsならね。(
というわけでサンプルデータにマイクロポスト追加しましょう。
db/seeds.rb
users = User.order(:created_at).take(6) 50.times do content = Faker::Lorem.sentence(5) users.each { |user| user.microposts.create!(content: content) } end
ここで、いつものように開発環境用のデータベースで再度サンプルデータを生成します。
$ bundle exec rake db:migrate:reset $ bundle exec rake db:seed
よし、サンプルデータを再生成したので実際の表示を見てみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
example@railstutorial.org
のアカウントでログイン。
プロフィールページにマイクロポストが表示されました!
図11.6のページにはマイクロポスト固有のスタイルが与えられていないので、リスト11.25を追加して、結果のページを見てみましょう。
レイアウトひどいなと思ってました。スタイル追加しましょう。
app/assets/stylesheets/custom.css.scss
: /* 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; } }
きれいになったー!
他の任意のユーザーもマイクロポスト表示されてます。
ページャーも問題なし。
今日の学習時間は【25分】。
次は「11.2.3 プロフィール画面におけるマイクロポストのテスト」から。
【149日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストのビューを追加する
今日は「11.2.1 マイクロポストの描画」から。
11.2.1 マイクロポストの描画
この項では、ユーザーのプロフィール画面 (show.html.erb) でそのユーザーのマイクロポストを表示させ、また、これまでに投稿した総数も表示するようにしていきます。
ふむふむ。Twitterのプロフィール画面のイメージかな。
まずは、Micropostのコントローラとビューを作成するために、コントローラを生成しましょう。
生成しましょう。
$ rails generate controller Microposts 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_unit invoke assets invoke coffee create app/assets/javascripts/microposts.coffee invoke scss create app/assets/stylesheets/microposts.scss
user.html.erbパーシャルと同じようにmicropost.html.erbパーシャルを定義してマイクロポストを表示するようにする。
パーシャル定義しようとしたらファイルがなかったので作成しておく。
$ touch app/views/microposts/_micropost.html.erb
app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> </li>
今回の場合は、ユーザーコントローラのコンテキストにおいて、マイクロポストをページネーションしたいため、明示的に@microposts変数を will_paginateに渡す必要があります。
will_paginateについての説明が全くピンと来てないけど明示的に変数を渡す必要があるのは分かった。
チュートリアルのコードにはredirect_toの行のコードがないけどとりあえず下に@microposts変数追加しといた。
app/controllers/users_controller.rb
def show @user = User.find(params[:id]) redirect_to root_url and return unless @user.activated? @microposts = @user.microposts.paginate(page: params[:page]) end
これですべての要素が揃ったので、プロフィール画面にマイクロポストを表示させてみましょう (リスト11.23)。
マイクロポストの投稿数はuser.microposts.countで取得できて、それなりに高速らしい。いいね!
アイコンの表示の下にマイクロポストを表示させるようにコードを追加。
app/views/users/show.html.erb
<div class="col-md-8"> <% if @user.microposts.any? %> <h3>Microposts (<%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts %> </ol> <%= will_paginate @microposts %> <% end %> </div>
ここで、改良した新しいプロフィール画面をブラウザで見てみましょう (図11.5)。
…何とも寂しいページで、がっかりですね。マイクロポストが1つもないのでは無理もありません。
今までのユーザーページと何も変わっていない…!ので動作確認はマイクロポスト追加してからにしよう。
今日の学習時間は【20分】。
次は「11.2.2 マイクロポストのサンプル」から。
【148日目】【1日20分のRailsチュートリアル】【第11章】ユーザーが破棄された場合、マイクロポストも破棄されるようにする
今日は「11.1.4 マイクロポストを改良する」の「Dependent: destroy」から。
11.1.4 マイクロポストを改良する
Dependent: destroy
サイト管理者はユーザーを破棄する権限を持ちます。ユーザーが破棄された場合、ユーザーのマイクロポストも同様に破棄されるべきです。
マイクロポストとユーザーの関連性を切るのもありだと思ったけど、残しておいても容量圧迫するもんねぇ。。。要らないか。
この振る舞いは、has_manyメソッドにオプションを渡してあげることで実装できます (リスト11.18)。
元々書いてたhas_manyメソッドにdependent: :destroyオプションを追加。
これだけでユーザーを削除したらそのユーザーに紐付いたマイクロポストも削除されるらしい…!楽!シンプル!
app/models/user.rb
class User < ActiveRecord::Base has_many :microposts, dependent: :destroy :
次に、リスト11.18が正しく動くかどうか、テストを使ってUserモデルを検証してみます。
マイクロポストを生成して、次にユーザーを削除してマイクロポストの数が1減っているかを確認するテスト。
test/models/user_test.rb
test "associated microposts should be destroyed" do @user.save @user.microposts.create!(content: "Lorem ipsum") assert_difference 'Micropost.count', -1 do @user.destroy end end
問題なく動きました。
$ bundle exec rake test 54 tests, 223 assertions, 0 failures, 0 errors, 0 skips
11.2 マイクロポストを表示する
Twitterのような独立したマイクロポストのindexページは作らずに、図11.4のモックアップに示したように、ユーザーのshowページで直接マイクロポストを表示させることにします。
まずは特定のユーザーのマイクロポストを表示させるようにユーザーのプロフィールページを修正する。
今日の学習時間は【15分】。
次は「11.2.1 マイクロポストの描画」から。
【147日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストを特定の順序で取得できるようにする
今日は「11.1.4 マイクロポストを改良する」から。
11.1.4 マイクロポストを改良する
具体的には、ユーザーのマイクロポストを特定の順序で取得できるようにしたり、マイクロポストをユーザーに依存させて、ユーザーが削除されたらマイクロポストも自動的に削除されるようにしていきます。
ふむふむ。
デフォルトのスコープ
まずデータベース上の最初のマイクロポストが、fixture内のマイクロポスト (most_recent) と同じであるか検証するテストを書いていきましょう (リスト11.13)。
まずテストを書いてみる。
test/models/micropost_test.rb
test "order should be most recent first" do assert_equal microposts(:most_recent), Micropost.first end
リスト11.13では、マイクロポスト用のfixtureファイルからサンプルデータを読み出しているので、次のfixtureファイルも必要になります (リスト11.14)。
マイクロポスト用のfixtureファイルを作成する。
test/fixtures/microposts.yml
orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> tau_manifesto: content: "Check out the @tauday site by @mhartl: http://tauday.com" created_at: <%= 3.years.ago %> cat_video: content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk" created_at: <%= 2.hours.ago %> most_recent: content: "Writing a short test" created_at: <%= Time.zone.now %>
ほとんどのシステムでは上から順に作成されるので、fixtureファイルでも意図的に順序をいじっています。
(中略)
この振る舞いは恐らくシステムに依存していて崩れやすいので、(本来は) この振る舞いに依存したテストは書くべきでは無いでしょう。
崩れやすいからオススメではないしいつも使えるわけではない?けど、今回はとりあえず大丈夫なのかな…??
この段階ではマイクロポストをソートしてないのでテストは失敗する。
$ bundle exec rake test TEST=test/models/micropost_test.rb TESTOPTS="--name test_order_should_be_most_recent_first" Finished in 0.11341s 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
残念ながらデフォルトの順序が昇順となっているので、このままでは数の小さい値から大きい値にソートされてしまいます (最も古い投稿が最初に表示されてしまいます)。
順序を逆にしたい場合は、一段階低いレベルの技術ではありますが、次のように生のSQLを引数に与える必要があります。
新しい投稿から古い投稿の順になるようソートする。
app/models/micropost.rb
default_scope -> { order(created_at: :desc) }
これでテストが通るようになりました。
$ bundle exec rake test Finished in 1.94684s 53 tests, 222 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【25分】。
次は「11.1.4 マイクロポストを改良する」の「Dependent: destroy」から。