【Rails】pry-byebug(binding.pry)で効率よくデバッグ!インストールから使い方・Tipsまで

Ruby on Rails

もっと効率よくデバッグできないだろうか

そんな疑問にお答えします。

エラーになったけど、そこで手が止まってしまう。
初心者の頃はよくあることです。

デバッグの基本はどの変数にどの値が入っているかを追う技術です。
しかし慣れないうちはコードを眺めていただけではなかなか理解できないですよね。

そのようなときは「デバッガ」を使うことでもっとカンタンに、そして効率よく把握できるようになります。
本記事では Rails のデバッガ pry-byebug の導入と使い方を解説し、具体的な使い方としてさまざな Tips を紹介します。

この記事を読んで実践すればデバッグスキルが格段に向上しますよ

スポンサーリンク

pry-byebug とは

pry-byebug は Rails で使えるメジャーなデバッガです。

pry-byebug でできること

  • ブレークポイントを設置して一時停止
  • ステップ実行
  • 変数に入っている値の確認

インストール

インストールはほんの2ステップだけです。

ステップ1: Gemfile に追記

まずは Gemfile に以下を追加します。

gem 'pry-byebug', group: :development

group: :development を指定しているのは、デバッガは開発中しか使用しないので本番環境(production)ではインストールしないようにするためです。

ステップ2: bundle install でインストール

Gemfile に追記したらインストールを実行しましょう。

$ bundle install

準備はこれだけです。
カンタンですね!

使用の流れ

ブレークポイントの設置

止めたい行の前に以下を追記します。

binding.pry

例えば

  def create
    @post = Post.new(post_params)
    if @post.save
      flash[:success] = 'Success!'
      redirect_to @post
    else
      flash.now[:danger] = 'Failed to create post!'
      render :new
    end
  end

こんなコードがあったとして、このメソッドの先頭で止めたいと思ったら、

  def create
    binding.pry # ← ここに追記
    @post = Post.new(post_params)
    if @post.save
      flash[:success] = 'Success!'
      redirect_to @post
    else
      flash.now[:danger] = 'Failed to create post!'
      render :new
    end
  end

こんな感じで追記します。

確認したい変数を設定している行の前後に設置するのがコツです。

実行してブレークポイントで止める

それでは該当するアクションをブラウザで実行してみましょう。

すると

    15: def create
    16:   binding.pry
 => 17:   @post = Post.new(post_params)
    18:   if @post.save
    19:     flash[:success] = 'Success!'
    20:     redirect_to @post
    21:   else
    22:     flash.now[:danger] = 'Failed to create post!'
    23:     render :new
    24:   end
    25: end

[1] pry(#<PostsController>)>

このように binding.pry の次の行で止まり、コマンド受付状態になります。

Rails を起動したターミナルを確認しましょう

ビューのプログラムでブレークポイントを設置したい場合

Ruby コードだけではなくビューのコードでも止めることができます。

binding.pry は Ruby のコードなので、Ruby のコードを実行するための記法で書く必要があります。

例えば ERB の場合は

<% binding.pry %>

のようになります。

代表的なコマンド

よく使うコマンドを紹介します。

next現在の行を実行し、次の行で停止させる
stepメソッド内に入る
$ソースコードを表示する
@今いる箇所を再表示する
continue実行を再開し、次のブレークポイントまで実行
!!!強制終了
disable-prypry を OFF にして処理を再開する
コードを書く変数やメソッドの実行して結果(戻り値)を表示する

それぞれ解説していきます。

next

nextコマンドは現在の行を実行は、次の行で停止させるコマンドです。

例えば先程の例で

    15: def create
    16:   binding.pry
 => 17:   @post = Post.new(post_params)
    18:   if @post.save
    19:     flash[:success] = 'Success!'
    20:     redirect_to @post
    21:   else
    22:     flash.now[:danger] = 'Failed to create post!'
    23:     render :new
    24:   end
    25: end

[1] pry(#<PostsController>)>

この状態の場合は、next を実行すると

[1] pry(#<PostsController>)> next

    15: def create
    16:   binding.pry
    17:   @post = Post.new(post_params)
 => 18:   if @post.save
    19:     flash[:success] = 'Success!'
    20:     redirect_to @post
    21:   else
    22:     flash.now[:danger] = 'Failed to create post!'
    23:     render :new
    24:   end
    25: end

[1] pry(#<PostsController>)>

一行進んでから、再度コマンド受付状態になります。

step

stepコマンドはメソッドの中に入るコマンドです。

デバッガ用語では「ステップイン」といったりします

例えば

    2: def index
    3:   binding.pry
 => 4:   @posts = Post.search("")
    5: end

[1] pry(#<PostsController>)>

この状態では、次の実行は右辺の search メソッドの実行となるので

    2: def self.search(keyword)
 => 3:   Post.where('content like ?', "%#{keyword}%")
    4: end

[1] pry(Post)>

Post クラスの search メソッドの最初の行で停止します。

$

$コマンドはソースコードを見るコマンドです。

$ を実行すると以下のように、現在実行中のメソッドを表示してくれます。

[1] pry(#<PostsController>)> $

From: app/controllers/posts_controller.rb:15:
Owner: PostsController
Visibility: public
Signature: create()
Number of lines: 11

def create
  binding.pry
  @post = Post.new(post_params)
  if @post.save
    flash[:success] = 'Success!'
    redirect_to @post
  else
    flash.now[:danger] = 'Failed to create post!'
    render :new
  end
end

[2] pry(#<PostsController>)>

これは

  • たくさんコマンドを実行してソースコードが画面外に消えてしまった
  • だけど、進めずに現在のコードを見たい!!

といった場合に使用します。

@

@ は今いる箇所を再表示するコマンドです。

binding.pry で停止すると最初に停止箇所を表示してくれます。

     6: def show
     7:   binding.pry
 =>  8:   @post = Post.find(params[:id])
     9:   binding.pry
    10:   @comments = @post.public_comments
    11: end

[1] pry(#<PostsController>)>

その後色々入力すると上記の停止箇所の表示がターミナルの画面外に出てしまいます。
もう一度今いる箇所を表示したいときに @ コマンドを使います。

[21] pry(#<PostsController>)> @

     6: def show
     7:   binding.pry
 =>  8:   @post = Post.find(params[:id])
     9:   binding.pry
    10:   @comments = @post.public_comments
    11: end

[22] pry(#<PostsController>)>

「今どこで止まってるんだっけ?」と迷子になったらとりあえず @ を入力してみることをおすすめします。

continue

continueコマンドは実行を再開し、次のブレークポイントにたどり着くまで実行するコマンドです。

ブレークポイントにたどり着けなければ最後まで実行します。

例えば

    15: def create
    16:   binding.pry
 => 17:   @post = Post.new(post_params)
    18:   if @post.save
    19:     binding.pry
    20:     flash[:success] = 'Success!'
    21:     redirect_to @post
    22:   else
    23:     binding.pry
    24:     flash.now[:danger] = 'Failed to create post!'
    25:     render :new
    26:   end
    27: end

[1] pry(#<PostsController>)>

この17行目で止まっている状態で continue を実行すると

[1] pry(#<PostsController>)> continue

    15: def create
    16:   binding.pry
    17:   @post = Post.new(post_params)
    18:   if @post.save
    19:     binding.pry
 => 20:     flash[:success] = 'Success!'
    21:     redirect_to @post
    22:   else
    23:     binding.pry
    24:     flash.now[:danger] = 'Failed to create post!'
    25:     render :new
    26:   end
    27: end

[1] pry(#<PostsController>)>

20行目まで実行が進んで停止します。(else節に行った場合は24行目で止まります)

さらに continue を実行すると

[1] pry(#<PostsController>)> continue
Redirected to http://localhost:3000/posts/9
Completed 302 Found in 207174ms (ActiveRecord: 4.9ms | Allocations: 449907)


Started GET "/posts/9" for ::1 at 2021-09-20 00:03:33 +0900
Processing by PostsController#show as HTML
  Parameters: {"id"=>"9"}
  Post Load (0.4ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 9], ["LIMIT", 1]]
  ↳ app/controllers/posts_controller.rb:8:in `show'
  Rendering layout layouts/application.html.erb
  Rendering posts/show.html.erb within layouts/application
  Rendered posts/show.html.erb within layouts/application (Duration: 0.1ms | Allocations: 4)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layout layouts/application.html.erb (Duration: 15.1ms | Allocations: 2953)
Completed 200 OK in 20ms (Views: 16.2ms | ActiveRecord: 0.4ms | Allocations: 3902)

このように最後まで実行されてレスポンスが返されます。

このように

  • 複数箇所で確認したいポイントに binding.pry を埋め込んで、飛び飛びに確認したい
  • 終わらせて残りを実行したい

と行った場合に continue コマンドを実行します。

!!!

!!!コマンドはデバッグを終了します。

たとえば

    14: def create
    15:   @post = Post.new(post_params)
    16:   binding.pry
 => 17:   if @post.save
    18:     flash[:success] = 'Success!'
    19:     redirect_to @post
    20:   else
    21:     flash.now[:danger] = 'Failed to create post!'
    22:     render :new
    23:   end
    24: end

[1] pry(#<PostsController>)>

この状態で 17 行目で待機している状態を考えます。

next や continue を実行すると save が実行されデータベースに保存されることになります。

このとき例えば

「コードをまちがえていることに気づいたので save させずに一旦プログラムを終了したい」

と考えた場合に !!! コマンドを実行するとそこでプログラムが終了します。

[1] pry(#<PostsController>)> !!!
Completed 500 Internal Server Error in 148165ms (ActiveRecord: 0.0ms | Allocations: 213081)



SystemExit (exit):

app/controllers/posts_controller.rb:17:in `create'

この場合は 17 行目が実行されないため save されずデータベースに保存される前に止めることができています。

disable-pry

disable-pry は pry を OFF にした状態で処理を再開します。

たとえば以下の状態で入力待ち状態になっているとします。

     6: def show
     7:   binding.pry
 =>  8:   @post = Post.find(params[:id])
     9:   binding.pry
    10:   @comments = @post.public_comments
    11: end

[1] pry(#<PostsController>)>

ここで disable-pry を入力すると9行目のbinding.pryでは停止せずに最後まで実行が進みます。

[1] pry(#<PostsController>)> disable-pry
〜略〜
Completed 200 OK in 68572ms (Views: 12.9ms | ActiveRecord: 0.8ms | Allocations: 123379)

binding.pry を気になる箇所にたくさん記述しているが、今回はもう最後まで実行してほしい。」
という場合に便利です。

コードを書く

コードを書くと、それが実行されて戻り値を表示します。

たとえば

    14: def create
    15:   @post = Post.new(post_params)
    16:   binding.pry
 => 17:   if @post.save
    18:     flash[:success] = 'Success!'
    19:     redirect_to @post
    20:   else
    21:     flash.now[:danger] = 'Failed to create post!'
    22:     render :new
    23:   end
    24: end

[1] pry(#<PostsController>)>

では、15行目で @post 変数へ代入が行われた状態で停止しています。

このとき

[1] pry(#<PostsController>)> @post
=> #<Post:0x00007f8128c8a950 id: nil, content: "cc", created_at: nil, updated_at: nil, title: "cc">
[2] pry(#<PostsController>)>

のように @post を入力すると @post の中身が確認できます。

その他にもメソッド実行などもできます。

たとえば同じ状況で、

[4] pry(#<PostsController>)> @post.to_json
=> "{\"id\":null,\"content\":\"cc\",\"created_at\":null,\"updated_at\":null,\"title\":\"cc\"}"
[5] pry(#<PostsController>)>

のように、@post に入っているインスタンスに対して to_json メソッドを呼び出すと、その戻り値が => 記号に続いて表示されます。

デバッグでよくやるパターン(TIPS)

pry-byebug を使用すればやりたいことは大体できます。

ここでは個人的によくやるパターンを紹介します。

  • HTTPパラメータの確認
  • インスタンスのクラスの確認
  • インスタンスが持っているメソッドの確認
  • バリデーションエラーの確認
  • 条件付きブレークポイント
  • これから呼び出そうとしているメソッドのコードを確認したい
  • クラスのソースコードを確認したい
  • クラス#メソッドのソースコードを確認したい

それぞれ解説していきます。

HTTPパラメータの確認

WebアプリケーションはHTTPリクエストを入力としてHTTPレスポンスを出力とするプログラムです。
HTTPリクエストの中でもHTTPパラメータは動作上とても大切です。

デバッガでHTTPパラメータを確認するには params メソッドの戻り値を確認すればいいです。

[5] pry(#<PostsController>)> params
=> #<ActionController::Parameters {"authenticity_token"=>"x", "post"=>#<ActionController::Parameters {"title"=>"cc", "content"=>"cc"} permitted: false>, "commit"=>"create", "controller"=>"posts", "action"=>"create"} permitted: false>

ただこのままでは見づらいので、以下のように JSON.parse(params.to_json) を実行すると多少見やすくなっておすすめです。

[9] pry(#<PostsController>)> JSON.parse(params.to_json)
=> {"authenticity_token"=>"x",
 "post"=>{"title"=>"aa", "content"=>"bb"},
 "commit"=>"create",
 "controller"=>"posts",
 "action"=>"create"}

「このインスタンスはどのクラスなのか?」の確認

プログラムが複雑になてくると、変数にどんな値が入っているのかが把握しづらい状況が多々起きます。

そんなときに手がかりとしてクラスが知りたいことが多いと思います。

たとえば、以下のように4行目で停止させているとき

    2: def self.search(keyword)
    3:   binding.pry
 => 4:   Post.where('content like ?', "%#{keyword}%")
    5: end

引数の keyword は何クラスかを確認したければ


[1] pry(Post)> keyword.class
=> String

のように class メソッドを実行すれば確認できます。

この場合は String クラス(つまり文字列)であることが確認できました。

インスタンスが持っているメソッドの確認

ある変数に入っているインスタンスに対して、

「名前はなんとなくしか覚えてないけどなんていうメソッドだっけな〜」

ということはよくあります。

そんなときは methods 当メソッドを使ってさらに、grep メソッドで絞り込むと見つけやすくなります。

たとえば、以下の様な状態で

    14: def create
    15:   @post = Post.new(post_params)
    16:   binding.pry
 => 17:   if @post.save
    18:     flash[:success] = 'Success!'
    19:     redirect_to @post
    20:   else
    21:     flash.now[:danger] = 'Failed to create post!'
    22:     render :new
    23:   end
    24: end

[1] pry(#<PostsController>)>

@post 変数に入っている Post クラスのインスタンスに対して

「json にするメソッドはなんだっけ????」

と思ったとします。

その場合は

[3] pry(#<PostsController>)> @post.methods.grep(/json/)
=> [:include_root_in_json, :include_root_in_json?, :from_json, :as_json, :to_json]

のように methods でメソッド一覧を返し、正規表現で json を含むものに絞り込むことで当たりをつけることができます。(この場合はお目当てのメソッドは to_json です)

バリデーションエラーの確認

データベースにうまく保存できないとき、バリデーションエラーを確認するのが定石です。

たとえば、

    14: def create
    15:   @post = Post.new(post_params)
    16:   binding.pry
    17:   if @post.save
    18:     flash[:success] = 'Success!'
    19:     redirect_to @post
    20:   else
 => 21:     flash.now[:danger] = 'Failed to create post!'
    22:     render :new
    23:   end
    24: end

この状態では、17行目で @post に入っているActiveRecordのインスタンスに対して save を実行すると false が返り(つまりデータベースへの保存に失敗)、else 節にいっています。

この状態でなぜ保存できなかったのかは ActiveRecord の errors メソッドを呼び出すと確認できます。

[3] pry(#<PostsController>)> @post.errors
=> #<ActiveModel::Errors:0x00007f812e4eafa0
 @base=#<Post:0x00007f812e0532f8 id: nil, content: "", created_at: nil, updated_at: nil, title: "">,
 @errors=[#<ActiveModel::Error attribute=title, type=blank, options={}>]>

attribute=title, type=blank ということで title カラムが空だから失敗したんだということが分かります。

もう少しわかりやすく表示するためには、さらに full_messages メソッドを呼び出すと

[4] pry(#<PostsController>)> @post.errors.full_messages
=> ["Title can't be blank"]

と、このように Title can’t be blank (タイトルは空にできません)、つまり空だから保存できなかったのだということが分かります。

条件付きブレークポイント

ループのなかで特定の条件のときだけ不具合がおきる、ということがあります。

たとえば、

<% @posts.each do |post| %>
  <div><%= post.title %></div>
  <div><%= post.content %></div>
<% end %>

のようにループで表示しているときにタイトルが空のときだけなにかおかしい、というような状況です。

このような場合に例えば

<% @posts.each do |post| %>
  <% binding.pry %> ← ここに追加
  <div><%= post.title %></div>
  <div><%= post.content %></div>
<% end %>

のようにループ内に binding.pry を設置すると、目的のデータにたどり着くまで何度も何度も continue を実行することになり効率的ではありません。

そんなときは if 文を使って

<% @posts.each do |post| %>
  <% binding.pry if post.title.blank? %> ← if 文を追加した
  <div><%= post.title %></div>
  <div><%= post.content %></div>
<% end %>

とすると、条件が true のときだけ binding.pry が実行されるので、目的のデータのときだけ停止して便利です。

これから呼び出そうとしているメソッドのコードを確認したい

ブレークポイントで止まっているときに、これから呼び出そうとしているコードの中身をステップを進めずに確認したいことがあります。

例えば以下の様なコードでブレークポイントを設置しているとします。

  def show
    @post = Post.find(params[:id])

    binding.pry

    @comments = @post.public_comments
  end

実行すると以下の状態で入力待ちになります。

     6: def show
     7:   @post = Post.find(params[:id])
     8:
     9:   binding.pry
    10:
 => 11:   @comments = @post.public_comments
    12: end

[1] pry(#<PostsController>)>

この状態でステップを進めたくはないけど、public_comments メソッドが何者なのかを確認したい。
そんな時は $ を使ってソースコードを確認できます。

[1] pry(#<PostsController>)> $ @post.public_comments

From: 
Owner: Post
Visibility: public
Signature: public_comments()
Number of lines: 3

def public_comments
  comments.select { |c| c.public }
end

デバッグは進めてしまうと戻すことは難しいので、進めずにコードを確認できて便利です。

クラスのソースコードを確認したい

クラスのソースコードを確認することもできます。
やりかたは $ の引数にクラスをしていするだけ。

[2] pry(#<PostsController>)> $ Post

From: 
Class name: Post
Number of monkeypatches: 8. Use the `-a` option to display all available monkeypatches
Number of lines: 7

class Post < ApplicationRecord
  has_many :comments

  def public_comments
    comments.select { |c| c.public }
  end
end

クラス#メソッドのソースコードを確認したい

クラス名とメソッド名が分かっていてそのソースコードを確認したい場合は $ の引数にクラス名#メソッド名とすると確認できます。

[4] pry(#<PostsController>)> $ Post#public_comments

From: 
Owner: Post
Visibility: public
Signature: public_comments()
Number of lines: 3

def public_comments
  comments.select { |c| c.public }
end

後片付け:デバッグが終わったら binding.pry を消す

binding.pry はデバッグように入れたコードなので、デバッグが終わったらきっちり消しましょう。

binding.pry が残ったままだと、例えば以下のような状況となり迷惑がかかります。

  • 同じチームのメンバーが実行したときに意図せずそこで止まって驚く
  • 本番環境にデプロイされてしまうと pry-byebug がインストールされていないためにエラーとなる

しっかりと確認し、全て消すようにしましょう。

まとめ

Ruby on Rails でデバッグ手法として pry-byebug を紹介し、使い方やTIPSを解説しました。

ソフトウェア開発においてデバッグは避けては通れません。

本記事を通して効率よくデバッグできるようになりましょう!

タイトルとURLをコピーしました