この記事では、binding.pryの使い方を解説しています。
binding.pryを活用することで、
・一次ソースに触れる機会が増え、学習効率が上がる
・binding.pryを複数設置して、paramsの流れが理解しやすくなる
など、たくさんメリットがあります。
自分みたいな、初学者方の参考になればと思い、記事にしてみました。
前提
チャットアプリを題材にbinding,pryの使い方を学びます。
(注意:この記事ではチャットアプリは完成しません!チャットアプリ作成の記事ではありません)
開発環境
・ruby 2.6.5
・Rails 6.0.3.3
完成イメージ
ER図
必要なテーブル
・usersテーブル
・roomsテーブル
・entriesテーブル (中間テーブルです!)
流れ
①user, room, entryモデル、テーブルを作成(下準備)
②アソシエーションを書く(下準備)
③Gemfileにpry-railsを追加してbundle installを実行(下準備)
④ビューにform_withを用意
⑤roomsコントローラーにcreateアクションを書く
①user, room, entryモデル、テーブルを作成(下準備)
userモデルのマイグレーションを編集
userモデルは、deviseを使って作成しているものとします!
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :nickname, null: false
t.string :email, null: false, default: ""
#省略
end
#省略
end
end
usersテーブルには、nicknameとemailのカラムを用意しています。
roomモデルのマイグレーションを編集
class CreateRooms < ActiveRecord::Migration[6.0]
def change
create_table :rooms do |t|
t.string :name, null: false
t.timestamps
end
end
end
roomsテーブルには、nameのカラムを用意しています。
entryモデルのマイグレーションを編集(下準備)
class CreateEntries < ActiveRecord::Migration[6.0]
def change
create_table :entries do |t|
t.references :room, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
entriesテーブルは、usersテーブルとroomsテーブルを繋ぐ中間テーブルなので、
user, roomそれぞれを外部キーとして、references型で保存するようにしています。
②アソシエーションを書く(下準備)
一人のuserは複数のroomに入れて、
一つのroomは複数人のuserが入るので、
usersテーブルとroomsテーブルは「多対多」の関係になります。
以下のように、アソシエーションを記述します。
userモデル
class User < ApplicationRecord
#省略
has_many :entries
has_many :rooms, through: :entries
end
roomモデル
class Room < ApplicationRecord
has_many :entries
has_many :users, through: :entries
end
entryモデル
class Entry < ApplicationRecord
belongs_to :room
belongs_to :user
end
③gem 'pry-rails' をインストール(下準備)
pry-railsをインストールできるようGemfileに記述して、bundle installを実行します。
参考 : rweng/pry-rails: Rails >= 3 pry initializer - GitHub
gem 'pry-rails'
% bundle install
④ビューにform_withを用意
新しくルームを作成するために、roomsコントローラーにnewアクションを定義します。
class RoomsController < ApplicationController
def new
@room = Room.new #newメソッドでインスタンスを作成
@users = User.all #全ユーザーのレコードを取得
end
end
ビューには、フォームを設置します。
チャットルーム名(name)を入力し、チャットしたい相手(user_ids)を選べるようにします。
<%= form_with model: @room, local: true do |f| %>
<%= f.label :チャットルーム名%>
<%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %>
<label>チャットしたい相手</label>
<select name="room[user_ids][]"> <%# 選択したユーザーを取得 %>
<option value="">未選択</option>
<% @users.each do |user| %>
<option value=<%= user.id %>><%= user %></option>
<% end %>
</select>
<%= f.submit %>
<% end %>
ここで、user_ids
と複数形になっているのは、自分と相手の2人分保存するからです!
userモデルにて、has_many
を定義したことで、_ids
メソッドが使えるようになりました!
参考 : Active Record の関連付け - Railsガイド
実際にフォームへ入力してみます。
すると下記のように、ユーザーが誰が誰だか分からないではありませんか!
では、何が原因でこの出力が得られたのか推測します。
考えやすくするために、ユーザー選択の記述を、rubyの文法で書き直してみます。
@users.each do |user|
user
end
@users
は、roomsコントローラーのnewアクションで定義しているインスタンス変数で、
@users = User.all
と定義しています。
ビューの中では、each文による繰り返し処理によって、@users
から一人ずつ取り出しています。
では、binding.pry
を使って、出力される値を確認してみましょう。
<%= form_with model: @room, local: true do |f| %>
<%= f.label :チャットルーム名%>
<%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %>
<label>チャットしたい相手</label>
<select name="room[user_ids][]"> <%# 選択したユーザーを取得 %>
<option value="">未選択</option>
<% @users.each do |user| %>
<% binding.pry %> <%# 👈each文の中にbinding.pryを設置!! %>
<option value=<%= user.id %>><%= user %></option>
<% end %>
</select>
<%= f.submit %>
<% end %>
ブラウザをリロードすると、ターミナルに以下のような出力が表示されます。
3: <%= f.text_field :name%>
4: <p><label>チャットしたい相手</label></p>
5: <select name="room[user_ids][]">
6: <option value="">未選択</option>
7: <% @users.each do |user| %>
=> 8: <% binding.pry %>
9: <option value=<%=user.id%>><%= user %></option>
10: <% end %>
11: </select>
13: <p><%= f.submit%></p>
[1] pry(#<#<Class:xxxx>>)>
=>
で、ビューの8行目で処理を止めてるよ!とターミナルが教えてくれています。
7〜10行間は、each文で繰り返し処理していることから、
繰り返し処理の1回目
で、一時的に処理を止めてくれています。
したがって、user
には一人目のデータ
が格納されていると考えられます。
では、実際にuser
の値を確認してみましょう。
[1] pry(#<#<Class:xxxx>>)> user
=> #<User id: 1, nickname: "user_1", email: "test@1">
[2] pry(#<#<Class:xxxx>>)> user.nickname
=> "user_1"
[1]pry>
の後に、式
を入力することで、
=>
後に、値
を出力してくれます。
user
には、idが1であるユーザーのレコード
が格納されていることが確認できました。
今回はユーザー名を一覧表示させたいので、nickname
の値だけを取り出すことにします。
2回目のpryで、user.nickname
と記述すると、ユーザー名を取り出せると確認できました。
したがって、ビューファイルを下記のように書き換えます。
<%= form_with model: @room, local: true do |f| %>
<%= f.label :チャットルーム名%>
<%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %>
<label>チャットしたい相手</label>
<select name="room[user_ids][]"> <%# 選択したユーザーを取得 %>
<option value="">未選択</option>
<% @users.each do |user| %>
<option value=<%= user.id %>><%= user.nickname %></option> <%# 👈user.nicknameを表示するように変更!! %>
<% end %>
</select>
<%= f.submit %>
<% end %>
無事にユーザー名を一覧表示することができました!
しかし、この一覧表示には、一つだけ問題があります。
それは、自分自身も表示されている
ことです。
このままでは、自分しかいない孤独なチャットルームが作成されてしまいます...。
このような事態を防ぐために、自分以外のユーザーを一覧表示
するようにします。
どんな式が必要か、binding.pry
を活用して探していきます。
3: <%= f.text_field :name%>
4: <p><label>チャットしたい相手</label></p>
5: <select name="room[user_ids][]">
6: <option value="">未選択</option>
7: <% @users.each do |user| %>
=> 8: <% binding.pry %>
9: <option value=<%= user.id %>><%= user.nickname %></option>
10: <% end %>
11: </select>
12: <p><%= f.submit%></p>
[1] pry(#<#<Class:xxxx>>)>
まず初めに、@users
の中身を確認しましょう。
[1] pry(#<#<Class:xxxx>>)> @users
=> [#<User id: 1, nickname: "user_1", email: "test@1">,
#<User id: 2, nickname: "user_2", email: "test@2">,
#<User id: 3, nickname: "user_3", email: "test@3">,
#<User id: 4, nickname: "user_4", email: "test@4">,
#<User id: 5, nickname: "user_5", email: "test@5">] # 👈現在のユーザー(current_user)
@users
には、全ユーザーのデータ
が、一人ずつ配列で格納されていることが確認できます。
ではそもそも、インスタンス変数@users
とは何と定義していたかというと、
User.all
と等しいよ!と定義していましたね。
では、User.all
の内容を確認しましょう。
[2] pry(#<#<Class:xxxx>>)> User.all
=> [#<User id: 1, nickname: "user_1", email: "test@1">,
#<User id: 2, nickname: "user_2", email: "test@2">,
#<User id: 3, nickname: "user_3", email: "test@3">,
#<User id: 4, nickname: "user_4", email: "test@4">,
#<User id: 5, nickname: "user_5", email: "test@5">]
@users
と全く同じデータが出力されることが確認できました。
続いて、現在のユーザーのデータ
を取り出せないか試してみます。
[3] pry(#<#<Class:xxxx>>)> User.all.where(id: current_user)
=> [#<User id: 5, nickname: "user_5", email: "test@5">]
.where
はActive Recordのメソッドの一つで、条件に該当するレコードを配列に格納して出力してくれます。
超便利なのでどんどん使っていきましょう。
参考 : Active Record の基礎 - Railsガイド
さて、現在のユーザーのデータを取り出すことができました。
ということは、現在のユーザー以外のデータも取り出せるのでは?と思いつきます。
.where.not
メソッドを使うと良さそうです。
.where.not
は、条件に該当しないレコードを配列に格納して出力してくれる、.where
と対をなすメソッドです。
[4] pry(#<#<Class:xxxx>>)> User.all.where.not(id: current_user)
=> [#<User id: 1, nickname: "user_1", email: "test@1">,
#<User id: 2, nickname: "user_2", email: "test@2">,
#<User id: 3, nickname: "user_3", email: "test@3">,
#<User id: 4, nickname: "user_4", email: "test@4">]
現在のユーザーはuser_5
なので、現在のユーザー以外のデータ
が出力されています。
これで、現在のユーザー以外を一覧表示させる式を見つけることができました!
したがって、@users
の定義を変更しましょう。
class RoomsController < ApplicationController
def new
@room = Room.new #newメソッドでインスタンスを作成
@users = User.all.where.not(id: current_user) #現在のユーザー以外のレコードを取得
end
end
⑤roomsコントローラーにcreateアクションを書く
roomsコントローラーにcreateアクションを定義します。
class RoomsController < ApplicationController
def new
@room = Room.new
@users = User.all.where.not(id: current_user)
end
def create #createアクションを定義
binding.pry
end
end
フォームで入力した情報(リクエスト)を確認したいので、
この段階では、createアクションには何も処理は定義せず、
binding.pryを設置しておきます。
こうすることで、
「フォームで入力された情報が届いたよー!」
と、ルーティングを介して、roomsコントローラーのcreateアクションを実行する瞬間に、
処理を止めることができます。
では、フォームにルーム名room1
、チャットしたい相手user_2
と選択して送信します。
すると下記のようにターミナル上で、createアクション内で定義したbinding.pry
で処理を止めてるよと教えてくれます。
7: def create
=> 8: binding.pry
9: end
[1] pry(#<RoomsController>)>
では、リクエストのパラメータを確認してみましょう。
[1] pry(#<RoomsController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"xxxxxxx==", "room"=>{"name"=>"room1", "user_ids"=>["2"]}, "commit"=>"Create Room", "controller"=>"rooms", "action"=>"create"} permitted: false>
params
(パラムス)はパラメーターズの略です。
authenticity_token
は、セキュリティのために生成されるトークンなので、今回は無視します。
room
の中に、form_withで入力したパラメータが、配列としてハッシュで管理されています。
このroom
とは、form_withで用意した、model: @room
と対応しています。
では、paramsの中の、room
の情報だけ見てみます。
[2] pry(#<RoomsController>)> params[:room]
=> <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2"]} permitted: false>
params[:xxxx]
とすることで、見たいパラメータxxxx
だけを確認できます。
上記の結果から、チャットルームroom1
にuser_idが2
のユーザーが入ったことが確認できます。
ちゃんとルームに人を呼べていることが確認できましたね。
めでたしめでたし.....
って、自分自身がルームに入ってないじゃん!!!
ここから、自分もルームに入れるよう、ビューを書き換えきます!
<%= form_with model: @room, local: true do |f| %>
<%= f.label :チャットルーム名%>
<%= f.text_field :name%> <%# 入力したチャットルーム名を取得 %>
<label>チャットしたい相手</label>
<select name="room[user_ids][]"> <%# 選択したユーザーを取得 %>
<option value="">未選択</option>
<% @users.each do |user| %>
<option value=<%= user.id %>><%= user.nickname %></option>
<% end %>
</select>
<input name="room[user_ids][]" type="hidden" value=<%=current_user.id%>> <%# 👈現在のユーザーもroomに追加するように変更!! %>
<%= f.submit %>
<% end %>
input
は、formにおけるテキストフィールドの種類を指定します。
hidden
属性を指定することで、ブラウザには表示せずにパラメータとしてデータを受け渡すことができます。
この記述では、user_ids
にcurrent_user(現在のユーザー)
も含まれるよう記述しています!
ではもう一度、フォームにルーム名room1
、チャットしたい相手user_2
と選択して送信します。
[1] pry(#<RoomsController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"xxxxxxx==", "room"=>{"name"=>"room1", "user_ids"=>["2", "5"]}, "commit"=>"Create Room", "controller"=>"rooms", "action"=>"create"} permitted: false>
[2] pry(#<RoomsController>)> params[:room]
=> <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: false>
無事に現在のユーザー(user_5)
がuser_ids
に含まれていることが確認できました!
続いて、createアクションを定義し直して、テーブルにデータが保存できるようにしましょう。
class RoomsController < ApplicationController
#省略
def create
@room = Room.new(room_strong_params)
if @room.save
redirect_to root_path
else
render :new
end
end
private
def room_strong_params
params.require(:room).permit(:name, user_ids: [])
end
end
ストロングパラメータは、roomモデル
の、name
とuser_ids
のパラメータだけ許可するとしています。
createアクションの内部で、どのようにパラメータの受け渡しがされているか、binding.pry
を使って確認しましょう。
#省略
def create
@room = Room.new(room_strong_params)
binding.pry # 👈binding.pryを設置!!
if @room.save
binding.pry # 👈binding.pryを設置!!
redirect_to root_path
else
render :new
end
end
インスタンス変数@room
が、保存される前、後でパラメータをそれぞれ確認してみます。
では、フォームにルーム名room1
、チャットしたい相手user_2
と選択して送信します。
7: def create
8: @room = Room.new(room_strong_params)
=> 9: binding.pry
10: if @room.save
11: binding.pry
12: redirect_to root_path
13: else
14: render :new
15: end
16: end
[1] pry(#<RoomsController>)>
@room
保存前の各パラメータを確認します。
[1] pry(#<RoomsController>)> params[:room]
=> <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: false>
[2] pry(#<RoomsController>)> @room
=> #<Room:xxxx id: nil, name: "room1", created_at: nil, updated_at: nil>
[3] pry(#<RoomsController>)> room_strong_params
=> <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: true>
2回目のpryに注目してください。
@room
は、id: nil
であることから、この時点では、レコードは作成されていないと分かります。
3回目のpryでは、ストロングパラメータを確認していますが、name
とuser_ids
に値が正しく格納されていることが確認できます。
リクエストしたパラメータが、コントローラーのcreateアクションに正しく受け渡されているのに、まだレコードが作成されていない理由は、
.new
メソッドでインスタンスを作成する場合、
.save
メソッドを実行して初めてデータベースにレコードとしてコミットされるからです。
では、@room.save
後を確認してみます。
7: def create
8: @room = Room.new(room_strong_params)
9: binding.pry
10: if @room.save
=> 11: binding.pry
12: redirect_to root_path
13: else
14: render :new
15: end
16: end
[1] pry(#<RoomsController>)> params[:room]
=> <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: false>
[2] pry(#<RoomsController>)> @room
=> #<Room:xxxx id: 1, name: "room1">
[3] pry(#<RoomsController>)> room_strong_params
=> <ActionController::Parameters {"name"=>"room1", "user_ids"=>["2", "5"]} permitted: true>
2回目のpryに注目してください。
@room
は、id: 1
であることから、レコードは正常に保存されました!
今回は無事に保存できたのですが、
レコードを保存できなかった時に使える便利なメソッドも、合わせて紹介します!
[4] pry(#<RoomsController>)> @room.valid?
=> true
[5] pry(#<RoomsController>)> @room.errors
=> #<ActiveModel::Errors:xxxx @base=#<Room:xxxx id: 1, name: "room1">, @details={}, @messages={}>
[4] pryの@room.valid?
では、「@room
のバリデーションはOK?」みたいな感じで、
バリデーションを実行してエラーがあるかを判別します。
エラーが無ければtrue
を,
エラーが有ればfalse
を返します。
[5] pryの@room.errors
では、@room.valid?でfalseが返された時に、エラーメッセージを出力してくれます。
今回はエラーはないので、エラーメッセージは出力されていません。
エラーがある時はmessages{}
の中にエラーメッセージが格納されます。
最後に、保存されたデータをコンソールで確認してみましょう。
% rails c
[1] pry(main)> Room.all
=> [#<Room:xxxx id: 1, name: "room1">]
[2] pry(main)> Entry.all
=> [#<Entry:xxxx id: 1, room_id: 1, user_id: 2>,
#<Entry:xxxx id: 2, room_id: 1, user_id: 5>]
コンソールでも、Active Recordのメソッドを使うことができます。
Room.all
で全てのルームを表示させると、
room1が保存されていることが確認できます。
Entry.all
で全てのレコードを表示させると、
2つのレコードが保存されていることが確認できます。
「ルーム1にユーザー2と5がいるよーっ!」と教えてくれています。
コンソールで確認した内容は、以下の表と同じ内容です!
roomsテーブル
id | name |
---|---|
1 | room1 |
entriesテーブル
id | room_id | user_id |
---|---|---|
1 | 1 | 2 |
2 | 1 | 5 |
最後までお付き合いいただきありがとうございました!