マイクロポストコントローラのアクションを指定する
config/routes.rb
Rails.application.routes.draw do
get 'password_resets/new'
get 'password_resets/edit'
get 'sessions/new'
root 'static_pages#home'
get '/signup', to: 'users#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
# マイクロポストコントローラはcreate,destroyアクションだけ行える
end
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
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)
# マイクロポストを削除する
# createアクション microposts_path
end
assert_redirected_to root_url
# ホーム画面にリダイレクトされているか確認
end
end
みんながlogged_in_userを使えるようにする
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include SessionsHelper
private
def logged_in_user
# ユーザーのログインを確認する
# 各コントローラが継承するApplicationコントローラに、このメソッドを移してしまいましょう。
unless logged_in?
# ログインされていないか?
store_location
# アクセスしようとしたURLを覚えておく
flash[:danger] = "Please log in."
# メッセージを表示
redirect_to login_url
# ログインページにリダイレクト
end
end
end
logged_in_userを削除する
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
@users = User.paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
@microposts = @user.microposts.paginate(page: params[:page])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
@user.send_activation_email
# メソッドを使ってメールを送信する
flash[:info] = "メールをチェックしてアカウントを有効にしてください。"
redirect_to root_url
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
flash[:success] = "プロフィールを更新しました。"
redirect_to @user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "ログアウトされました。"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# logged_in_userを削除
# 正しいユーザーかどうか確認
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless @user == current_user
end
# 管理者かどうか確認
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
application_controllerから継承される
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
# 作成、削除を行う前にlogged_in_userメソッドを行う
def create
@micropost = current_user.microposts.build(micropost_params)
# @micropostに紐付いた新しいMicropostオブジェクトを返す
# @micropost インスタンス変数
# .build()
# newメソッドと違いはありません
# モデルの関連付けの際はbuildメソッドを使う約束があるようです。
# わからない
if @micropost.save
# マイクロポストが保存された時は
flash[:success] = "Micropost created!"
# メッセージ表示
redirect_to root_url
# ホーム画面にリダイレクト
else
@feed_items = current_user.feed.paginate(page: params[:page])
# マイクロポストのページネーションを作成
render 'static_pages/home'
# それ以外はホーム画面に移動する
end
end
def destroy
@micropost.destroy
# インスタンス変数を削除
flash[:success] = "Micropost deleted"
# メッセージを表示
redirect_to request.referrer || root_url
# 遷移前のページのURLによってページに表示するリンク先
# または
# ホーム画面にリダイレクト
# マイクロポストがHomeページから削除された場合でもプロフィールページから削除された場合でも、request.referrerを使うことでDELETEリクエストが発行されたページに戻すことができるので、非常に便利です。
end
private
def micropost_params
params.require(:micropost).permit(:content)
# マイクロポストオブジェクトはcontent属性しか変更できないと設定
end
def correct_user
@micropost = current_user.microposts.find_by(id: params[:id])
# ログイン中のユーザーのマイクロポストを探す
redirect_to root_url if @micropost.nil?
# マイクロポストが空ならホーム画面にリダイレクト
end
end
マイクロポストが作成、削除されるか?
テストをする
ubuntu:~/environment/my_app (user-microposts) $ rails t
Running via Spring preloader in process 10397
Run options: --seed 31784
# Running:
...........................................
Finished in 7.523936s, 5.7151 runs/s, 34.1577 assertions/s.
43 runs, 257 assertions, 0 failures, 0 errors, 0 skips
###app/views/static_pages/home.html.erb
<% if logged_in? %>
<!--ログインされていたら?
Homeページ(/)にマイクロポストの投稿フォームを追加する-->
<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>マイクロポストフィード</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
<div class="center jumbotron">
<h1>ようこそ自作アプリへ</h1>
<h2>
出来事を書き込もう。
</h2>
<%= link_to "新規登録をしよう!", signup_path , class: "btn btn-lg btn-primary" %>
</div>
<%= image_tag "jisaku.jpg", size: '300x150' %>
<% 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>
<!--名詞.pluralize([個数, ロケール])
"micropost"は複数形になる
例 50microposts と表示される-->
マイクロポスト投稿フォームのパーシャル
app/views/shared/_micropost_form.html.erb
<%= form_with(model: @micropost, local: true) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
<!--テキスト入力欄-->
</div>
<%= f.submit "投稿", class: "btn btn-primary" %>
<!--投稿ボタン-->
<% end %>
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])
# user_idのマイクロポストをページネーションを作成する
end
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 %>
app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>プロフィールを編集する</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user, local: true) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "編集内容を保存", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
</div>
マイクロポストのステータスフィードを実装するための準備
app/models/user.rb
class User < ApplicationRecord
# 継承させる
has_many :microposts, dependent: :destroy
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
# コストパラメータはテストでは最小にする
BCrypt::Password.create(string, cost: cost)
end
# ランダムなトークンを返す
def User.new_token
SecureRandom.urlsafe_base64
end
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# ユーザーのログイン情報を破棄する
def forget
update_attribute(:remember_digest, nil)
end
def activate
# # アカウントを有効にする
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# 有効化用のメールを送信する
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# パスワード再設定の属性を設定する
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# パスワード再設定のメールを送信する
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# パスワード再設定の期限が切れている場合はtrueを返す
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
# 試作feedの定義
# 完全な実装は次章の「ユーザーをフォローする」を参照
def feed
Micropost.where("user_id = ?", id)
# 変数idと同じ値をもつマイクロポストを取り出す
#where()
# モデル名.where("条件")
# テーブル内の条件に一致したレコードを配列の形で取得することができる
# ? の意味はわからない
# エスケープされセキュリティホールを避けるため
end
private
# メールアドレスをすべて小文字にする
def downcase_email
self.email = email.downcase
end
# 有効化トークンとダイジェストを作成および代入する
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
ステータスフィードのパーシャル
app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
<!--もしモデル(マイクロポスト)にデータが存在する-->
<ol class="microposts">
<%= render @feed_items %>
<!--マイクロポストを表示させる-->
</ol>
<%= will_paginate @feed_items,
params: { controller: :static_pages, action: :home } %>
<!--will_paginate
表示件数を選択できるページネーションを実装する
params: { controller: :static_pages, action: :home }
ページネーションの2を押すとエラーを起こすが、
Homeページに対応するcontrollerパラメータとaction パラメータを明示的にwill_paginateに渡すことで解決できます。-->
<% end %>
マクロポストに削除リンクをつける
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.
<!--作成日時前と表示されるのか?
time_ago_in_words()
日時を表示
micropost.created_at
マイクロポスト作成日時-->
<% if current_user?(micropost.user) %>
<!--ログイン中だったら
micropost.user
Micropostに紐付いたUserオブジェクトを返す-->
<%= link_to "削除", micropost, method: :delete,
data: { confirm: "You sure?" } %>
<!--マイクロポストを削除する-->
<% end %>
</span>
</li>
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: https://tauday.com"
created_at: <%= 3.years.ago %>
user: michael
cat_video:
content: "Sad cats are sad: https://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(word_count: 5) %>
created_at: <%= 42.days.ago %>
user: michael
<% end %>
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
インテグレーションテストを生成する
ubuntu:~/environment/my_app (user-microposts) $ rails generate integration_test microposts_interface
Running via Spring preloader in process 4394
invoke test_unit
create test/integration/microposts_interface_test.rb
マイクロポストのUIに対する統合テスト
test/integration/microposts_interface_test.rb
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
# テストユーザー
end
test "micropost interface" do
log_in_as(@user)
# ログイン
get root_path
# ホーム画面に移動を要求
assert_select 'div.pagination'
# divタグにpaginationが表示されているか確認
assert_no_difference 'Micropost.count' do
# マイクロポストの数が変わらないことを確認 無効
post microposts_path, params: { micropost: { content: "" } }
# 中身が空のマイクロポストを投稿
end
assert_select 'div#error_explanation'
# _error_messages.html.erbに<div id="error_explanation">が含まれているか確認
assert_select 'a[href=?]', '/?page=2'
# 正しいページネーションリンク
# 2ページのリンクが表示されているか確認
content = "This micropost really ties the room together"
# テストcontent
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
# コンテントとbodyの内容が一致するか確認
# assert_match(正規表現, 文字列 [, メッセージ])
# 正規表現に文字列がマッチすれば成功
# response.body
# body は Response インターフェイスの読み取り専用プロパティ
assert_select 'a', text: 'delete'
# リンクでdeleteが表示されているか確認
first_micropost = @user.microposts.paginate(page: 1).first
# 1ページの最初のマイクロポストを返す
assert_difference 'Micropost.count', -1 do
# マイクロポストが一つ減っているか確認
delete micropost_path(first_micropost)
# マイクロポストを削除
end
get user_path(users(:archer))
# 違うユーザーのプロフィールにアクセス(削除リンクがないことを確認)
assert_select 'a', text: 'delete', count: 0
# deleteリンクが表示されていないか確認
end
end