自作アプリを作る マイクロポスト

## マイクロポストコントローラのアクションを指定する
### config/routes.rb
```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
```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
```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
```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
```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
```html
<% 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
```html
<%= 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"は複数形になる
    例 ５０microposts と表示される-->
```

## マイクロポスト投稿フォームのパーシャル
### app/views/shared/_micropost_form.html.erb
```html
<%= 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
```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
```html
<% 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
```html
<% 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
```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
```html
<% 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 }
　　     　　　　　　ページネーションの２を押すとエラーを起こすが、
         Homeページに対応するcontrollerパラメータとaction パラメータを明示的にwill_paginateに渡すことで解決できます。-->
<% end %>
```
## マクロポストに削除リンクをつける
### app/views/microposts/_micropost.html.erb
```html
<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
```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
```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
    #　１ページの最初のマイクロポストを返す
    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
```