マイページの実装

 tweetsテーブルにuser_idカラムをinteger型で追加しましょう

カラムの追加の手順は、nicknameカラムを追加した際と同様です。
ファイル名の命名規則にしたがって、自動でコードが記述されたマイグレーションを生成します。

ターミナル
1
2
3
4
5
# tweetsテーブルにuser_idカラムをinteger型で追加するマイグレーションファイルの作成
% rails g migration AddUserIdToTweets user_id:integer

# マイグレーションファイルの実行
% rails db:migrate

これで、マイグレーションを編集せずに、tweetsテーブルへuser_idカラムが追加できました。

current_userカレント ユーザーメソッド

Gemのdeviseを導入しているため、使用できるメソッドです。
current_userは、現在ログインしているユーザーの情報を取得できます。

現在、ビューから送られてくる情報が入ったparamsと、current_userメソッドで取得したログイン中ユーザーのidを統合した上で、ツイートを保存させたいところです。

そこで、2つのハッシュを統合するときに使うmergeメソッドを利用して、paramsとuser_idの情報を統合しましょう。

mergeマージメソッド

ハッシュを結合させるときに使用するRubyのメソッドです。
今回は、tweetの情報を持つハッシュと、user_idを持つハッシュを結合させます。
以下の例のように、2つのハッシュを1つにまとめることができます。

【例】
1
2
3
4
tweet = { name: "たなか", text: "test", image: "test.jpeg" }
uid = { user_id: "1" }
tweet.merge(uid)
=> {:name=>"たなか", :text=>"test", :image=>"test.jpeg", :user_id=>"1"}

ではmergeメソッドを用いてコントローラーを編集しましょう。

 ①

app/controllers/tweets_controller.rb
 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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)
  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

 

 

 

アソシエーション

アソシエーションとは、モデルを利用したテーブル同士の関連付けのことです。

アソシエーションをモデルに定義することで、そのモデルに紐づく別のモデルの情報へアクセスできるようになります。

 

 has_manyハズ メニーメソッド

Userモデルの視点で考えると、あるユーザーの作成した投稿は複数個ある状態です。
つまり、1人のユーザーは複数の投稿を所有しています。

この状態のことをhas manyの関係といい、今回の場合は「User has many Tweets」の状態であると言えます。
この関連付けをするため、userと他のモデルとの間に「1対多」のつながりがあることを示すのがhas_manyメソッドです。

app/models/user.rb
1
2
3
4
5
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :tweets
end

belongs_toビロングス トゥーメソッド

1つの投稿は、1人のユーザーが投稿したものです。
つまり1つの投稿を複数人が投稿できないため、投稿は必ず1人のユーザーに所属します。

この状態のことをbelongs toの関係

① app/models/tweet.rb
1
2
3
4
class Tweet < ApplicationRecord
  validates :text, presence: true
  belongs_to :user
end

 

<マイページの作成>

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

① 

config/routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do
  devise_for :users
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root to: 'tweets#index'
  resources :tweets
  resources :users, only: :show
end

 

マイページボタンを投稿一覧に追加しよう

マイページへはPicTweetのページ上部から遷移できるように実装します。

マイページへのパスは/users/ログイン中のユーザーのidです。

app/views/layouts/application.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
  # 省略
  <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>
  # 省略

ユーザー名には、current_user.nicknameとすることで現在ログインしているニックネームを表示できます。

マイページへのリンクは、/users/:idと指定します。
:idの部分には表示したいユーザーのidを入れる必要がありますが、今回はマイページ、つまりログイン中のユーザーのページなので、current_user.idとします。

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

ターミナル
1
2
# users_controllerと関連するファイルを作成
% rails g controller users

 

showアクションで表示するのは、ログイン中ユーザーのマイページとなるため、
必要な情報は「ニックネーム」と「ログイン中のユーザーのツイート投稿」の2つです。

それぞれを@nickname@tweetsというインスタンス変数に代入します。

app/controllers/users_controller.rb
1
2
3
4
5
6
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
1
2
3
4
5
6
7
8
9
<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>

 

<投稿者となるユーザー名の表示>

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

tweetsテーブルのレコードである、「name」カラムの値が表示されている部分を、
アソシエーションを利用してツイート投稿者の「nickname」が表示されるように変更します。

 

weetモデルのインスタンスが入った変数.userと記述すると、インスタンスが属しているUserモデルのインスタンスを取得できます。

コンソールを利用して例を見てみましょう。

【例】アソシエーションを利用しない
1
2
3
4
% rails c
[1] pry(main)> tweet = Tweet.find(1)
[2] pry(main)> User.find(tweet.user_id)
=> #<User id: 1, email: "test@gmail.com", encrypted_password: "@@@@@@@@@@@@@@@@@", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_at: "2014-12-06 09:00:00", last_sign_in_at: "2014-12-06 09:00:00", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", nickname: "test_ruby">

アソシエーションを利用すると、以下のように簡潔なコードで記述できます。

【例】アソシエーションを利用する
1
2
3
4
% rails c
[1] pry(main)> tweet = Tweet.find(1)
[2] pry(main)> tweet.user
=> #<User id: 1, email: "test@gmail.com", encrypted_password: "@@@@@@@@@@@@@@@@@", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_at: "2014-12-06 09:00:00", last_sign_in_at: "2014-12-06 09:00:00", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", nickname: "test_ruby">

 

app/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
22
23
24
25
26
<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="">
          <span>投稿者</span><%= tweet.user.nickname %>
        </a>
      </span>
    </div>
  <% end %>
</div>

 

tweets/index.html.erbを編集しましょう

app/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
22
23
24
25
26
<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>

 

tweets/show.html.erbを編集しましょう

ツイート詳細画面の投稿者の部分も@tweet.nameとnameを使う文になっているため、アソシエーションを利用する形に変更しましょう。
マイページに飛ぶリンクも設置します。

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

includesインクルーズメソッド

includesメソッドは、引数に指定された関連モデルを1度のアクセスでまとめて取得できます。

書き方は、includes(:紐付くモデル名)とします。引数に関連モデルをシンボルで指定します。

【例】
1
モデル名.includes(:紐付くモデル名)

これにより、N+1問題を解消できます。

① tweets_controller.rbを編集

app/controllers/tweets_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

 

<投稿画面のビューを変更しよう>

Nicknameを入力するためのフォーム部品を削除します。

app/views/tweets/new.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<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>

上記をすべて書き写すのではなく削除する点に注意してください。5行目を削除しました。

tweets/edit.html.erbを編集しましょう

Nicknameを入力するための5行目を削除しましょう。

app/views/tweets/edit.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<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>

これで、編集画面からもNickname入力欄がなくなりました。

 

投稿時の処理を変更しよう

投稿時に「name」を入力する必要がなくなったので、それに合わせてtweetsコントローラーの処理も変更しましょう。

 

app/controllers/tweets_controller.rb
 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

permit(:name, :image, :text)から:nameを削除しました。

 

tweetsテーブルから不要なカラムを削除しよう

ツイートに「Nickname」という情報を保存しなくなったのでnameカラムも不要になりました。

そこで、テーブルからカラムを削除するためのマイグレーションを作成して、カラムの削除を実行しましょう。そのためには、以下のようにコマンドを実行します。

【例】ターミナル
1
% rails g migration Removeカラム名From削除元テーブル名 削除するカラム名:型

今回は、tweetsテーブルからstring型のnameというカラムを削除します。

ターミナル
1
2
3
4
5
# マイグレーションの作成
% rails g migration RemoveNameFromTweets name:string

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

Removeカラム名From削除元テーブル名カラム名の部分は、Addカラムの際と同様に必ずしも厳密なカラム名を入力する必要はありません。
対して、削除元テーブル名は正確に記述する必要があります。

今回はnameというカラムを削除するので、Nameを含めておきましょう。
末尾のname:stringが、どのカラムを削除するのかを決めているので必ず記述します。

 

users/show.html.erbを編集しましょう

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

app/views/users/show.html.erb
1
2
3
4
5
6
7
8
9
<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>

 

<投稿者のマイページにアクセスできるように>

users_controller.rbを編集しましょう

クリックされたユーザーのidから情報を取得して、ビューに受け渡します。

app/controllers/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
  end
end