2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

【Rails】 binding.pryの活用方法

この記事では、binding.pryの使い方を解説しています。
 
binding.pryを活用することで、
・一次ソースに触れる機会が増え、学習効率が上がる
・binding.pryを複数設置して、paramsの流れが理解しやすくなる

など、たくさんメリットがあります。
 
自分みたいな、初学者方の参考になればと思い、記事にしてみました。

前提

チャットアプリを題材にbinding,pryの使い方を学びます。
(注意:この記事ではチャットアプリは完成しません!チャットアプリ作成の記事ではありません)

開発環境
・ruby 2.6.5
・Rails 6.0.3.3

完成イメージ

chat_demo.gif

ER図

名称未設定ファイル (1).png

必要なテーブル
・usersテーブル
・roomsテーブル
・entriesテーブル (中間テーブルです!)

流れ

①user, room, entryモデル、テーブルを作成(下準備)

②アソシエーションを書く(下準備)

③Gemfileにpry-railsを追加してbundle installを実行(下準備)

④ビューにform_withを用意

⑤roomsコントローラーにcreateアクションを書く

①user, room, entryモデル、テーブルを作成(下準備)

userモデルのマイグレーションを編集
userモデルは、deviseを使って作成しているものとします!

db/migrate/xxxx_devise_create_users.rb
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モデルのマイグレーションを編集

db/migrate/xxxx_create_rooms.rb
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モデルのマイグレーションを編集(下準備)

db/migrate/xxxx_create_entries.rb
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モデル

app/models/user.rb
class User < ApplicationRecord
  #省略
  has_many :entries
  has_many :rooms, through: :entries
end

roomモデル

app/models/room.rb
class Room < ApplicationRecord
  has_many :entries
  has_many :users, through: :entries
end

entryモデル

app/models/entry.rb
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

Gemfile
gem 'pry-rails'
ターミナル
% bundle install

④ビューにform_withを用意

新しくルームを作成するために、roomsコントローラーにnewアクションを定義します。

app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
  def new
    @room = Room.new #newメソッドでインスタンスを作成
    @users = User.all #全ユーザーのレコードを取得
  end
end

 
ビューには、フォームを設置します。
チャットルーム名(name)を入力し、チャットしたい相手(user_ids)を選べるようにします。

app/views/rooms/new.html.erb
<%= 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を使って、出力される値を確認してみましょう。

app/views/rooms/new.html.erb
<%= 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と記述すると、ユーザー名を取り出せると確認できました。
したがって、ビューファイルを下記のように書き換えます。

app/views/rooms/new.html.erb
<%= 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の定義を変更しましょう。

app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
  def new
    @room = Room.new #newメソッドでインスタンスを作成
    @users = User.all.where.not(id: current_user) #現在のユーザー以外のレコードを取得
  end
end

ユーザー一覧表示_自分以外_成功
これで自分だけのチャットルームを作らないよう設定できました。
 
めでたしめでたし...と言いたいところですが、
roomを保存できるか確認してみましょう。

⑤roomsコントローラーにcreateアクションを書く

roomsコントローラーにcreateアクションを定義します。

app/controllers/rooms_controller.rb
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だけを確認できます。
上記の結果から、チャットルームroom1user_idが2のユーザーが入ったことが確認できます。
ちゃんとルームに人を呼べていることが確認できましたね。
 
めでたしめでたし.....
 
って、自分自身がルームに入ってないじゃん!!!
 
ここから、自分もルームに入れるよう、ビューを書き換えきます!

app/views/rooms/new.html.erb
<%= 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_idscurrent_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アクションを定義し直して、テーブルにデータが保存できるようにしましょう。

app/controllers/rooms_controller.rb
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モデルの、nameuser_idsのパラメータだけ許可するとしています。
 
createアクションの内部で、どのようにパラメータの受け渡しがされているか、binding.pryを使って確認しましょう。

app/controllers/rooms_controller.rb
  #省略
  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では、ストロングパラメータを確認していますが、nameuser_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

最後までお付き合いいただきありがとうございました!:smile:

参考資料

【Rails】find・find_by・whereについてまとめてみた

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
2
Help us understand the problem. What are the problem?