hiyoko-programingの日記

プログラミングを勉強したてのひよっ子。   エンジニア目指して勉強中。

Pictweetの作成 その3

13:ツイートの詳細画面を表示

現在の実装では、ツイートをフィールドに一覧に表示させることはできてもツイッターフェイスブックのようにその投稿の詳細画面(=個別ページ)はない。そこで、ツイートに詳細ボタンを追加して、ツイートの詳細ページに遷移できるようにする。手順はユーザーのマイページを実装した時とほぼ同じで、以下のとおり。

 作業内容

1.ツイート詳細画面のルーティングを記述する
2.tweets controllerにアクションを作成する
3.ツイート詳細画面のビューを追加する
4.ツイート詳細画面へのリンクを作成する

1.ツイート詳細画面へのルーティングを記述

まずはルーティングから記述していく。ツイート詳細画面のルーティングはマイページのルーティングに非常に似ている。こちらもまたツイート毎にパスが異なり、idが1のツイートの詳細画面のパスは/tweets/1のようなパスになる。ツイートに関するアクションなのでtweetsコントローラを使う。アクション名はマイページ同様showアクションを使う。

config/routes.rb
1
2
3
  Rails.application.routes.draw do
    get     '/コントローラ名/:id'        => 'コントローラ名#show'
  end

11行目を追記する。

config/routes.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Rails.application.routes.draw do
  devise_for :users
  root 'tweets#index'               
  get   'tweets'      =>  'tweets#index'
  get 'tweets/new' => 'tweets#new'     
  post 'tweets' => 'tweets#create'  
  get 'users/:id' => 'users#show'    
  delete 'tweets/:id' => 'tweets#destroy'  
  get 'tweets/:id/edit' => 'tweets#edit' 
  patch 'tweets/:id'  => 'tweets#update'
  get 'tweets/:id' => 'tweets#show'       #ツイート詳細画面
end

マイページへのルーティング同様ハッシュ形式でツイートのidの値を受け取っている。

2.tweets_controllerにアクションを定義

次はツイート詳細画面に必要な情報をデータベースから取ってくるためのshowアクションを定義していく。
usersコントローラのshowアクションで実装したことを思い出しながら、showアクションを定義する。

 問題14:ツイート詳細ページに表示したい情報をshowアクションに定義する

作業ファイル:app/controllers/tweets_controller.rb
tweets_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class TweetsController < ApplicationController

# 中略

  def show
    @tweet = 詳細ページを見たいツイート
  end

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

  def move_to_index
    redirect_to :action => "index" unless user_signed_in?
  end
end

これでツイート詳細画面に遷移するためのアクションを作ることができた。アクションを定義した後はそれに対応するビューを作っていく。

3.ツイート詳細画面のビューを追加

showアクションを定義したら、次はツイート詳細ページのビューを作っていく。ここではtweetsコントローラのshowアクションに対応するビューファイルを作っていく。

まずは必要なビューファイルを作る。

 新規作成するファイル

  • app/views/tweets/show.html.erb
app/views/tweets/show.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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 '編集', "/tweets/#{@tweet.id}/edit", method: :get %>
        </li>
        <li>
          <%= link_to '削除', "/tweets/#{@tweet.id}", method: :delete %>
        </li>
      </ul>
    </div>
    <% end %>
    <%= simple_format(@tweet.text) %>
    <span class="name">
      <a href="/users/<%= @tweet.user.id %>">
        <span>投稿者</span><%= @tweet.user.nickname %>
      </a>
    </span>
  </div>
</div>

基本的にはviews/tweets/index.html.erbと同じ構造をしている。each文がないことに注意する。

 

4.ツイート詳細画面へのリンクを作成

次はツイート詳細ページに遷移するためのボタンをビューに追加していく。
今回はログインしていないユーザでも詳細ページに遷移できるようにするために、if文の条件分岐の外に詳細ボタンを追加していく。

ツイートの詳細ページはツイート作成者ではなくても見られるようにしたいので、ツイート右上にあるドロップダウンボタンはツイート投稿者でなくとも表示させるようにする必要がある。
あとはlink_toメソッドを使いながらshowアクションへのルーティングが走るボタンを作成する。

 問題15:ツイート詳細ページに行くボタンを作る

作業ファイル:app/views/tweets/index.html.erb
ヒント:showアクションへのパスをterminalで確認し、link_toメソッドを使いながら実装していく。
views/tweets/index.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="content_post" style="background-image: url();">
  <div class="more">
    <span><%= image_tag 'arrow_top.png' %></span>
    <ul class="more_list">
      <li>
        <ツイート詳細画面を呼び出すためのルーティング>
      </li>
      <% if user_signed_in? && current_user.id == tweet.user_id %>
      <li>
         <%= link_to '編集', "/tweets/#{tweet.id}/edit", method: :get %>
      </li>
      <li>
         <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>
      </li>
      <% end %>
    </ul>
  </div>  

/ 中略

</div> 

 

できたらツイート詳細ボタンをクリックして、ツイート詳細画面を確認する。一見ツイート一覧と変わらないが、ツイートが一つしか表示されないような画面が出てきたら成功。

https://tech-master.s3.amazonaws.com/uploads/curriculums//6d38143580af885a277eb763dc6664fa.png

14:ツイートにコメントできるようにする

ツイート詳細画面からツイートに対してコメントができるように実装する。下記のようなものを作っていく。

【例】
https://tech-master.s3.amazonaws.com/uploads/curriculums//06d287d1004c6c72aa617b04c599f3dc.png

コメントはツイートとは別のテーブルで管理しなくてはならない。なので、commentsテーブルを作る必要がある。さらに、コメントはどのツイートに対してのコメントなのか、そして誰のコメントなのかが明示的でなくてはならない。だとすると、userモデルとtweetモデルの2つとアソシエーションを組む必要がある。

テーブルを作り、ルーティング、コントローラ、ビューを作る流れはこれまでとほぼ同じである
1点、ルーティングの仕方だけ少し違うので、そこだけ注意する。手順は以下のとおり。

 作業内容

1.commentsテーブルを作る
2.ルーティングを記述する
3.フォームを作る
4.コントローラを作り、createアクションを記述する
5.ビューを編集する

1.commentsテーブルを作る

ツイートに対してのコメントをDBに保存するには、新しいテーブルとモデルを作らなければならない。commentを保存するテーブルなのでcommentsテーブル、モデルはcommentモデルという名前になる。テーブルの名前は複数形、モデル名は単数形になっていることに注意。

① commentモデルを作る

ターミナル
1
 $ rails g model comment

 新規作成されるファイル

  • app/models/comment.rb
  • db/migrate/2016XXXXXXXXXXX_create_comments.rb

commentモデルファイルとcommentsテーブルのマイグレーションファイル(=設計図)が作成された。

マイグレーションファイルを編集

モデルを作った際にマイグレーションファイルができた。このマイグレーションファイルを編集して、commentsテーブルに必要なカラムの情報を追加する。

db/migrate/2016XXXXXXXXXXX_create_comments.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.integer :user_id
      t.integer :tweet_id
      t.text :text
      t.timestamps
    end
  end
end

コメントの本文はtextカラムに保存していく。ここで注意したいのは、カラムにuser_idtweet_idがあること。

コメントはまず誰が投稿したコメントなのか分かる必要があるので、結びつくユーザーのidを保存する必要がある。コメントを投稿したユーザーのidを保存するカラムがuser_idとなる。

そしてもうひとつ、コメントはどのツイートに対してのコメントなのか明示する必要がある。結びつくツイートのidを保存するカラムがtweet_idとなる。

③ rake db:migrateをしてテーブルを作成

マイグレーションファイルの記述は完成したので、次はrake db:migrateを実行してテーブルを作る。

ターミナル
1
 $ rake db:migrate

マイグレーションファイルが実行されたことにより、commentsテーブルができた。Sequel Proを見てテーブルが追加されたことを確認。

https://tech-master.s3.amazonaws.com/uploads/curriculums//596f25dadd1844e19bd79f4d231dfe1f.png

 

要点チェック

 

④アソシエーションを定義

commentsテーブルのカラムにuser_idとtweet_idを作成したことによって、コメントに結びつくユーザーとツイートのidが保存できるカラムができた。しかし実際にツイートに結びつくコメントを呼び出したり、コメントに結びつくツイートを呼び出したりするにはアソシエーションというものを定義しなければならない。

なので、次はcommentsテーブル、tweetsテーブル、usersテーブルの3つのテーブルを結びつけるアソシエーションを記述する。やり方はtweetsテーブルとusersテーブルを結びつけた時と同様。

アソシエーションの図は少し複雑ですが、以下の通り。
テーブルとテーブルをつなぐ棒線は一対多の関係を表している。例えばtweetsテーブルからusersテーブルに伸びる棒線は一人のユーザーが複数のツイートを持つことができる関係を表している。

【テーブル図】

https://tech-master.s3.amazonaws.com/uploads/curriculums//c37c98c8cfffbfde132534f4f43c76e2.png

では実際にアソシエーションを組んでいく。

models/comment.rb
1
2
3
4
class Comment < ApplicationRecord
  belongs_to :tweet               #tweetsテーブルとのアソシエーション
  belongs_to :user                #usersテーブルとのアソシエーション
end

コメントは一人のユーザーと一つのツイートに従属するのでbelongs_to :モデル単数形を使う。

models/tweet.rb
1
2
3
4
class Tweet < ApplicationRecord
  belongs_to :user
  has_many :comments               #commentsテーブルとのアソシエーション
end

ツイートは複数のコメントを持つことができるので、has_many :モデル複数形を使ってアソシエーションを組む。

models/user.rb
1
2
3
4
5
6
7
8
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

ユーザーは複数のコメントを投稿することができるので、has_many: モデル複数形を使ってアソシエーションを組む。

これで、3つのテーブルのアソシエーションを組むことができた。

 

2.ルーティングを記述

コメントを投稿するためのルーティングを記述していく。

ルーティングを簡単に作成するためにresourcesメソッドを使って定義する。

 resourcesメソッド

Railsの基本となるコントローラの7つのアクションで記載した7つのアクション名に対してのルーティングを自動で生成するメソッド。

tweetsテーブルにとっての1ツイート、usersテーブルにとっての1ユーザー、つまり、テーブルに保存されるレコード1つの情報のことをリソースと呼ぶ。

以下の例のような記述では、bookというリソースに対して、基本となる7つのアクションをリクエストするルーティングが設定される。

【例】

config/routes.rb
1
2
3
 Rails.application.routes.draw do
  resources :books
end

ターミナルにて rake routes コマンドを実行すると以下のように7つのルーティングが設定されているのが確認できる。

【例】

ターミナル
1
2
3
4
5
6
7
8
9
 Prefix Verb         URI Pattern               Controller#Action
    books GET    /books(.:format)          books#index
          POST   /books(.:format)          books#create
 new_book GET    /books/new(.:format)      books#new
edit_book GET    /books/:id/edit(.:format) books#edit
     book GET    /books/:id(.:format)      books#show
          PATCH  /books/:id(.:format)      books#update
          PUT    /books/:id(.:format)      books#update
          DELETE /books/:id(.:format)      books#destroy

またcontrollerでbefore_actionの編集をした時と同様に、resourcesメソッドでもexceptやonlyオプションを組み合わせて特定のアクションへのルーティングのみを指定することができる。

コメントについてのルーティングを定義する前に、まずは今まで定義していたツイートとユーザーに関するルーティングをresourcesメソッドを使って書き換える。

config/routes.rb
1
2
3
4
5
6
Rails.application.routes.draw do
  devise_for :users
  root 'tweets#index'
  resources :tweets                     #tweets_controllerに対してのresourcesメソッド
  resources :users, only: [:show]       #users_controllerに対してのresourcesメソッド
end

tweetsコントローラは7つのアクション全て使っているが、usersコントローラはshowアクションしか使っていないので、onlyオプションでアクションを指定する。

できたら、rake routesコマンドを使ってどのようにルーティングが定義されているか確認。

ターミナル
1
 $ rake routes

コマンドを実行して以下のようなルーティングが表示されれば成功。

ターミナル
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Prefix Verb    URI Pattern                   Controller#Action
# 中略
root GET       /                              tweets#index
tweets_new GET /tweets/new(.:format)          tweets#new
tweets POST    /tweets(.:format)              tweets#create
GET            /users/:id(.:format)           users#show
DELETE         /tweets/:id(.:format)          tweets#destroy
GET            /tweets/:id/edit(.:format)     tweets#edit
PATCH          /tweets/:id(.:format)          tweets#update
GET            /tweets/:id(.:format)          tweets#show

次はcommentsコントローラのルーティングを記述する。
今まで通りに記述すると、下記のようなルーティングになる。

【例】config/routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do
  devise_for :users
  root 'tweets#index'
  resources :tweets
  resources :comments, only: [:create]
  resources :users, only: [:show]
end

このルーティングによって発行されるパスは以下の通り。

【例】ターミナル
1
2
3
4
Prefix Verb     URI Pattern           Controller#Action
# 中略
comments POST   /comments(.:format)   comments#create
# 中略

コメントには必ずコメント先というものが存在している。
今回の場合だと、コメント先のツイートがなければどのツイートに対してのコメントなのか分からなくなってしまう。
しかし今のままではパスの中にどのツイートのコメントなのかということを示す情報がない。
コメントを投稿する際には、どのツイートに対するコメントなのかをパスから判断できるようにしたいので、
ここではネストという方法を使っていく。

 ルーティングのネスト

ネストとは入れ子構造とも呼ばれ、ある記述の中に入れ子構造で別の記述をする方法
ルーティングでいうと、あるコントローラへのルーティングの記述の中に、別のコントローラへのルーティングを記述するということを指す。

今回の場合では、tweets_controllerのルーティングの中にcomments_controllerのルーティングを記述する。
こうすることによって、commentsのルーティングのパスの中にコメントに結びつくツイートのidの情報が含まれるようになる。

今回はcommentを投稿する(=作る)機能を実装するのでcreateアクションのルーティングを記述する。

config/routes.rb
1
2
3
4
5
6
7
8
Rails.application.routes.draw do
  devise_for :users
  root 'tweets#index'
  resources :tweets do
    resources :comments, only: [:create]
  end
  resources :users, only: [:show]
end

doendで挟むことで中の記述をネストさせることができる。この記述によって、どのようなルーティングになるのか、ターミナルでrake routesコマンドを実行し、ルーティングを確認してみる。

ターミナル
1
 $ rake routes

コマンドを実行して以下のようなルーティングが表示されれば成功。

ターミナル
1
2
3
4
Prefix Verb           URI Pattern                            Controller#Action
#中略
tweet_comments POST   /tweets/:tweet_id/comments(.:format)   comments#create
#中略

URLに注目すると、ネストによって、/tweets/:tweet_id/comments(.:format)となっている。リンクを飛ばすときは、この:tweet_idのところにコメントと結びつくツイートのidを記述する。そうするとparamsのなかにtweet_idというキーが追加され、コントローラで扱うことができる。

 

要点チェック

 

3.フォームを作成

コメントを投稿するためのフォームを作る。コメントはツイートの詳細画面から投稿できると便利なのでviews/tweets/show.html.erbにフォームを記述する。

tweets/show.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<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 '編集', "/tweets/#{@tweet.id}/edit", method: :get %>
        </li>
        <li>
          <%= link_to '削除', "/tweets/#{@tweet.id}", method: :delete %>
        </li>
      </ul>
    </div>
    <% end %>
    <%= simple_format(@tweet.text) %>
    <span class="name">
      <a href="/users/<%= @tweet.user.id %>">
        <span>投稿者</span><%= @tweet.user.nickname %>
      </a>
    </span>
  </div>
  <div class="container"> 
    <!-- ここからフォーム -->
    <% if current_user %>
      <%= form_tag("/tweets/#{@tweet.id}/comments", method: :post) do %>
        <textarea cols="30" name="text" placeholder="コメントする" rows="2"></textarea>
        <input type="submit" value="SENT">
      <% end %>
    <% end %>
  </div>
</div>

25行目でif current_userとすることで、ログインしていない状態では投稿フォームを出さないようにしている。これは、ログインしていない状態ではコメントを投稿できないようにするため。

また、form_tagのurlのところに注目すると、

tweets/show.html.erb
24
    <%= form_tag("/tweets/#{@tweet.id}/comments", method: :post) do %>

HTTPメソッドがpostメソッドになっている。これはツイートのフォームを作った際と同じ。ここで重要なところはform_tagの後に続く括弧の中のurlです。このurlの中に式展開させながら@tweet.idを記述することによって、paramsの中にコメントと結びつくツイートのidが追加される。

https://tech-master.s3.amazonaws.com/uploads/curriculums//69dde7101e8af14f52e4b467d129e4ed.png

 

4.コントローラを作り、createアクションを記述

コントローラとアクションを作ります。まずはcommentsコントローラを作る必要がある。rails gコマンドでcommentsコントローラを作る。

ターミナル
1
$ rails g controller comments

モデルと対応しているコントローラの名前は必ず複数形にする。

 新規作成されるファイル

  • app/controllers/comments_controller.rb
  • app/views/comments

これでcommentsコントローラを作ることができた。次はcommentsコントローラ内でcreateアクションを作る。
createアクションは以下の手順で進めていく。

1.createアクションを定義する

2.ストロングパラメーターを指定する

3.リダイレクト先をtweets/show.htmlにする

① createアクションを定義

まずはcommentsコントローラにcreateアクションを定義する。

comments_controller.rb
1
2
3
4
5
class CommentsController < ApplicationController
  def create
    Comment.create(text: params[:text], tweet_id: params[:tweet_id], user_id: current_user.id)
  end
end

クラスに対してcreateメソッドを使ってインスタンスを作成し、データベースに保存。引数では、カラムと、カラムの値を指定している。まずtextカラムにはparamsの中の"text"という情報を保存する。user_idカラムにはcurrent_user.id(=ログインしているユーザーのid)を保存している。

次にtweet_idカラムだが、ネストしてurlに@tweet.id(ツイートのid)を記述してあるのでparams[:tweet_id]とするだけで簡単に情報を得ることができる。

 

ネストはなぜ必要なのか?

もしネストしていなかったらどうなるのか

【例】

↓ネストさせた場合

ターミナル
1
2
3
4
Prefix Verb           URI Pattern                            Controller#Action
#中略
tweet_comments POST   /tweets/:tweet_id/comments(.:format)   comments#create
#中略

↓ネストさせなかった場合

ターミナル
1
2
3
4
Prefix Verb     URI Pattern           Controller#Action
# 中略
comments POST   /comments(.:format)   comments#create
# 中略

ネストをさせない場合だと、:tweet_idとなっている部分が無くなっている。そうすると、コメントと結びつくツイートのid情報が送れなくなる。先ほど、コントローラでtweet_idカラムの値を指定した。しかしネストしていない場合だとツイートのidの情報をコントローラに送れていないので、tweet_idカラムの値を保存することができなくなってしまう(厳密に言えば他の方法はあるのだが、この方法が一番良いとされている)。

まとめると、ルーティングをネストさせる一番の理由はアソシエーション先のレコードのidをparamsに追加してコントローラに送るためである。今回の実装だと、コメントと結びつくツイートのidをparamsに追加している。

 

要点チェック

 

② ストロングパラメーターを指定

先程までのアクションの記述でコメントの保存はできる。しかしストロングパラメーターを指定していないので、不正な情報を受け取ってしまう可能性がある。なので次はストロングパラメーターを指定して、必要なparamsだけを取ってこれるようにする。

comments_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class CommentsController < ApplicationController
  def create
    Comment.create(text: comment_params[:text], tweet_id: comment_params[:tweet_id], user_id: current_user.id)
  end

  private
  def comment_params
    params.permit(:text, :tweet_id)
  end
end

tweetsコントローラのcreateアクションを作った際とほぼ同じ。privateメソッドを使って、comment_paramsメソッドをコントローラ以外のところで呼び出せないようにしていて、permitメソッドで受け取るparamsのキーを指定している。

 

③ リダイレクト先をtweets/show.html.erbにする

ルーティングを記述し、アクションを作成したら次に行うことはビューを作ること。しかし今回は新しくビューファイルを作るのではなく、既にあるツイートの詳細画面に戻る処理を記述する。redirect_toメソッドを使って画面遷移の記述をしていく。

https://tech-master.s3.amazonaws.com/uploads/curriculums//e0f308abdc00d1cf14642c9ec765d306.png

comments_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class CommentsController < ApplicationController
  def create
    comment = Comment.create(text: comment_params[:text], tweet_id: comment_params[:tweet_id], user_id: current_user.id)
    redirect_to "/tweets/#{comment.tweet.id}"   #コメントと結びつくツイートの詳細画面に遷移する
  end

  private
  def comment_params
    params.permit(:text, :tweet_id)
  end
end

redirect_toの後にはルーティングのurlやprefixを記述することでそのアクションを実行することができた。
tweetsコントローラのshowアクションを実行するにはツイートのidが必要。ここではアソシエーションを使って、commentと結びつくツイートのidを記述している。

 

5.ビューを編集

① コメントをビューに表示させる

コメントを投稿した後は、自分が投稿したコメントや他のユーザーのコメントが読めたりするといい。コメントを表示する場所はツイートの詳細画面とする。このようにするとコメントを投稿した後に、先ほど記述したredirect_toメソッドによってツイートの詳細画面に遷移してコメントの確認ができる。

https://tech-master.s3.amazonaws.com/uploads/curriculums//bdc0d6e1c0185e5ec77ba381bb087a17.png

しかしその前に、ツイートの詳細画面でツイートと結びつくコメントを表示するためには、その前のコントローラの段階で、コメントのレコードをデータベースから取得する必要がある。

なのでまずはtweetsコントローラのshowアクションを編集する。

tweets_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class TweetsController < ApplicationController

# 中略

  def show
    @tweet = Tweet.find(params[:id])
    @comments = @tweet.comments.includes(:user)
  end

# 中略

end

表示させたいツイートのレコード@tweetは既に取得できているので、ツイートと結びつくコメントのレコードをデータベースから取得する。
tweetsテーブルとcommentsテーブルはアソシエーションが組まれているので、@tweet.commentsとすることで、@tweetについて投稿された全てのコメントのレコードを取得することができる。

また、ビューでは誰のコメントかを明らかにするために、アソシエーションを使ってユーザーのレコードを取得する処理をする。その時にN + 1問題が発生してしまうので、includesメソッドを使って、N + 1問題を解決している点に注意する。

 

コントローラで、あるツイートについて投稿された全てのコメントのレコードを取得することができたので、それをビューで表示する。
@commentsには複数のコメントのレコードが入っているので配列の形をとっている。なのでビューに表示させるためにはeachメソッドを使って、ひとつひとつのレコードに分解してから表示させる。

さらに、コメントをしたユーザー名をクリックしたらそのユーザーページに遷移するようにしたいので、名前のところにlink_toメソッドを使ってリンクを作る(リンクはまだクリックしないように)。

tweets/show.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 中略

  <div class="container">
    <% if current_user %>
      <%= form_tag("/tweets/#{@tweet.id}/comments", method: :post) do %>
        <textarea cols="30" name="text" placeholder="コメントする" rows="2"></textarea>
        <input type="submit" value="SENT">
      <% end %>
    <% end %>
<!-- ここから追記してください -->
    <div class="comments">   
      <h4><コメント一覧></h4>
      <% if @comments %>
        <% @comments.each do |comment| %>
          <p>
            <strong><%= link_to comment.user.nickname, "/users/#{comment.user_id}" %></strong>
            <%= comment.text %>
          </p>
        <% end %>
      <% end %>
    </div>
  </div>
</div>

こちらではif @commentsとすることで、もし@commentsが空だった場合でもエラーが起こらないようにしている。

eachメソッドで繰り返しコメントを表示する。名前のところはlink_toメソッドを使ってリンクを作る。ユーザーのidはcomment.user_idと記述することで、コメントを投稿したユーザーのidをparamsとして送る。
16行目で「comment.user」と記述し、コメントしたユーザーのレコードを取得しているが、includesメソッドで既にデータベースからレコードを取得しているのでSQL文は発行されず、N + 1問題は発生しない。

 

cssファイルを編集する

コメントの一覧画面を綺麗に表示させるために、cssファイルを編集する。

style.scssの318行目以降のコードを全て削除して、以下のコードをコピー&ペーストをして編集しましょう。

style.scss
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
  .success {
    @include boxBase(20px 0 ,30px);
    box-shadow: 0 0 10px rgba($black,0.2);
    background-color: $white;
    box-sizing: border-box;
    text-align: center;
    h3 {
      @include baseMargin;
    }
  }
}
form {
  h3 {
    @include baseMargin(0 0 20px);
    text-align: center;
    font-weight: normal;
    font-size: 20px;
    color: $dark;
  }
  input,textarea {
    width: 100%;
    @include boxBase(5px 0 15px,10px);
    border: 1px solid $gray;
    border-radius: 5px;
    font-family: "游ゴシック", "YuGothic";
  }
  input[type="submit"] {
    @extend .transition;
    background-color: $accent;
    border-radius: 20px;
    color: #fff;
    border: 0;
    font-size: 18px;
    &:hover {
      background-color: lighten($accent, 10%);
    }
  }
}
.container {
  @include boxBase(20px 0 ,30px);
  box-shadow: 0 0 10px rgba($black,0.2);
  background-color: $white;
  box-sizing: border-box;
}
.comments {
  padding: 5px;
  margin-top: 15px;
  a {
    color: $accent;
    &:hover {
      text-decoration: underline;
    }
  }
}

footer {
  @include boxBase;
  color: $gray;
  p {
    text-align: center;
  }
}

少しcssのクラスを編集したので、他のビューファイルも編集する必要がある。以下のようにtweets/new.html.erbを編集する

tweets/new.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div class="contents row">
  <div class="container">
    <%= form_tag('/tweets', method: :post) do %>
      <h3>
        投稿する
      </h3>
      <input placeholder="Image Url" type="text" name="image">
      <textarea cols="30" name="text" placeholder="text" rows="10"></textarea>           
      <input type="submit" value="SENT">
    <% end %>
  </div>
</div>

 ③ コントローラーを編集することでそれぞれのユーザーのマイページのビューの表示を正しくする

今、users_controller.rbは以下のようになっている。

users_controller.rb
1
2
3
4
5
6
class UsersController < ApplicationController
 def show
    @nickname = current_user.nickname
    @tweets = current_user.tweets.page(params[:page]).per(5).order("created_at DESC")
  end
end

今までユーザーページは自分自身のツイートしか見られない仕様になっていたので、current_userを使うことでログインしているユーザー、即ち自分自身のツイートを見ることが出来た。しかし、今はツイートの詳細ページからコメントをしたユーザーのページに遷移出来るような仕様になっている。なのでcurrent_userを使ったままでは、どのユーザー名をクリックしても自分自身のユーザーページしか表示されない(実際に試してみると、そのようになっているはず)。クリックしたユーザーのページに遷移できるように、users_controllerのshowアクション編集する。

users_controller.rb
1
2
3
4
5
6
7
class UsersController < ApplicationController
  def show
    user = User.find(params[:id])
    @nickname = user.nickname
    @tweets = user.tweets.page(params[:page]).per(5).order("created_at DESC")
  end
end

3行目では、コメント欄に表示されるユーザーをクリックすることで送られたidをparamsで取得して、そのユーザーのレコードをuserという変数に代入している。
4行目では、current_userからuser変数に変えて、nicknameの値を取り出している。
5 行目ではアソシエーションを利用することで、そのユーザーが投稿したツイートを取得して、@tweetsに代入している。

  

15:部分テンプレートを使ってみる

以前書いたコードを「部分テンプレート」を利用して書き換えてみる。

 部分テンプレート

ビューを作成する際に同じようなレイアウトの部分が複数存在する場合がある。例えば、Twitterでは複数のツイートが表示されているが、これらのHTML構造は全て同じである。このような場合に同じHTML構造の部分を共通化することによって、無駄なくビューファイルを作成することが出来る。この共通化された部分を、 部分テンプレートという。 部分テンプレートを使用した場合、そのHTML構造を他のビューでも使いまわすことができるという利点も存在する。

部分テンプレートのファイル名は必ずアンダーバー「_」から始まる。

部分テンプレート

Pictweetもツイートを複数表示している箇所があった。以下のツイートの一覧画面である。
この箇所を部分テンプレートに書き換えてみたい。
ピンクの枠で囲まれた部分がひとつのツイートで、この構造の繰り返しでツイートの一覧が出来ている。
ここでこの部分を別のファイルに切り出して、そのファイルを呼び出して使う。

https://tech-master.s3.amazonaws.com/uploads/curriculums//ff7609dcf9e7d7e42aa4db23c70dcc91.png

このページのビューファイル(tweets/index.html.erb)は以下のようになっている。この中のeachメソッドで囲まれた3~28行目がひとつのツイートのテンプレートになっている。この部分を別のファイルに切り出す。

tweets/index.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 <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 '編集', "/tweets/#{tweet.id}/edit", method: :get %>
            </li>
            <li>
              <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>
            </li>
          <% end %>
        </ul>
      </div>
      <%= simple_format(tweet.text) %>
      <span class="name">
        <a href="/users/<%= tweet.user_id %>">
          <span>投稿者</span><%= tweet.user.nickname %>
        </a>
      </span>
    </div>
  <% end %>
  <%= paginate(@tweets) %>
</div>
tweets/_tweet.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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 '編集', "/tweets/#{tweet.id}/edit", method: :get %>
        </li>
        <li>
          <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>
        </li>
      <% end %>
    </ul>
  </div>
  <%= simple_format(tweet.text) %>
  <span class="name">
    <a href="/users/<%= tweet.user_id %>">
      <span>投稿者</span><%= tweet.user.nickname %>
    </a>
  </span>
</div>

ここではコピー&ペーストのショートカットも使えるが、より作業がスムーズなカット&ペーストのショートカットを使ってみる。カットとは選択した部分をコピーして削除することであり、command + xがショートカットキーになる。
あとはcommand + vを使って選択した部分を適切な場所に貼り付ける。

cut and paste

この_tweet.html.erbファイルがツイートひとつ分のHTML構造を表す部分テンプレートになる。

続いて、こちらの部分テンプレートを呼び出す。そのためには、renderメソッドを利用する。

 render

renderメソッドは、部分テンプレートを呼び出す際に利用するメソッド。

 render partialオプション

renderメソッドに :partialというオプションをつけることで、明示的に部分テンプレート名を指定し、部分テンプレートを表示することができる。
下の例では、_sample.html.erbという部分テンプレートを呼び出している。

render
1
  render partial: "sample"

 render localsオプション

また、localsというオプションを用いると部分テンプレート内でその変数を使えるようになる。

render
1
  render partial: "sample", locals: { tweet: "hello!" }

これで部分テンプレート内においてhello! という文字列の代入されたtweetという変数が使えるようになる。

では、renderメソッドを利用して_tweet.html.erbを呼び出す。

tweets/index.html.erb
1
2
3
4
5
6
<div class="contents row">
  <% @tweets.each do |tweet| %>
    <%= render partial: "tweet", locals: { tweet: tweet } %>
  <% end %>
  <%= paginate(@tweets) %>
</div>

ここでのrenderメソッドのlocalsオプションに注目してみる。{ tweet: tweet } の右側の tweet はeachメソッド内の変数としてのtweettweetインスタンスを示している。一方、左側の tweet は部分テンプレート内での変数の名前を表している。

1
 <%= render partial: "tweet", locals: { tweet: tweet } %>

例えば、

1
 <%= render partial: "tweet", locals: { partial_tweet: tweet } %>

と名前を変更すると、tweetというインスタンスを部分テンプレートの中ではpartial_tweetとして使えるということである。

その後サーバーを立ち上げた状態で「localhost:3000/tweets」にアクセスすると、これまでと同様にツイートの一覧が表示されたら成功。

※表示自体は変わらないため、エラーが起きなければ問題ない。

表示自体が変わらないにも関わらず、部分テンプレートを用いるのは繰り返し再利用出来るから
再利用できるメリットとして

・繰り返し書くコードを一回で済ます

・修正するときに修正箇所が少なく済む
などがある。

では、実際に先ほど作成した部分テンプレートを別の箇所にも適用させてみる。
マイページの一覧画面にも部分テンプレートを適用出来る。
https://tech-master.s3.amazonaws.com/uploads/curriculums//0923ede64265fd42bbda77936cc3db35.png

users/show.html.erb
1
2
3
4
5
6
7
<div class="contents row">
  <p><%= @nickname %>さんの投稿一覧</p>
  <% @tweets.each do |tweet| %>
    <%= render partial: "tweets/tweet", locals: { tweet: tweet } %>
  <% end %>
  <%= paginate(@tweets) %>
</div>

※違うフォルダ内の部分テンプレートを呼び出すときは tweets/tweetのようにどのフォルダの部分テンプレートを使用しているかを明示的に記載する。

その後サーバーを立ち上げた状態でマイページにアクセスして、これまでと同様にツイートの一覧が表示されたら成功。

このように部分テンプレートを使うと同じHTML構造を使い回すことが出来る。

 

要点チェック