0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

tweet投稿アプリの実装

Last updated at Posted at 2022-09-07

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?