【156日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストを削除する
今日は「11.3.4 マイクロポストを削除する」から。
11.3.4 マイクロポストを削除する
最後の機能として、マイクロポストリソースにポストを削除する機能を追加します。これはユーザー削除と同様に(9.4.2)、"delete" リンクで実現します (図11.16)。
(中略)
カレントユーザーが作成したマイクロポストに対してのみ削除リンクが動作するようにします。
自分が投稿したマイクロポストしか消せないようにしないとね。
最初のステップとして、マイクロポストのパーシャル (リスト11.21) に削除リンクを追加します。
マイクロポストの投稿ユーザーとカレントユーザーが一致するときは削除リンクを表示する。
app/views/microposts/_micropost.html.erb
: <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> :
次に、Micropostsコントローラのdestroyアクションを定義しましょう。
(中略)
大きな違いは、admin_userフィルターで@user変数を使うのではなく、関連付けを使ってマイクロポストを見つけるようにしている点です。
マイクロポストのidは一意なので、カレントユーザーのマイクロポストからfindしたときに見つからない→カレントユーザーのマイクロポストじゃない→削除できない、ってできるってことかな。
削除リンクも表示させないけど、destroyアクション呼ばれたときにもチェックしてあるユーザーが他のユーザーのマイクロポストを削除しようとするのを防ぐのかー。
app/controllers/microposts_controller.rb
: before_action :correct_user, only: :destroy : def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end private : def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end end
マイクロポストがHomeページから削除された場合でもProfileページから削除された場合でも、request.referrerを使うことでDELETEリクエストが発行されたページに戻すことができるので、非常に便利です。
URLって保存されてるんだなぁ…。
これらのコードにより、上から2番目のマイクロポストを削除すると、図 11.17のようにうまく動くはずです。
動作見てみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
上から2番目のマイクロポストを削除してみる。
確認のポップアップも表示されて
マイクロポストも削除できました。
今日の学習時間は【24分】。
次は「11.3.5 フィード画面におけるマイクロポストのテスト」から。
【155日目】【1日20分のRailsチュートリアル】【第11章】お試しフィードを追加する
今日は「11.3.3 フィードの原型」から。
11.3.3 フィードの原型
マイクロポスト投稿フォームが動くようになりましたが、今の段階では投稿した内容をすぐに見ることができません。というのも、Homeページにまだマイクロポストを表示する部分が実装されていないからです。
ホーム画面でマイクロポスト投稿してからプロフィール画面に行けば見れるけど、めんどいよね!ってことか。そうだね、微妙に手間だね。
すべてのユーザーがフィードを持つので、feedメソッドはUserモデルで作るのが自然です。フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得してきます。(リスト11.44)
whereメソッドを使って現在ログインしているユーザーのマイクロポストを全て取得。
app/models/user.rb
: # 試作feedの定義 # 完全な実装は第12章「ユーザーをフォローする」を参照してください。 def feed Micropost.where("user_id = ?", id) end :
上の疑問符があることで、SQLクエリにインクルードされる前にidが適切にエスケープされることを保証してくれるため、SQLインジェクションと呼ばれる深刻なセキュリティホールを避けることができます。
へー。SQLクエリ使うときは気をつけなきゃいけないのか。。。
サンプルアプリケーションでフィードを使うために、カレントユーザーのページ分割されたフィードに@feed_itemsインスタンス変数を追加し (リスト11.45)、次にフィード用のパーシャル (リスト11.46) をHomeページに追加します。
homeアクションに@feed_items
インスタンス変数を追加。
app/controllers/static_pages_controller.rb
def home if logged_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page]) end end
フィード用のパーシャルはまずファイルを生成して
$ touch app/views/shared/_feed.html.erb
下記内容で更新。
app/views/shared/_feed.html.erb
<% if @feed_items.any? %> <ol class="microposts"> <%= render @feed_items %> </ol> <%= will_paginate @feed_items %> <% end %>
このとき、@feed_itemsの各要素がMicropostクラスを持っていたため、RailsはMicropostのパーシャルの呼び出すことができました。
Micropostクラスを持っていたら@feed_items
で「1つのマイクロポストを表示するパーシャル」を呼び出すことができるんだ…!なんか不思議。
後は、いつものようにフィードパーシャルを表示すればHomeページにフィードを追加できます (リスト11.47)。
マイクロポストの投稿フォームタグの下にフィードを追加。
app/views/static_pages/home.html.erb
<% if logged_in? %> : <div class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> :
動作確認のためにサーバーを起動して
$ rails server -b $IP -p $PORT
ホーム画面にフィードが表示されました。
マイクロポスト投稿もOK!
ただしささいなことではありますが、マイクロポストの投稿が失敗すると、 Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れてしまいます。
試しに空で投稿してみたらエラー画面が。。。
最も簡単な解決方法は、リスト11.48のように空の配列を渡しておくことです。
とりあえずcreateアクションで空の配列渡すようにしておきましょう。
app/controllers/microposts_controller.rb
: else @feed_items = [] render 'static_pages/home' end :
これするとどうなるのかなー、と思ったらフィードが表示されなくなるのか。。。まぁ空の配列渡してるもんね。
今日の学習時間は【28分】。
次は「11.3.4 マイクロポストを削除する」から。
【154日目】【1日20分のRailsチュートリアル】【第11章】マイクロポスト作成フォームを定義する
今日は「11.3.2 マイクロポストを作成する」のマイクロポスト作成フォームを定義するところから。
11.3.2 マイクロポストを作成する
次はマイクロポスト作成フォームを定義します (リスト11.37)。
これもファイルから作成します。
$ touch app/views/shared/_micropost_form.html.erb
できたファイルを下記内容で更新。マイクロポストを入力してPOSTで送信するフォームを定義。
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <% end %>
1つは、(以前と同様) 関連付けを使用して次のように@micropostを定義することです。
homeアクションにマイクロポストのインスタンス変数を追加。
app/controllers/static_pages_controller.rb
def home @micropost = current_user.microposts.build if logged_in? end
リスト11.37を動かすためのもう1つの変更は、エラーメッセージのパーシャルを再定義することです。
(中略)
object: f.objectはerror_messagesパーシャルの中でobjectという変数名を作成してくれるので、この変数を使ってエラーメッセージを更新すればよいということです (リスト11.39)。
うーん、なんか難しい…。
error_messagesパーシャルを汎用的に使えるようにするためのあれこれってことだよね…。
@user
としてたところをobject
に変更。
app/views/shared/_error_messages.html.erb
<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %>. </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
この時点でテストを走らせてみてください。テストが失敗したままになっています。
$ bundle exec rake test ERROR["test_password_resets", PasswordResetsTest, 2017-06-08 11:12:25 +0000] : 57 tests, 286 assertions, 0 failures, 1 errors, 0 skips
失敗しますね。
このパーシャルは他の場所でも使われていたため、ユーザー登録 (リスト7.18)、パスワード再設定 (リスト10.50)、そしてユーザー編集 (リスト9.2) のそれぞれのビューを更新する必要があったのです。
パスワード再設定のやつしかログには出てないけど、いいのかな。
app/views/password_resets/edit.html.erb
: <%= render 'shared/error_messages', object: f.object %> :
試しにパスワード再設定だけ直してテスト実施してみる。
$ bundle exec rake test 57 tests, 294 assertions, 0 failures, 0 errors, 0 skips
…通っちゃったよ!!何故だ……
ユーザー登録のソースコード見てみると、「9.6 演習」のnewフォームとeditフォームをパーシャル化するの対応がmasterに入っちゃってるっぽい。
あれー、演習の内容マージしたっけな…。エラー出てないしこっちは直さなくてもいいのかな。
パーシャルの方でobject: @user
って指定してるから問題ないのかも。でも一応f.object
に統一しておくか。
app/views/users/_form.html.erb
: <%= render 'shared/error_messages', object: f.object %> :
修正してもテストが通ることを確認。
$ bundle exec rake test 57 tests, 294 assertions, 0 failures, 0 errors, 0 skips
さらに、この章で作成したすべてのHTMLが適切に表示されるようになったはずです。
動作確認してみましょう。
サーバーを起動して
$ rails server -b $IP -p $PORT
ホーム画面にアクセスしてみる。
おぉ、マイクロポスト投稿フォームができてる!
空のまま「Post」をクリックしてエラーメッセージが出ることも確認。
今日の学習時間は【35分】。
次は「11.3.3 フィードの原型」から。
【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 プロフィール画面におけるマイクロポストのテスト」から。