今日やったこと15
13.3 マイクロポストを操作する
マイクロポストリソースのルーティングを設定する。
resources :microposts, only: [:create, :destroy]
HTTPリクエスト | URL | アクション | 名前付きルート |
POST | /microposts | create | microposts_path |
DELETE | /microposts/1 | destroy | micropost_path(micropost) |
関連付けられたユーザーを通してマイクロポストにアクセスするので、createアクションやdestroyアクションを利用するユーザーは、ログイン済みでなければならない。正しいリクエストを各アクションに向けて発行し、マイクロポストの数が変化していないかどうか、また、リダイレクトされるかどうかをテストする。
test/controllers/microposts_controller_test.rb require 'test_helper' class MicropostsControllerTest < ActionDispatch::IntegrationTest def setup @micropost = microposts(:orange) end test "should redirect create when not logged in" do assert_no_difference 'Micropost.count' do post microposts_path, params: { 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 micropost_path(@micropost) end assert_redirected_to login_url end end
テストは失敗。
Micropostsコントローラでもlogged_in_userメソッドを使えるようにするために、各コントローラが継承するApplicationコントローラに 、このメソッドを移す。
コードが重複しないよう、このときUsersコントローラからもlogged_in_userを削除しておく。
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
Micropostsコントローラの各アクションでlogged_in_userメソッドを使えるようにする。
app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create end def destroy end end
再びテストし成功。
マイクロポストを作成する.
マイクロポストのcreateアクションを作っていく。
app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] 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 def destroy end private def micropost_params params.require(:micropost).permit(:content) end end
マイクロポスト作成フォームを構築するために、if-else文の分岐を使ってサイト訪問者がログインしているかどうかに応じてコードを書き分ける。
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 %> <div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> </div> <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %> <% end %>
パーシャルを作る。
サイドバーで表示するユーザー情報のパーシャル
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>
マイクロポスト投稿フォームのパーシャル
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 %>
フォームが動くようにするために@micropostを定義する.。
app/controllers/static_pages_controller.rb class StaticPagesController < ApplicationController def home @micropost = current_user.microposts.build if logged_in? end def help end def about end def contact end end
エラーメッセージのパーシャルを再定義する。
Userオブジェクト以外でも動作するようにerror_messagesパーシャルを更新する。
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 %>
このパーシャルは他の場所でも使われているので、それぞれのビューでも使えるように更新する。
これでマイクロポスト作成フォームが表示できる。
Homeページに投稿したマイクロポストを表示する部分を実装する。
Userモデルにfeedメソッドを作る。
app/models/user.rb class User < ApplicationRecord . . . def feed Micropost.where("user_id = ?", id) end private . . . end
サンプルアプリケーションでフィードを使うために、現在のユーザーのページ分割されたフィードに@feed_itemsインスタンス変数を追加して、フィード用のパーシャルをHomeページに追加する。
app/controllers/static_pages_controller.rb class StaticPagesController < ApplicationController def home if logged_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page]) end end . . . end
app/views/shared/_feed.html.erb <% if @feed_items.any? %> <ol class="microposts"> <%= render @feed_items %> </ol> <%= will_paginate @feed_items %> <% end %>
パーシャルで、renderに@feed_itemsを渡しているのは、Railsが対応する名前のパーシャルを、渡されたリソースのディレクトリ内から探しにいくことができるため。
Homeページにステータスフィードを追加する。
app/views/static_pages/home.html.erb <% if logged_in? %> <%= render 'shared/home_login' %> <% else %> <%= render 'shared/home_logout' %> <% end %>
app/views/shared/_home_login.html.erb <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 class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div>
マイクロポストを削除する
自分が投稿したマイクロポストに対してのみ削除リンクが動作するように機能を追加する。
最初にマイクロポストのパーシャルに削除リンクを追加。
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. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> </span> </li>
次に、Micropostsコントローラのdestroyアクションを定義する。
app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy . . . def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end private def micropost_params params.require(:micropost).permit(:content) end def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end end
フィード画面のマイクロポストをテストする
まずはマイクロポスト用のfixtureに、別々のユーザーに紐付けられたマイクロポストを追加していく。
test/fixtures/microposts.yml . . . ants: content: "Oh, is that what you want? Because that's how you get ants!" created_at: <%= 2.years.ago %> user: archer zone: content: "Danger zone!" created_at: <%= 3.days.ago %> user: archer tone: content: "I'm sorry. Your words made sense, but your sarcastic tone did not." created_at: <%= 10.minutes.ago %> user: lana van: content: "Dude, this van's, like, rolling probable cause." created_at: <%= 4.hours.ago %> user: lana
次に、自分以外のユーザーのマイクロポストは削除をしようとすると、適切にリダイレクトされることをテストする。
test/controllers/microposts_controller_test.rb require 'test_helper' class MicropostsControllerTest < ActionDispatch::IntegrationTest def setup @micropost = microposts(:orange) end . . . test "should redirect destroy for wrong micropost" do log_in_as(users(:michael)) micropost = microposts(:ants) assert_no_difference 'Micropost.count' do delete micropost_path(micropost) end assert_redirected_to root_url end end
最後に統合テストを書くためにファイルを生成。
$ rails generate integration_test microposts_interface
統合テストでは、ログイン、マイクロポストのページ分割の確認、無効なマイクロポストを投稿、有効なマイクロポストを投稿、マイクロポストの削除、そして他のユーザーのマイクロポストには [delete] リンクが表示されないことを確認、といった順でテストしていく。
test/integration/microposts_interface_test.rb require 'test_helper' class MicropostsInterfaceTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end # マイクロポストのUIに対する統合テスト test "micropost interface" do log_in_as(@user) get root_path assert_select 'div.pagination' # 無効な送信 assert_no_difference 'Micropost.count' do post microposts_path, params: { micropost: { content: "" } } end assert_select 'div#error_explanation' # 有効な送信 content = "This micropost really ties the room together" assert_difference 'Micropost.count', 1 do post microposts_path, params: { micropost: { content: content } } end assert_redirected_to root_url follow_redirect! assert_match content, response.body # 投稿を削除する assert_select 'a', text: 'delete' first_micropost = @user.microposts.paginate(page: 1).first assert_difference 'Micropost.count', -1 do delete micropost_path(first_micropost) end # 違うユーザーのプロフィールにアクセス (削除リンクがないことを確認) get user_path(users(:archer)) assert_select 'a', text: 'delete', count: 0 end end