tweet投稿アプリの実装

techcampのカリキュラムでtweet投稿アプリを実装したので実装手順をまとめます。

```
1.新規アプリケーション作成

% rails _6.0.0_ new pictweet -d my sql
```
database.ymlの編集
```config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock
```

  ```
データベースを作成 
% rails db:create
```

```gemのバージョン変更を追加
Gemfile

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.5'

 Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.0'
 Use mysql as the database for Active Record
gem 'mysql2', '0.5.3'  # mysqlバージョンの変更
 Use Puma as the app server
gem 'puma', '~> 3.11'

...
（中略）
...

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'pry-rails' #pry-railsの追加
```

```
gemの更新とインストール

% cd ~/projects/pictweet
% bundle install
```

```
2.モデル作成

% rails g model tweet
```

```
テーブル作成
db/migrate/20XXXXXXXXXXXX_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[6.0]
  def change
    create_table :tweets do |t|
      t.string :name  # name text imageカラムを追加
      t.string :text
      t.text :image
      t.timestamps
    end
  end
end

マイグレーション実行
%rails db:migrate
```

3.投稿表示一覧機能の実装
indexアクションのルーティング設定

```
config/routes.rb
Rails.application.routes.draw do
  resources :tweets, only: :index #resourcesでアクションを自動生成
end

コントローラー作成
% rails g controller コントローラー名(複数形)

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  def index
    @tweets = Tweet.all  # @tweetsというインスタンス変数にtweetsテーブルの全てのレコードを代入,ビューに受け渡す
  end
end

ビュー作成
app/views/tweetsディレクトリにindex.html.erbを作成

app/views/tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>  #eachメソッドを使い、全てのツイートを表示
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <p><%= tweet.text %></p>
      <span class="name">
        <%= tweet.name %>
      </span>
    </div>
  <% end %>
</div>
```

ページの共通部分をまとめる

application.html.erbを書き換える(レイアウトテンプレート)
```
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Pictweet</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <header class="header">                               # 全てのページでheaderとfooterを表示
      <div class="header__bar row">
        <h1 class="grid-6"><a href="/">PicTweet</a></h1>
        <div class="user_nav grid-6">
          <a class="post" href="/tweets/new">投稿する</a>
        </div>
      </div>
    </header>
    <%= yield %>
    <footer>
      <p>
        Copyright PicTweet 2019.
      </p>
    </footer>
  </body>
</html>
```
app/assets/stylesheetsディレクトリにstyle.cssを配置
app/assets/imageディレクトリに画像を配置

ルートパス設定
```
config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index' #tweets_controllerのindexアクション
  resources :tweets, only: :index
end
```
4.ツイート投稿機能の実装(new,createアクション)

```
ルーティング設定
config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets, only: [:index, :new] #newアクションの設定
end
```

```
コントローラーにnewアクションを記述

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

end
```
```
投稿画面のビュー作成

app/views/tweetsにnew.html.erbを作成

app/views/tweets/new.html.erb
<div class="contents row">
  <div class="container">
    <h3>投稿する</h3>
    <%= form_with(model: @tweet, local: true) do |form| %> @tweetはnewアクションで定義したインスタンス変数
      <%= form.text_field :name, placeholder: "Nickname" %>
      <%= form.text_field :image, placeholder: "Image Url" %>
      <%= form.text_area :text, placeholder: "text", rows: "10" %>
      <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>

投稿処理実装(createアクションの定義)

config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets, only: [:index, :new, :create] #createアクションを追加
end

コントローラを編集

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params) #フォームから入力されたデータtweet_paramsを引数
  end

  private
  def tweet_params #formから入力された内容なのでストロングパラメーターを設定する
    params.require(:tweet).permit(:name, :image, :text)
  end

end

投稿完了画面のビュー作成
app/views/tweetsにcreate.html.erbを作成

app/views/tweets/create.html.erb
<div class="contents row">
  <div class="success">
    <h3>投稿が完了しました。</h3>
    <a class="btn" href="/">投稿一覧へ戻る</a>
  </div>
</div>

空のツイート登録を防ぐためにバリデーション設定
app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :text, presence: true
end

5.ツイート削除機能実装(destroyアクション)

ルーティング設定

config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets, only: [:index, :new, :create, :destroy]  # destroyアクションの追加
end

削除ボタンを投稿一覧に追加

app/views/tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '削除', tweet_path(tweet.id), method: :delete %>    # DELETEのパスであるtweet_pathにtweet_idをパラメータとして持たせる
          </li>
        </ul
      </div>
      <p><%= tweet.text %></p>
      <span class="name">
        <%= tweet.name %>
      </span>
    </div>
  <% end %>
</div>

destroyアクションをコントローラーに定義

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])  # ビューに値を渡さないためtweetをただの変数として定義、params[:id]はtweet.idから受け取ったid,findでidを元にtweetを探し、destroyアクションで削除する
    tweet.destroy
  end

  private
  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end

end

削除完了画面のビュー作成
app/views/tweetsにdeatroy.html.erbを作成

app/views/tweets/destroy.html.erb
<div class="contents row">
  <div class="success">
    <h3>削除が完了しました。</h3>
    <a class="btn" href="/">投稿一覧へ戻る</a>
  </div>
</div>

6.ツイート編集機能の実装(edit,updateアクション)

editアクションのルーティング設定
config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets, only: [:index, :new, :create, :destroy, :edit] # editアクションを追加
end

編集ボタンを一覧に追加
app/views/tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '編集', edit_tweet_path(tweet.id), method: :get %> # edit_tweetがパス
          </li>
          <li>
            <%= link_to '削除', tweet_path(tweet.id), method: :delete %>
          </li>
        </ul>
      </div>
      <p><%= tweet.text %></p>
      <span class="name">
        <%= tweet.name %>
      </span>
    </div>
  <% end %>
</div>

editアクションをコントローラに定義

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
    @tweet = Tweet.find(params[:id]) #idと一致するツイートを@tweetに入れる
  end

  private
  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end
end

編集画面のビューを作成
app/views/tweetsにedit.html.erbを作成

app/views/tweets/edit.html.erb
<div class="contents row">
  <div class="container">
    <h3>編集する</h3>
    <%= form_with(model: @tweet, local: true) do |form| %>
      <%= form.text_field :name, placeholder: "Nickname" %>
      <%= form.text_field :image, placeholder: "Image Url" %>
      <%= form.text_area :text, placeholder: "text", rows: "10" %>
      <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>

更新処理の実装

updateアクションのルーティング設定

config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets, only: [:index, :new, :create, :destroy, :edit, :update] #updateアクションの追加
end

updateアクションをコントローラーに追加
app/controllers/tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
    @tweet = Tweet.find(params[:id])
  end

  def update                        # ビューファイルにツイート情報を受け渡さないのでインスタンス変数は使用しない。
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  private

  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end
end

更新完了画面のビュー作成
app/views/tweetsディレクトリにupdate.html.erbを作成

app/views/tweets/update.html.erb
<div class="contents row">
  <div class="success">
    <h3>更新が完了しました。</h3>
    <a class="btn" href="/">投稿一覧へ戻る</a>
  </div>
</div>

7.ツイートの詳細表示機能の実装(show)
 showアクションのルーティング設定
 config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets, only: [:index, :new, :create, :destroy, :edit, :update, :show] # showアクションの追加
end

config/routes.rb
Rails.application.routes.draw do
  root to: 'tweets#index'
  resources :tweets # 7つアクションが揃ったのでまとめる
end

一覧表示に詳細ボタンを追加

app/views/tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '詳細', tweet_path(tweet.id), method: :get %> #パスはtweet
          </li>
          <li>
            <%= link_to '編集', edit_tweet_path(tweet.id), method: :get %>
          </li>
          <li>
            <%= link_to '削除', tweet_path(tweet.id), method: :delete %>
          </li>
        </ul>
      </div>
      <p><%= tweet.text %></p>
      <span class="name">
        <%= tweet.name %>
      </span>
    </div>
  <% end %>
</div>

showアクションをコントローラーに追加

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
    @tweet = Tweet.find(params[:id])
  end

  def update
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  def show
    @tweet = Tweet.find(params[:id]) #詳細表示したいツイートの情報をビューに渡す
  end

  private

  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end
end

詳細画面のビューを作成

app/views/tweetsディレクトリにshow.html.erbを作成

app/views/tweets/show.html.erb
<div class="contents row">
  <div class="content_post" style="background-image: url(<%= @tweet.image %>);">
    <div class="more">
      <span><%= image_tag 'arrow_top.png' %></span>
      <ul class="more_list">
        <li>
          <%= link_to '編集', edit_tweet_path(@tweet.id), method: :get %>
        </li>
        <li>
          <%= link_to '削除', tweet_path(@tweet.id), method: :delete %>
        </li>
      </ul>
    </div>
    <p><%= @tweet.text %></p>
    <span class="name"><%= @tweet.name %>
    </span>
  </div>
</div>

同じアクションをまとめる

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]   # @tweet = Tweet.find(params[:id])をset_tweetアクションにまとめる

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
  end

  def update
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  def show
  end

  private

  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end
end

8.ユーザー管理機能の実装(deviseを使用したログイン管理機能)

gemfileを編集

Gemfile
# 中略
gem 'devise'

% bundle install #gemをインストール

devise設定ファイルのインストール
% rails g devise:install

deviseのuserモデルの作成

% rails g devise user

作成されたマイグレーションファイルを開く

db/migrate/20XXXXXXXXX_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
 def change
   create_table :users do |t|
     ## Database authenticatable
     t.string :email,              null: false, default: ""
     t.string :encrypted_password, null: false, default: ""

     ## Recoverable
     t.string   :reset_password_token
     t.datetime :reset_password_sent_at

     ## Rememberable
     t.datetime :remember_created_at

     # 省略

     t.timestamps null: false
   end

   add_index :users, :email,                unique: true
   add_index :users, :reset_password_token, unique: true
   # add_index :users, :confirmation_token,   unique: true
   # add_index :users, :unlock_token,         unique: true
 end
end

マイグレーション実行
% rails db:migrate

deviseのビューファイルを作成

% rails g devise:views

ビューファイルの編集

ログイン画面の編集
app/views/devise/sessions/new.html.erb
<div class="contents row">
  <div class="container">
    <h2>Log in</h2>
    <%= form_with model: @user, url: user_session_path, id: 'new_user', class: 'new_user', local: true do |f| %>
      <div class="field">
        <%= f.label :email %><br />
        <%= f.email_field :email, autofocus: true %>
      </div>

      <div class="field">
        <%= f.label :password %><br />
        <%= f.password_field :password, autocomplete: "off" %>
      </div>

      <% if devise_mapping.rememberable? %>
        <div class="field">
          <%= f.label :remember_me %><br />
          <%= f.check_box :remember_me %>
        </div>
      <% end -%>

      <div class="actions">
        <%= f.submit "Log in" %>
      </div>
    <% end %>
  </div>
</div>

サインアップ画面の編集
app/views/devise/registrations/new.html.erb
<div class="contents row">
  <div class="container">
    <h2>Sign up</h2>
    <%= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user', local: true do |f| %>
      <%= render "devise/shared/error_messages", resource: resource %>

      <div class="field">
        <%= f.label :email %><br />
        <%= f.email_field :email %>
      </div>

      <div class="field">
        <%= f.label :password %>
        <% if @minimum_password_length %>
          <em>(<%= @minimum_password_length %> characters minimum)</em>
        <% end %><br />
        <%= f.password_field :password, autocomplete: "off" %>
      </div>

      <div class="field">
        <%= f.label :password_confirmation %><br />
        <%= f.password_field :password_confirmation, autocomplete: "off" %>
      </div>

      <div class="actions">
        <%= f.submit "Sign up" %>
      </div>
    <% end %>
  </div>
</div>

サインアップ(新規ユーザー登録)時に登録する情報にニックネームを追加(ニックネームカラムの追加)
% rails g migration AddNicknameToUsers nickname:string  # nicknameカラムをstring型で追加
% rails db:migrate

新規登録画面の編集
app/views/devise/registrations/new.html.erb
<div class="contents row">
  <div class="container">
    <h2>Sign up</h2>
    <%= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user', local: true do |f| %> 
      <%= render "devise/shared/error_messages", resource: resource %>

      <div class="field">
        <%= f.label :nickname %> <em>(6 characters maximum)</em><br />
        <%= f.text_field :nickname, autofocus: true, maxlength: "6" %> # nicknameの上限を6文字に指定
      </div>

      <div class="field">
        <%= f.label :email %><br />
        <%= f.email_field :email %>
      </div>

      <div class="field">
        <%= f.label :password %>
        <% if @minimum_password_length %>
          <em>(<%= @minimum_password_length %> characters minimum)</em>
        <% end %><br />
        <%= f.password_field :password, autocomplete: "off" %>
      </div>

      <div class="field">
        <%= f.label :password_confirmation %><br />
        <%= f.password_field :password_confirmation, autocomplete: "off" %>
      </div>

      <div class="actions">
        <%= f.submit "Sign up" %>
      </div>
    <% end %>
  </div>
</div>

deviseにストロングパラメータを設定

app/controllers/application_controller.rb (大元のコントローラー)
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters #deviseモデルからパラメーターを取得できる
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end

ログインの有無で表示を変える

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Pictweet</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <header class="header">
      <div class="header__bar row">
        <h1 class="grid-6"><a href="/">PicTweet</a></h1>
        <% if user_signed_in? %>  #ログインしているかどうか
          <div class="user_nav grid-6">
            <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
            <%= link_to "投稿する", new_tweet_path, class: "post" %>
          </div>
        <% else %>
          <div class="grid-6">
            <%= link_to "ログイン", new_user_session_path, class: "post" %>
            <%= link_to "新規登録", new_user_registration_path, class: "post" %>
          </div>
        <% end %>
      </div>
    </header>
    <%= yield %>
    <footer>
      <p>
        Copyright PicTweet 2019.
      </p>
    </footer>
  </body>
</html>

未ログインのユーザーを転送

リダイレクト処理を用意
app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show]

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
  end

  def update
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  def show
  end

  private
  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def move_to_index   # ログインしていない場合はindexに飛ばす
    unless user_signed_in?
      redirect_to action: :index
    end
  end
end

9.ユーザーのマイページの実装

tweetsテーブルにカラムを追加

% rails g migration AddUserIdToTweets user_id:integer

ツイート保存時にユーザー情報も追加する
app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show]

  def index
    @tweets = Tweet.all
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
  end

  def update
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  def show
  end

  private
  def tweet_params
    params.require(:tweet).permit(:name, :image, :text).merge(user_id: current_user.id) # tweetに現在のユーザーのidを統合
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def move_to_index
    unless user_signed_in?
      redirect_to action: :index
    end
  end
end

アソシエーションを定義

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :tweets #userは複数のtweetを持つ
end

app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :text, presence: true
  belongs_to :user #userに帰属
end

ユーザーに関するshowアクションのルーティングを設定

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'tweets#index'
  resources :tweets
  resources :users, only: :show #追加
end

マイページボタンを投稿一覧に表示

app/views/layouts/application.html.erb
  # 省略
  <header class="header">
    <div class="header__bar row">
      <h1 class="grid-6"><a href="/">PicTweet</a></h1>
      <% if user_signed_in? %>
        <div class="user_nav grid-6">
          <span><%= current_user.nickname %>
            <ul class="user__info">
              <li>
                <%= link_to "マイページ", "/users/#{current_user.id}" %>
                <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
              </li>
            </ul>
          </span>
          <%= link_to "投稿する", new_tweet_path, class: "post" %>
        </div>
      <% else %>
        <div class="grid-6">
          <%= link_to "ログイン", new_user_session_path, class: "post" %>
          <%= link_to "新規登録", new_user_registration_path, class: "post" %>
        </div>
      <% end %>
    </div>
  </header>
  # 省略

  ユーザーに関するshowアクションをコントローラーに作成

  userscontrollerを作成
  % rails g controller users

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @nickname = current_user.nickname
    @tweets = current_user.tweets
  end
end

マイページのビューを作成
app/views/usersディレクトリにshow.html.erbを作成

app/views/users/show.html.erb
<div class="contents row">
  <p><%= @nickname %>さんの投稿一覧</p>
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <p><%= tweet.text %></p>
      <span class="name"><%= tweet.name %></span>
    </div>
  <% end %>
</div>

投稿者名を表示するようにビューを変更する

app/views/tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '詳細', tweet_path(tweet.id), method: :get %>
          </li>
          <li>
            <%= link_to '編集', edit_tweet_path(tweet.id), method: :get %>
          </li>
          <li>
            <%= link_to '削除', tweet_path(tweet.id), method: :delete %>
          </li>
        </ul>
      </div>
      <p><%= tweet.text %></p>
      <span class="name">
        <a href="/users/<%= tweet.user.id %>">
          <span>投稿者</span><%= tweet.user.nickname %>
        </a>
      </span>
    </div>
  <% end %>
</div>

tweet詳細画面も変更する
app/views/tweets/show.html.erb
<div class ="contents row">
  <div class="content_post" style="background-image: url(<%= @tweet.image %>);">
    <div class="more">
      <span><%= image_tag 'arrow_top.png' %></span>
      <ul class="more_list">
        <li>
          <%= link_to '編集', edit_tweet_path(@tweet.id), method: :get %>
        </li>
        <li>
          <%= link_to '削除', tweet_path(@tweet.id), method: :delete %>
        </li>
      </ul>
    </div>
    <p><%= @tweet.text %></p>
    <span class="name">
      <a href="/users/<%= @tweet.user.id %>">
        <span>投稿者</span><%= @tweet.user.nickname %>
      </a>
    </span>
  </div>
</div>

N+1問題を解消
app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show]

  def index
    @tweets = Tweet.includes(:user)
  end

  #中略

end

投稿画面のビューを変更

app/views/tweets/new.html.erb
<div class="contents row">
  <div class="container">
    <%= form_with(model: @tweet, local: true) do |form| %>
      <h3>投稿する</h3>
      <%= form.text_field :name, placeholder: "Nickname" %>  # この5行目を削除
      <%= form.text_field :image, placeholder: "Image Url" %>
      <%= form.text_area :text, placeholder: "text", rows: "10" %>
      <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>

app/views/tweets/edit.html.erb
<div class="contents row">
  <div class="container">
    <%= form_with(model: @tweet, local: true) do |form| %>
      <h3>編集する</h3>
      <%= form.text_field :name, placeholder: "Nickname" %>  # この5行目を削除
      <%= form.text_field :image, placeholder: "Image Url" %>
      <%= form.text_area :text, placeholder: "text", rows: "10" %>
      <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>

投稿時にnameを入力する必要も無くなったのでコントローラの処理も変更

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show]

  def index
    @tweets = Tweet.includes(:user)
  end

  def new
    @tweet = Tweet.new
  end

  def create
    Tweet.create(tweet_params)
  end

  def destroy
    tweet = Tweet.find(params[:id])
    tweet.destroy
  end

  def edit
  end

  def update
    tweet = Tweet.find(params[:id])
    tweet.update(tweet_params)
  end

  def show
  end

  private
  def tweet_params
    params.require(:tweet).permit(:image, :text).merge(user_id: current_user.id)
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def move_to_index
    unless user_signed_in?
      redirect_to action: :index
    end
  end
end

tweetsテーブルから不要なカラムも削除

% rails g migration RemoveNameFromTweets name:string
% rails db:migrate

削除されたnameカラムをtweet.nameと記述し利用している文は、1行まるごと削除

app/views/users/show.html.erb
<div class="contents row">
  <p><%= @nickname %>さんの投稿一覧</p>
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <p><%= tweet.text %></p>
      <span class="name"><%= tweet.name %></span>  # この行を削除
    </div>
  <% end %>
</div>

投稿者のマイページにアクセスできるようにuserコントローラーを修正

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    user = User.find(params[:id])
    @nickname = user.nickname
    @tweets = user.tweets
  end
end

10.ユーザーログインの有無で表示を変える

「ユーザーがログインしている」かつ「投稿したユーザーである投稿だけに許可」という実装

編集ボタンと削除ボタンの表示部分をif文で囲います。

app/views/tweets/index.html.erb
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <div class="content_post" style="background-image: url(<%= tweet.image %>);">
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '詳細', tweet_path(tweet.id), method: :get %>
          </li>
          <% if user_signed_in? && current_user.id == tweet.user_id %> #ここから
            <li>
              <%= link_to '編集', edit_tweet_path(tweet.id), method: :get %>
            </li>
            <li>
              <%= link_to '削除', tweet_path(tweet.id), method: :delete %>
            </li>
          <% end %>  # ここまで
        </ul>
      </div>
      <p><%= tweet.text %></p>
      <span class="name">
        <a href="/users/<%= tweet.user.id %>">
          <span>投稿者</span><%= tweet.user.nickname %>
        </a>
      </span>
    </div>
  <% end %>
</div>

詳細ページも同様に編集

app/views/tweets/show.html.erb
<div class="contents row">
  <div class="content_post" style="background-image: url(<%= @tweet.image %>);">
    <% if user_signed_in? && current_user.id == @tweet.user_id %>
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '編集', edit_tweet_path(@tweet.id), method: :get %>
          </li>
          <li>
            <%= link_to '削除', tweet_path(@tweet.id), method: :delete %>
          </li>
        </ul>
      </div>
    <% end %>
    <p><%= @tweet.text %></p>
    <span class="name">
      <a href="/users/<%= @tweet.user.id %>">
        <span>投稿者</span><%= @tweet.user.nickname %>
      </a>
    </span>
  </div>
</div>

11 ツイートの最新順表示

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show]

  def index
    @tweets = Tweet.includes(:user).order("created_at DESC") # ASC(昇順) DESC(降順)
  end

  def new
    @tweet = Tweet.new
  end

  # 省略
11 ツイートへのコメント機能の実装

% rails g model comment

マイグレーション編集

db/migrate/20XXXXXXXXXXXX_create_comments.rb
class CreateComments < ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.integer :user_id
      t.integer :tweet_id
      t.text :text
      t.timestamps
    end
  end
end

% rails db:migrate

アソシエーションを定義
app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :tweet  # tweetsテーブルとのアソシエーション
  belongs_to :user  # usersテーブルとのアソシエーション
end

app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :text, presence: true
  belongs_to :user
  has_many :comments  # commentsテーブルとのアソシエーション
end

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :tweets
  has_many :comments  # commentsテーブルとのアソシエーション
end

ルーティング設定

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'tweets#index'
  resources :tweets do # あるツイートに対してのコメントなのでネストを組む
    resources :comments, only: :create
  end
  resources :users, only: :show
end

commentコントローラーを作成

% rails g controller comments

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    Comment.create(comment_params)
  end

  private
  def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id]) # user_idカラムには、ログインしているユーザーのidとなるcurrent_user.idを保存し、tweet_idカラムは、paramsで渡されるようにするので、params[:tweet_id]として保存しています。
  end
end

コメントを保存したのち、コメント完了画面を表示させるのではなく、
コメントしたツイートの詳細画面へリダイレクトさせる

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    comment = Comment.create(comment_params)
    redirect_to "/tweets/#{comment.tweet.id}"  # コメントと結びつくツイートの詳細画面に遷移する
  end

  private
  def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
  end
end

コメント投稿用のフォームを作成

app/views/tweets/show.html.erb
<div class="contents row">
  <div class="content_post" style="background-image: url(<%= @tweet.image %>);">
    <% if user_signed_in? && current_user.id == @tweet.user_id %>
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '編集', edit_tweet_path(@tweet.id), method: :get %>
          </li>
          <li>
            <%= link_to '削除', tweet_path(@tweet.id), method: :delete %>
          </li>
        </ul>
      </div>
    <% end %>
    <p><%= @tweet.text %></p>
    <span class="name">
      <a href="/users/<%= @tweet.user.id %>">
        <span>投稿者</span><%= @tweet.user.nickname %>
      </a>
    </span>
  </div>
  <div class="container">
    <% if user_signed_in? %> #ここから
      <%= form_with(model: [@tweet, @comment], local: true) do |form| %>
        <%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
        <%= form.submit "SEND" %>
      <% end %>
    <% else %>
      <strong><p>※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p></strong>
    <% end %>
  </div>
</div>

コメント表示欄を投稿詳細に追加

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]

# 中略

  def show
    @comment = Comment.new
    @comments = @tweet.comments.includes(:user) #全commentを取得　ビューに送る
  end

  private
  def tweet_params
    params.require(:tweet).permit(:image, :text).merge(user_id: current_user.id)
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def move_to_index
    unless user_signed_in?
      redirect_to action: :index
    end
  end
end

ビュー設定

app/views/tweets/show.html.erb
<div class="contents row">
  <div class="content_post" style="background-image: url(<%= @tweet.image %>);">
    <% if user_signed_in? && current_user.id == @tweet.user_id %>
      <div class="more">
        <span><%= image_tag 'arrow_top.png' %></span>
        <ul class="more_list">
          <li>
            <%= link_to '編集', edit_tweet_path(@tweet.id), method: :get %>
          </li>
          <li>
            <%= link_to '削除', tweet_path(@tweet.id), method: :delete %>
          </li>
        </ul>
      </div>
    <% end %>
    <p><%= @tweet.text %></p>
    <span class="name">
      <a href="/users/<%= @tweet.user.id %>">
        <span>投稿者</span><%= @tweet.user.nickname %>
      </a>
    </span>
  </div>
  <div class="container">
    <% if user_signed_in? %>
      <%= form_with(model: [@tweet, @comment], local: true) do |form| %>
        <%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
        <%= form.submit "SEND" %>
      <% end %>
    <% else %>
      <strong><p>※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p>
      </strong>
    <% end %>
    <div class="comments">
      <h4>＜コメント一覧＞</h4>
      <% @comments.each do |comment| %>
        <p>
          <strong><%= link_to comment.user.nickname, "/users/#{comment.user_id}" %>：</strong>
          <%= comment.text %>
        </p>
      <% end %>
    </div>
  </div>
</div>
```