Pictweetの作成 その2
7:ツイートにユーザー情報を追加
現在ユーザーが投稿したツイートはどのユーザーが投稿したものなのかわからない状態になっている。これがわかるようになると、特定のユーザーがつぶやいたツイート一覧を取得できたり、ツイートの削除を行う際につぶやいた本人しか削除できないように設定できたりと色々便利。そこで、ツイートにユーザーの情報を追加して、誰が投稿したツイートなのかが分かるようにする。
作業内容
1.tweetsテーブルにカラムを追加する
2.ツイート保存時にユーザー情報を追加する処理を記述する
3.ツイート保存時にユーザー情報も保存できたか確かめる
1.tweetsテーブルにカラムを追加する
ツイートにユーザーの情報を追加する場合、tweetsテーブルに新しくuser_id
というカラムを作成し、そこにツイートを投稿したユーザーのidを入れる。カラムの追加の手順はnickname
カラムを追加した際と同様。
1 2 3 4 5 |
$ rails g migration AddUserIdToTweets user_id:integer
# tweetsテーブルにuser_idカラムをinteger型で追加するマイグレーションファイルの作成
$ rake db:migrate
# マイグレーションファイルの実行
|
新規作成されるファイル
- db/migrate/2014XXXXXXXXXX_add_user_id_to_tweets.rb
2.ツイート保存時にユーザー情報を追加する処理を記述
上記の作業でtweetsテーブルにカラムが追加されたので、ツイートの保存時に投稿したユーザーのidをテーブルに保存する処理を記述する。
current_user
deviseでログイン機能を実装すると、current_user
というヘルパーメソッドを使用することができる。これは、現在ログイン中のユーザーのレコードを、userクラスのインスタンスとして取得することができるメソッド。
usersテーブル
でのid
カラムの値が1
のアカウントでログインしている場合、current_user
はUser.find(1)
と同じ意味を持つ。この時、以下のようにcurrent_user.usersテーブルのカラム名
とすることで、ログイン中のユーザーの情報として登録されている各カラムの値を取得することができる。
1 2 3 4 5 6 |
ツイートを投稿したユーザーとはつまり現在ログインしているユーザーのこと。なので、tweetsテーブルのuser_idカラムに保存すべきなのはcurrent_userのidカラムの値
。
実際にツイートをテーブルに保存する処理が書かれているのはtweets_controller
のcreate
アクション。現在は、以下のようになっている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class TweetsController < ApplicationController
before_action :move_to_index, except: :index
def index
@tweets = Tweet.page(params[:page]).per(5).order("created_at DESC")
end
def new
end
def create
Tweet.create(tweet_params)
end
private
def tweet_params
params.permit(:name, :image, :text)
end
def move_to_index
redirect_to action: "index" unless user_signed_in?
end
end
|
ツイートを保存する際、name
、image
、text
というビューから送られてくる情報に加えて、user_id
カラムにログイン中のユーザーのidを保存しなければならない。そこで、createメソッドの引数をcreate(カラム名: 保存する値, カラム名: 保存する値, …)
と変更する必要がある。この時、name、image、textを保存する際にはtweet_params
というストロングパラメーターを使用する。tweet_params
はハッシュの形なので、tweet_params[:キーの名前]
とすることでそのキーのバリューの値を取ることができる。すると、以下のように書くことができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class TweetsController < ApplicationController
before_action :move_to_index, except: :index
def index
@tweets = Tweet.page(params[:page]).per(5).order("created_at DESC")
end
def new
end
def create
Tweet.create(name: tweet_params[:name], image: tweet_params[:image], text: tweet_params[:text])
end
private
def tweet_params
params.permit(:name, :image, :text)
end
def move_to_index
redirect_to action: :index unless user_signed_in?
end
end
|
加えて、user_idに現在ログイン中のユーザーのidを保存する処理を追記。
問題7:ツイートの保存時に、user_idに現在ログイン中のユーザーのidを保存する
作業ファイル:app/controllers/tweets_controller
ヒント:createの引数の形式に注意して、引数の末尾に保存するカラム名と保存したい値を書き加える。
ツイート保存時にユーザー情報も保存できたか確かめる
投稿したユーザーのidをテーブルに保存する処理を記述できたら、実際にそれがうまく機能するか確かめる。実際にツイートを投稿し、Sequel Proに保存されたレコードを確認。user_idに現在ログイン中のユーザーのidが保存されていれば成功。
8:マイページを作成
現在、ツイートの一覧画面には全ユーザーのツイートが表示されている。そこで、一覧画面とは別に、現在ログイン中のユーザーの投稿のみが表示されているマイページを作成する。
作業内容
1.マイページのルーティングを記述する
2.コントローラとアクションを作成する
3.マイページ用のビューファイルを作成する
4.マイページへのリンクを作成する
1.マイページのルーティングを記述
新しくページを作成する場合、まずはルーティングを設定する。マイページへのパスは、usersテーブルでのidが1のユーザーでは/users/1
、idが99のユーザーでは/users/99
となるようにルーティングを設定する。
マイページのパスは他のページと違って、ユーザーごとにパスが異なる。その場合、通常とは多少異なるルーティングの記述方法を使う。
マイページを表示する際にはusersコントローラのshowアクションを動かす。show
アクションへのルーティングは、以下のように書く。
1 2 3 |
Rails.application.routes.draw do
get '/コントローラ名/:id' => 'コントローラ名#show'
end
|
:id
の部分には表示するユーザーページのユーザーのidが入る。
1 2 3 4 5 6 7 8 |
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' #Mypageへのルーティング
end
|
このようにルーティングを定義することによって、パスの一部をコントローラにパラメーターというハッシュ形式で値を送る事ができる。これはparamsというメソッドを使用することで取得することができる。コントローラ内でparams[:id]
と記述することにすれば/users/:id
の:id
部分の情報を使用することができる。
2.コントローラとアクションを作成
usersコントローラを作成し、showアクションを定義していく。
whereメソッド
where
メソッドはActiveRecordメソッドのうちの一つ。モデル.where(条件)
のように引数部分に条件を指定することで、テーブル内の条件に一致したレコードのインスタンスを配列型で取得できる。
また、where
メソッドを連続して記述することによって、複数の条件に一致したレコードを取得することもできる。
1 2 3 4 5 6 7 |
[1] pry(main)> Tweet.where('id < 3')
=> [#<Tweet id: 1, image: "test1.jpg", text: "いい景色だ。", created_at: "2014-12-06 00:00:00", updated_at: "2014-12-06 00:00:00", user_id: 1>,#<Tweet id: 2, image: "test2.jpg", text: "Thank you!", created_at: "2014-12-07 00:00:00", updated_at: "2014-12-07 00:00:00", user_id: 2>]
# idが3未満のtweetsテーブルのインスタンスを配列で取得
[2] pry(main)> Tweet.where('id < 3').where(user_id: 1)
=> [#<Tweet id: 1, image: "test1.jpg", text: "いい景色だ。", created_at: "2014-12-06 00:00:00", updated_at: "2014-12-06 00:00:00", user_id: 1>]
# idが3未満かつuser_idが1のtweetsテーブルのインスタンスを配列で取得
|
1 2 |
$ rails g controller users
# users_controllerと関連するファイルを作成
|
新規作成されるファイル
- app/controllers/users_controller.rb
- test/controllers/users_controller_test.rb
- app/helpers/users_helper.rb
- test/helpers/users_helper_test.rb
- app/assets/javascripts/users.js.coffee
- app/assets/stylesheets/users.css.scss
続いて、作成したusers_controllerにshow
アクションを定義する。show
アクションで表示するのはユーザーのマイページだが、マイページに必要な情報はニックネームとログイン中のユーザーのツイートの2つ。そこで、showアクションにはユーザーページを表示する際に必要な情報をビューに受け渡す処理を記述する。
ビューに情報を受け渡す際にはインスタンス変数(@付きの変数)をコントローラのアクション内で定義する。今回の場合、必要な情報はニックネームとログイン中のユーザーのツイートの2つ。それぞれを@nickname
と@tweets
という変数に定義する。
問題8:マイページに表示したい情報をshowアクションに定義
作業ファイル:app/controllers/users_controller
ヒント:@tweetsを定義する際はActiveRecordメソッドのwhereを使用。
1 2 3 4 5 6 7 8 |
class UsersController < ApplicationController
def show
@nickname = 現在ログインしているユーザーのニックネーム
@tweets = 現在ログインしているユーザーが投稿したツイート.page(params[:page]).per(5).order("created_at DESC")
end
end
|
3.マイページ用のビューファイルを作成
usersコントローラにshowアクションを定義することができたので、アクションに対応するビューファイルを作成していく。
①ファイルの新規作成
先ほどusers_controllerを作成した際にapp/views/usersディレクトリが作成されているので、その中にshow.html.erb
というファイルを作成。
新規作成されるファイル
- app/views/users/show.html.erb
②ファイルの編集
1 2 3 4 5 6 7 8 9 10 |
4.マイページへのリンクを作成
マイページへはPicTweetのページ上部から遷移できるように実装する。
1.マイページのルーティングを記述するで実装した通り、マイページへのパスは/users/ログイン中のユーザーのid
。こちらをどう記述するかが鍵になる。
問題9:マイページへのリンクを作成する
作業ファイル:app/views/layouts/application.html.erb
ヒント:マイページのパスは「/users/ログイン中のユーザーのid」となっている。そのため、ビューファイルもログイン中のユーザーによってリンクが変化するようにRubyタグを使って記述を行う。
【例】
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>ログイン中のユーザーのニックネームを記述
<ul class="user__info">
<li>
<a href="/users/ログイン中のユーザーのid">マイページ</a>
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
</li>
</ul>
</span>
<a class="post" href="/tweets/new">投稿する</a>
</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>
|
localhost:3000/users/ ログイン中のユーザーのid にアクセスしてマイページが作成されているか確認
最後にルーティングを設定したところでparamsの中を確認する。binding.pryを使用。
1 2 3 4 5 6 7 |
class UsersController < ApplicationController
def show
binding.pry
@nickname = current_user.nickname
@tweets = Tweet.where(user_id: current_user.id).page(params[:page]).per(5).order('created_at desc')
end
end
|
編集したら、http://localhost:3000/users/ ログインしているユーザーのidにアクセスする。
(ログインしているユーザーのID番号が1ならば、http://localhost:3000/users/1 。)
ここでの「ログインしているユーザーのid」には、現在ログインしているユーザーのidを入力。
するとサーバーが停止するので、ターミナルを確認する。
1 2 3 4 5 |
2: def show
=> 3: binding.pry
4: @nickname = current_user.nickname
5: @tweets = current_user.tweets.page(params[:page]).per(5).order('created_at desc')
6: end
|
この状態で、params
と入力。
1 2 |
[1] pry(#<UsersController>)> params
=> {"controller"=>"users", "action"=>"show", "id"=>"1"}
|
paramsというハッシュの中に"id" => "1"
というルーティングで設定したキーとビューでパスに組み込まれたユーザーのidが追加されているのが確認できる。このparamsがコントローラに送られてくるのでコントローラ内でparams[:id]とすることでユーザーのidを取得することができる。また、ルーティングで:id
を:user_id
のように変更すると、以下のようにキーがuser_idになる。
1 2 |
[1] pry(#<UsersController>)> params
=> {"controller"=>"users", "action"=>"show", "user_id"=>"1"}
|
users_controller.rbに記述したbinding.pryを削除する
9:アソシエーションを利用
Railsにはモデル間の関係を管理するアソシエーションという機能がある。今回の作業ではこのアソシエーションという機能を実装する。
アソシエーション
アソシエーションとはモデル間の関連付けを管理する機能のことで、定義しておくことでモデルをまたいだデータの呼び出しをより簡単に行うことができるようになる。
今回の場合、UserモデルとTweetモデルをアソシエーションで関連付ける。下のヒントを参考にしながら実装する。
作業内容
1.アソシエーションを定義する
2.アソシエーションを実装する
1.アソシエーションを定義
アソシエーションを利用するために、まずはモデル間の関係を定義する。アソシエーションを利用するには以下の2つの条件を満たす必要がある。
① モデルクラスにhas_many
やbelongs_to
などの定義がされている
② 所属する側のテーブルに所属するクラス名_id
というカラムがある
現在PicTweetにはサインアップという機能があり、利用者がユーザーを作成してログインを行うことができる。また、ツイートの投稿に関してはログインしたユーザーのみが行うことができる。
つまり、全てのツイートはいずれかのユーザーが作成したものであり、逆に言えばユーザーは自分の作成したツイートを複数個所持している状態といえる。
Userモデルの視点で考えると、あるuserの作成したtweetが複数個ある状態と言える。この状態のことをhas manyの関係といい、今回の場合には「User has many Tweets」の状態であると言える。
この関係をアソシエーションで定義するためにはUserモデルのモデルファイルを編集する。「User has many Tweets」の状態を定義するので、Userモデルのモデルファイルにhas_many :tweets
と追記。
1 2 3 4 5 |
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets
end
|
また、Tweetモデルの視点で考えると、全てのtweetはいずれかのuserに属している状態と言える。この状態のことをbelongs toの関係といい、今回の場合は「Tweet belongs to User」の状態であると言える。
この関係をアソシエーションで定義するためにはTweetモデルのモデルファイルを編集する。「Tweet belongs to User」の関係を定義するので、Tweetモデルのモデルファイルにbelongs_to :user
と追記。
2.アソシエーションを実装
以上の作業でアソシエーションを定義することができた。そこで、実際にアソシエーションを利用して実装を行っていく。さきほど編集を行ったusers_controller.rb
のshow
アクション部分でのデータの呼び出しにアソシエーションを使用して記述を行う。
アソシエーションを定義すると、モデルをまたいだデータの呼び出しをより直感的に行うことができる。便利さを実感してもらうために、アソシエーションを使用しない場合とする場合のコードの違いを見てみる。
アソシエーションを使用しない場合は、以下のように現在ログイン中のユーザーが投稿したツイートを取得する。
例としてコンソールからのデータの呼び出しを行う。
アソシエーションを使用しない場合
1 2 3 4 5 |
$ rails c
[1] pry(main)> user = User.find(1)
[2] pry(main)> Tweet.where(user_id: user.id)
=> [#<Tweet id: 1, image: "http://photo1.jpg", text: "いい景色だ。", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", user_id: 1>,
#<Tweet id: 1, image: "http://photo2.jpg", text: "楽しい思い出は消えない。", created_at: "2014-12-06 10:00:00", updated_at: "2014-12-06 10:00:00", user_id: 1>]
|
tweetsテーブルに対して、user_id
がuser
のidと等しいレコードを取得している。
対して、これと同じくアソシエーションを利用することで以下のように記述できる。
アソシエーションを使用する場合
1 2 3 4 5 |
$ rails c
[1] pry(main)> user = User.find(1)
[2] pry(main)> user.tweets
=> [#<Tweet id: 1, image: "http://photo1.jpg", text: "いい景色だ。", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", user_id: 1>,
#<Tweet id: 1, image: "http://photo2.jpg", text: "楽しい思い出は消えない。", created_at: "2014-12-06 10:00:00", updated_at: "2014-12-06 10:00:00", user_id: 1>]
|
Userモデルに「User has many Tweets」の状態のアソシエーションを定義したので、Userモデルのインスタンス.tweets
と記述するだけでそのインスタンスが所持しているツイートを取得することができる。
users_controllerのshow
アクション内を編集してアソシエーションを利用してみる。
問題10:アソシエーションを利用して@tweetsを定義する
作業ファイル:app/controllers/users_controller
ヒント:現在ログイン中のユーザーがつぶやいたツイート達を取得するよう書く
1 2 3 4 5 6 |
class UsersController < ApplicationController
def show
@nickname = current_user.nickname
@tweets = アソシエーションを使用して記述.page(params[:page]).per(5).order("created_at DESC")
end
end
|
10:ツイート一覧画面にニックネームを表示
現在、ツイートの一覧画面のツイートの右下には、投稿時に入力した「name」が表示されている。その部分の表示を投稿者の「nickname」を表示するように実装する。
作業内容
1.投稿者名を表示するようにビューを変更する
2.ツイートからユーザー情報を先読みする
3.投稿画面のビューを変更する
4.投稿時のコントローラでの処理を変更する
5.tweetsテーブルから不要なカラムを削除する
1.投稿者名を表示するようにビューを変更
ツイートの右下、tweetsテーブルのレコードの「name」カラムの値が表示されている部分に、ツイートの投稿者の「nickname」が表示されるようにビューの変更を行っていく。「nickname」を表示する際にはアソシエーションを利用する。
Tweetモデルに対して「Tweet belongs to User」という形でアソシエーションを定義しているので、Tweetモデルのインスタンス.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">
|
usersテーブルに対して、idがtweet
のuser_id
と等しいレコードを取得している。これと同じくアソシエーションを利用することで、以下のように記述できる。
アソシエーションを利用する場合
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">
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div class="contents row" >
<% @tweets.each do |tweet| %>
<div class="content_post" style="background-image: url(<%= tweet.image %>);">
<%= simple_format(tweet.text) %>
<span class="name">
<a href="">
<span>投稿者</span><%= tweet.user.nickname %>
</a>
</span>
</div>
<% end %>
<%= paginate(@tweets) %>
</div>
|
sequelproを開き、tweetsテーブルのuser_idの値を、全て現在ログインしているユーザーのid番号に変更する
ビューを確認する前に、以下、注意事項がある。
Sequel Pro上で、tweetsテーブルにあるレコードのうちuser_id
カラムが空のものに、現在存在しているusersテーブルのレコードのidを入力しているか確かめる。
7で、tweetを投稿した際にuser_idも一緒に保存するようにしたが、Sequel Proで確認すればわかる通り、それ以前に投稿したtweetはuser_idがnullのままになっているはず。この時tweet.userはnilとなるため、tweet.user.nicknameと書くと、nullクラスに対してnicknameメソッドが実行されてしまいエラーとなる。
localhost:3000にアクセスして、投稿一覧にnicknameが表示されているかを確認。
2.ツイートからユーザー情報を先読みする
上記の実装でツイートの一覧画面に投稿者の「nickname」が表示されるようになったが、ツイートからユーザーの情報を呼び出す際に「n+1問題」という問題が発生している。そこで、この問題を解決するためにユーザー情報を先読みするように実装していく。
n+1問題
モデルを利用してデータベースの情報にアクセスする際にはSQLが発行される。SQLが発行されるたびにデータベースに対して通信が走るので、SQLが大量に発行されれば処理が重くなる。n+1問題とは、データを呼び出す際に大量のSQLが発行されてしまう問題のこと。(DBからデータを抽出する操作を、SQLを発行すると表現するのが一般的。)
今回の場合、indexアクションで全ツイートを取得する1回に加えて、アソシエーションを利用してツイートの数だけユーザー情報を呼び出している。つまり、現在の状態だとツイート数+1回SQLが発行されている。この状態のことをn+1問題と言う。
includesメソッド
includesメソッドはn+1問題を解消することができる。指定された関連モデルをまとめて取得することで、SQLの発行回数を減らすことができる。書き方は、includes(:モデル名)
とする。引数で、関連モデルをシンボル型(接頭に:がつく型)で指定する。
今回、usersテーブルとtweetsテーブルの間には以下の図のような関係がある。tweetsテーブルのレコードは必ず1つのusersテーブルのレコードに属しているので、includesメソッドを利用することでtweetsテーブルのレコードを取得する段階で関連するusersテーブルのレコードも一度に取得することができる。
1 2 3 4 5 6 7 8 9 |
class TweetsController < ApplicationController
def index
@tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
end
#以下省略
end
|
3.投稿画面のビューを変更
ツイートを表示する際にアソシエーションを利用して投稿者のニックネームが表示されるようになったので、投稿時に「name」を入力する必要がなくなった。そこで、この実装に合わせたビューの変更を行いる。投稿時に「name」を入力する必要がなくなったので、「name」の入力フォームを削除する。
1 2 3 4 5 6 7 8 9 10 |
<div class="contents row">
<%= form_tag('/tweets', method: :post) do %>
<h3>
投稿する
</h3>
<input placeholder="Image URL" type="text" name="image" autofocus="true">
<textarea placeholder="text" name="text" cols="30" rows="10"></textarea>
<input type="submit" value="SENT">
<% end %>
</div>
|
4.投稿時のコントローラでの処理を変更
投稿時に「name」を入力する必要がなくなったので、それに合わせてtweetsコントローラでの処理も変更する。name
カラムを使用しないので、ツイートの保存時にname
カラムに情報を保存しないように変更を行う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class TweetsController < ApplicationController
before_action :move_to_index, except: :index
def index
@tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
end
def new
end
def create
Tweet.create(image: tweet_params[:image], text: tweet_params[:text], user_id: current_user.id)
end
private
def tweet_params
params.permit(:image, :text)
end
def move_to_index
redirect_to action: :index unless user_signed_in?
end
end
|
5.tweetsテーブルから不要なカラムを削除
ツイートに「name」という情報を保存しなくなったので「name」カラムが不要になった。
そこで、テーブルからカラムを削除するためのマイグレーションファイルを作成して、カラムの削除を実行する。
今回は、tweetsテーブルからstring
型のname
というカラムを削除する。
1 2 3 4 5 |
Removeカラム名From削除元テーブル名
のカラム名の部分は、Addカラムの際と同様に必ずしも厳密なカラム名を入力する必要はない。今回はnameというカラムのみを削除するので、Nameと記述する。対して、削除元テーブル名
は正確に記述する必要がある。
新規作成されるファイル
- db/migrate/2015XXXXXXXXXX_remove_name_from_tweets.rb
また、tweetsテーブルのnameカラムを削除したため、users/show.html.erbの6行目でエラーが発生してしまう。6行目を削除する。
1 2 3 4 5 6 7 8 9 |
11:ツイートの削除機能を追加
現在の実装では一度投稿したツイートは削除することができない。そこで、ツイートに削除ボタンを追加して、ツイートの削除を行えるようにする。ツイートを削除する流れは、下記の図のような流れで実行される。
作業手順はルーティングを設定することでビューに削除のパスを指定できるため、ルーティングの編集から始まる。
作業内容
1.ツイートを削除するためのルーティングを設定する
2.削除ボタンをビューに追加する
3.destroyアクションをコントローラに定義する
4.削除後のビューを追加する
1.ツイートを削除するためのルーティングを設定
ツイート削除機能を実装するためにまずはルーティングを定義している。ツイートを削除する際にはツイートごとに固有のパスが発行されるので、ユーザーのマイページを作成したときと同様に少し特別なルーティングが必要になる。
また、ツイートの削除を行う際にはdelete
というHTTPメソッドを利用する。
deleteメソッド
delete
メソッドは情報の削除を行う際に利用するHTTPメソッド。今回の場合、ツイートの削除を行うのでdelete
メソッドを利用する。
また、ツイートの削除を行う際には、tweetsコントローラのdestroyアクションを動かす。
1 2 3 4 5 6 7 8 9 |
Rails.application.routes.draw do
devise_for :users
root 'tweets#index'
get 'tweets' => 'tweets#index'
get 'tweets/new' => 'tweets#new'
post 'tweets' => 'tweets#create'
delete 'tweets/:id' => 'tweets#destroy'
get 'users/:id' => 'users#show'
end
|
リクエストする箇所(ビュー)では、パスの:id
の部分に削除するツイートのidが入るように記述。すると、パラメーターがコントローラに渡る際params
のキーにid
が追加され、そのバリューはリクエストの際に:id
の部分に記述された値になる。つまり、:id
の部分に記述された値はコントローラ上でparams[:id]
とすることで取得できる。
2.削除ボタンをビューに追加
ツイートを削除するためにビューファイルを編集して削除ボタンを追加する。今回は、link_toメソッドを使用。削除ボタンはツイートの表示画面の右上に配置される。また、自分が投稿したツイートにのみ表示されるようにする。
link_to
メソッドを実装する際にはrake routes
コマンドを使用して、パスを確認する必要がある。
必ずしもrake routesで確認する必要性はなく、指定するパスを忘れてしまった場合にコマンドを実行する。
rake routes
このコマンドは現在定義しているルーティングを表示してくれるコマンド。ターミナルで入力することで、config/routes.rb
に記述したルーティングを表示してくれる。
1 |
$ rake routes
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Prefix Verb URI Pattern Controller#Action
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
user_password POST /users/password(.:format) devise/passwords#create
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
PATCH /users/password(.:format) devise/passwords#update
PUT /users/password(.:format) devise/passwords#update
cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel
user_registration POST /users(.:format) devise/registrations#create
new_user_registration GET /users/sign_up(.:format) devise/registrations#new
edit_user_registration GET /users/edit(.:format) devise/registrations#edit
PATCH /users(.:format) devise/registrations#update
PUT /users(.:format) devise/registrations#update
DELETE /users(.:format) devise/registrations#destroy
root GET / tweets#index
tweets GET /tweets(.:format) tweets#index
POST /tweets(.:format) tweets#create
new_tweet GET /tweets/new(.:format) tweets#new
tweet DELETE /tweets/:id(.:format) tweets#destroy
user GET /users/:id(.:format) users#show
|
rake routes
コマンドを実行すると例えば上記のような出力結果が表示される。(完全に同様の出力結果でなくても構わない。)この出力結果は以下のような意味を持っている。
項目名 | 意味 |
---|---|
Prefix | 「Prefix」はルーティングの名前を表しています。「Prefix」は設定を行わなくても、自動的に割り当てられますが、自ら設定することもできる。 |
Verb | 「Verb」はHTTPメソッドを表している。右側に表示されているパスにどのHTTPメソッドでアクセスするかを示している。 |
URI Pattern |
「URI Pattern」はルーティングのパスを表している。このパスにアクセスされた際に、指定のコントローラとアクションで処理が行われる。 |
Controller#Action | 「Controller#Action」は指定したパスにアクセスした際に処理が行われるコントローラとアクションを表している。#以前がコントローラ名、#以後がアクション名を示している。 |
また、/tweets(.:format) の後ろについている(.:format)とは、formatオプションとよばれるもので、html形式やjson形式といった複数の形式で出力することができるというもの。
問題11:削除ボタンを作成する
作業ファイル:app/views/tweets/index.html.erb
ヒント1:下記の例をもとに、link_toメソッドを利用する。
ヒント2:rake routesで確認した、tweetsコントローラのdestroyメソッドを動かすhttpメソッド/パスの組み合わせを書く
ヒント3:Rubyタグの中の文字列内で変数を使いたい場合は式展開#{}を使用する。詳しくはトラブルシューティング(Rails編)で確認する。
ヒント4:削除したいツイートを投稿したユーザーのidは変数`tweet`を使用して取得する。
【例】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//省略
<div class="content_post" style="background-image: url(<%= tweet.image %>);">
<% if ユーザーがログインしているか? かつ current_user.id == 削除したいツイートを投稿したユーザーのid %>
<div class="more">
<span><%= image_tag 'arrow_top.png' %></span>
<ul class="more_list">
<li>
<%= link_to '削除', "/tweets/削除したいツイートのid", method: :HTTPメソッドを指定 %>
</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>
//省略
|
3.destroyアクションをコントローラに定義
ツイートを削除する際にはtweetsコントローラのdestroyアクションを動かす。そこで、このアクションをコントローラに定義していく。
destroyメソッド
ActiveRecordメソッドのうちの一つで、インスタンスに対してそのレコードを削除する際に使うことができる。ターミナルで実行して確認する。
1 2 3 4 |
すると、以下のような結果になり、レコードが削除される。
1 2 3 4 |
では、tweets_controller
にdestroy
アクションを実装していく。 ツイートを削除する際にはdestroy
メソッドを使う。また、どのツイートを削除するのかを特定する場合はparams[:id]
を使用して、削除したいツイートの情報を取得する。
削除を行えるのは、そのツイートを投稿したユーザーのみ。そのため、別のユーザーが削除をできないように条件をつける必要がある。
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 |
class TweetsController < ApplicationController
before_action :move_to_index, except: :index
def index
@tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
end
def new
end
def create
Tweet.create(image: tweet_params[:image], text: tweet_params[:text], user_id: current_user.id)
end
def destroy
tweet = Tweet.find(params[:id])
if tweet.user_id == current_user.id
tweet.destroy
end
end
private
def tweet_params
params.permit(:image, :text)
end
def move_to_index
redirect_to action: :index unless user_signed_in?
end
end
|
後置if
最後のendを省略してif文を処理の後方に配置する書き方。elsif, else
にあたる条件分岐が無く、かつ処理が一行で完結する場合に用いる。
コード量も少なくシンプルに記述できるので、今回のdestroy
メソッドは以下のように書き直す。
16 17 18 19 |
4.削除後のビューを追加
ルーティングとコントローラの編集が終わったので、続いてビューファイルの追加を行う。このビューファイルはツイートの削除を行ったあとに呼び出されるビューファイルになる。
①ファイルの新規作成
app/views/tweetsディレクトリの中にdestroy.html.erb
というファイルを作成
②ファイルの編集
作成したファイルを、以下のように編集。
1 2 3 4 5 6 7 8 |
<div class="contents row">
<div class="success">
<h3>
削除が完了しました。
</h3>
<a class="btn" href="/">投稿一覧へ戻る</a>
</div>
</div>
|
localhost:3000にアクセスして、削除機能を試してみる
① ログインする。
② ログインしたユーザーでツイートの投稿する。一番上に表示される。
③ 削除ボタンをクリックする。
④ 削除完了画面が表示される。
⑤ トップページに戻り、②のツイートがなくなっていれば、削除機能は実装完了。
12:ツイートの編集機能を追加
編集を行う際には編集ページを用意して、そのページから編集を行うようにする。
作業内容
1.ツイート編集画面のルーティングを設定する
2.編集ボタンをビューに追加する
3.editアクションをコントローラに定義する
4.編集画面のビューファイルを作成する
5.ツイートの更新を行うためのルーティングを設定する
6.updateアクションをコントローラに定義する
7.更新後のビューファイルを追加する
1.ツイート編集画面のルーティングを設定
ツイートを編集する際は、ツイートを編集するための画面に遷移したあとに、ツイートの編集を行う。今回の作業では、編集画面へ遷移するためのルーティングを設定する。
編集画面への遷移にはコントローラtweetsコントローラのeditアクションを動かす。また、編集画面のパスはツイートごとに異なるものになるので、destroyメソッドの時と同じように:id
のような書き方を利用する。
1 2 3 4 5 6 7 8 9 10 |
Rails.application.routes.draw do
devise_for :users
root 'tweets#index'
get 'tweets' => 'tweets#index'
get 'tweets/new' => 'tweets#new'
post 'tweets' => 'tweets#create'
delete 'tweets/:id' => 'tweets#destroy'
get 'tweets/:id/edit' => 'tweets#edit'
get 'users/:id' => 'users#show'
end
|
2.編集ボタンをビューに追加
ツイートを編集する画面に遷移するためのボタンを作成する。編集ボタンは、削除ボタンの上に表示されるようする。削除ボタンと同じように、link_to
メソッドを使用する。
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 %>);">
<% if user_signed_in? && tweet.user_id == current_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>
<% end %>
<%= paginate(@tweets) %>
</div>
|
3.editアクションをコントローラに定義
ツイートを編集する画面を表示する際にはtweetsコントローラのeditアクションを使用する。そこで、コントローラにeditアクションを作成していく。
現在実装している編集して更新、という流れは、入力して新規投稿、という流れと似ている。新規作成時と違うのは、編集→更新の場合は既に存在しているレコードを選択して中身を書き換えるという点。そのため、edit
アクションでは編集したいレコードを@tweet
に代入し、編集画面で利用できるようにする。
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 |
class TweetsController < ApplicationController
before_action :move_to_index, except: :index
def index
@tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
end
def new
end
def create
Tweet.create(image: tweet_params[:image], text: tweet_params[:text], user_id: current_user.id)
end
def destroy
tweet = Tweet.find(params[:id])
tweet.destroy if tweet.user_id == current_user.id
end
def edit
@tweet = Tweet.find(params[:id])
end
private
def tweet_params
params.permit(:image, :text)
end
def move_to_index
redirect_to action: :index unless user_signed_in?
end
end
|
4.編集画面のビューファイルを作成
ルーティングとコントローラの編集が終わったので、ビューファイルを作成する。このビューはツイートを編集するためのビューファイルである。そのため、ツイートの投稿画面と同様に情報を入力するためのフォームが必要になる。
入力情報の送信先は/tweets/編集するツイートのid
となる。また、情報を送信する際にはpatch
というHTTPメソッドを使用する。
patchメソッド
HTTPリクエストのpatch
メソッドは情報の更新を行う際に使用する。今回はツイート情報を更新する機能を実装するので、patch
メソッドを使用する。
HTTPリクエスト
Railsでは以下の4つのメソッドを利用して開発を行う。
メソッド | 役割 |
---|---|
get | サーバーからブラウザに情報を返す。単にウェブサイトを閲覧する際にはこのメソッドが利用されている。 |
post | ブラウザからサーバーに情報を送信し、サーバーに情報を保存する。情報の登録などの際に、サーバーに情報を送信するために利用される。 |
delete | ブラウザからサーバーに情報を送信し、サーバーの情報を削除する。アカウントの削除などの際に、サーバー内のデータを削除するために利用される。 |
patch | ブラウザからサーバーに情報を送信し、サーバー内の情報を置き換える。登録情報の更新などの際に、サーバー内のデータを更新するために利用される。 |
put | メソッド名は違うが、patchと同じ役割を持っている。rails3まではputを使っていたが、rails4からはpatchに移行したためカリキュラム内では使用しない。 |
フォームを作成する際にはフォームの初期値として、編集前のツイート情報を表示する。
①ファイルの新規作成
app/views/tweetsディレクトリの中にedit.html.erb
というファイルを作成
②ファイルの編集
問題12:編集画面を作成する
作業ファイル:app/views/tweets/edit.html.erb
ヒント1:下記の例をもとに編集画面を作成する。
ヒント2:このビューでは、コントローラで定義した@tweetを利用できます。@tweetには、編集したいレコードが代入されている。
ヒント3:form_tagメソッドのあたりは、tweetsのnewアクションのビューを参考にする。
ヒント4:編集前の値を表示するには、タグを利用する必要がある。
【例】
1 2 3 4 5 6 7 8 9 10 |
<div class="contents row">
form_tagメソッドを使って「/tweets/編集するツイートのid」に「patch」メソッドで情報を送信
<h3>
編集する
</h3>
<input placeholder="Image Url" type="text" name="image" value=編集前の初期値を表示 autofocus="true">
<textarea cols="30" name="text" placeholder="text" rows="10">編集前の初期値を表示</textarea>
<input type="submit" value="SENT">
<% end %>
</div>
|
5.ツイートの更新を行うためのルーティングを設定
ツイートの更新を行うためのルーティングを設定していく。
ツイートを更新する際には/tweets/編集するツイートのid
にpatch
メソッドでアクセスする。このパスにアクセスした際にtweetsコントローラのupdateアクションが動くようにする。
1 2 3 4 5 6 7 8 9 10 11 |
Rails.application.routes.draw do
devise_for :users
root 'tweets#index'
get 'tweets' => 'tweets#index'
get 'tweets/new' => 'tweets#new'
post 'tweets' => 'tweets#create'
delete 'tweets/:id' => 'tweets#destroy'
patch 'tweets/:id' => 'tweets#update'
get 'tweets/:id/edit' => 'tweets#edit'
get 'users/:id' => 'users#show'
end
|
6.updateアクションをコントローラに定義
ツイートを更新する際にはtweetsコントローラのupdateアクションを使用。updateアクション内ではActiveRecordメソッドのupdate
を使用する。
updateメソッド
update
はActiveRecordメソッドのうちの一つ。update
メソッドはモデルのインスタンスに対して使用することで、引数内の情報にレコードを更新することができる。
1 2 3 4 5 6 |
[1] pry(main)> tweet = Tweet.create(user_id: 1, text: "Hello", image: "test.jpg")
=> #<Tweet id: 1, user_id: 1, text: "Hello", image: "test.jpg", created_at: "2014-12-10 00:00:00", updated_at: "2014-12-10 00:00:00">
[2] pry(main)> tweet.update(text: "Thank you!")
=> true
[3] pry(main)> tweet
=> #<Tweet id: 1, user_id: 1, text: "Thank you!", image: "test.jpg", created_at: "2014-12-10 00:00:00", updated_at: "2014-12-10 00:01:00">
|
update
メソッドの引数は、update(カラム名: 更新する情報)
という形で指定する。同時に複数の情報を更新する場合にはupdate(カラム1: 更新情報1, カラム2: 更新情報2)
という形で使用することができる。create
メソッドと似ている。
問題13:updateアクションを実装する
作業ファイル:app/controllers/tweets_controller
ヒント1:destroyアクションの実装の時と同じような流れ。
ヒント2:ツイートを更新する際にはActiveRecordメソッドのupdateメソッドを使用する。また、updateメソッドの引数にはtweet_paramsを使用する。
7.更新後のビューファイルを追加
続いてビューファイルの追加を行う。このビューファイルはツイートの更新を行ったあとに呼び出されるビューファイル。
①ファイルの新規作成
app/views/tweetsディレクトリの中にupdate.html.erb
というファイルを作成。
新規作成されるファイル
- app/views/tweets/update.html.erb
②ファイルの編集
1 2 3 4 5 6 7 8 |
<div class="contents row">
<div class="success">
<h3>
更新が完了しました。
</h3>
<a class="btn" href="/">投稿一覧へ戻る</a>
</div>
</div>
|
localhost:3000にアクセスして編集機能を試す
① ログインする。
② ツイートを投稿する。
③ ②で投稿したツイートは一番上にあるので、そのツイートの編集ボタンをクリックする。
④ 編集画面が表示されるので、テキストを変更する。
⑤ 更新完了画面が表示される。
⑥ トップページに戻り、一番上のツイートのテキストが変更されていることを確認。
⑦ これで編集機能実装は完了。
Railsのリソースの7つのアクション
Railsでは、7つの基本アクションへのリクエストが下記の通りデフォルトで設定されている。これらのリクエストは、routes.rb
にresources :ルーティングを設定したいコントローラ名
と書くだけで反映させることができる。
アプリケーションの作成において何か機能を追加したい時は、まずはこの7つのアクションに当てはまる機能かを考え、そうであれば積極的に利用する。ルーティングを楽に設定できると同時に、他の人がコードを読む際わかりやすいという利点がある。
Railsのリソースの 7つのアクション |
役割 |
---|---|
index | リソースの一覧を表示する。 |
show | リソースの内容を表示する。 |
new | リソースを追加する。 |
create | リソースを追加し、作成する。 |
edit | リソースを更新するためのフォームを表示する。 |
update | リソースを更新する。 |
destroy | リソースを削除する。 |