LoginSignup
1
0

More than 1 year has passed since last update.

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

Posted at

マイクロポストコントローラのアクションを指定する

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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0