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>