rails3からはruby-debugをサポートとしているが、場合によってはruby-debugを使えなかったりもする。個人環境であれば全く問題ないはずだろうが、たまにはデプロイサーバなどでデバックを行わざるを得ない。デプロイサーバなどで新しいライブラリやツールを導入するのはちょっと面倒なので、できれば標準ライブラリを使った方がスムーズに行けるだろう。そのため、今回は標準ライブラリdebugを使ったrailsデバッグに手をかけてみることにした。
※railsとruby-debugの公式文書は
こちらに詳しく説明されている。
※
日本語文書もそこそこポスティングされているようだ。
大まかに言うと標準ライブラリのdebugとruby-debugはほぼ同じ(かも)。ただ、railsの起動そのものとは違うので、必要な環境設定ファイルなどを手動でロードさせるしかない。
まず、テスト環境を作る。
$ rails new debug -d sqlite3
$ cd debug/
$ rake db:create
$ rails g model Blank
$ rake db:migrate
新しいモデルまで作成できたら、問題を起こすコードを入れる。
# app/models/blank.rb
1: class Blank < ActiveRecord::Base
2: class << self
3: def get_all
4: self.all
5: []
6: end
7: end
8: end
ふざけたコードだが、よいとしよう。ごく単純なコードだが、行数が数十行程度だとしても分かりづらかったりする。ここでは、debugに慣れることだけ考えよう。
このコードで知りたいのは、get_allからなぜ空っぽの配列ばかりが返ってくるのかだ。ちなみにblank.rbそのものはBlankクラスとBlank::get_allを定義しているたけなので、これらを実行するコードも用意しておく。場所はどこでもよい。
# ../blank_get_all.rb
Blank.get_all
これで準備はOK。いよいよデバッグを開始する。
$ ruby -rdebug ../blank_get_all.rb
するとデバッグプロンプトが出てくる。基本的な操作方法はhまたはhelpで出せるので、知りたいコマンドがあればその都度調べておこう。
まず、正常にデバッガが立ち上がったのか確認して見る。
rdb:1) l
[-4, 5] in ../blank_get_all.rb
=> 1 Blank.get_all
よし、行けた。lはlistの略であり、表示したいラインの前後のコードを表示する。オプションとしてライン番号を指定できる。省略した場合は、現在のポイントが指しているラインを表示してくれる。
Blank.get_allが空っぽの配列を返すのは既に分かっていることだ。これだけじゃ、問題解決はできなさそうなので、もう少し詳しく調べてみよう。ただし、このままではBlankクラスが何なのかをデバッガは全く知らない。pコマンドでBlankを調べてみると、
(rdb:1) p Blank
......lib/ruby/1.9.1/debug.rb:130:in `eval':uninitialized constant Object::Blank
from .../lib/ruby/1.9.1/debug.rb:130:in `rescue in debug_eval'
from .../lib/ruby/1.9.1/debug.rb:127:in `debug_eval'
from .../lib/ruby/1.9.1/debug.rb:492:in `block in debug_command'
from .../lib/ruby/1.9.1/debug.rb:240:in `catch'
from .../lib/ruby/1.9.1/debug.rb:240:in `debug_command'
from .../lib/ruby/1.9.1/debug.rb:691:in `trace_func'
from .../lib/ruby/1.9.1/debug.rb:905:in `block in '
from ../blank_get_all.rb:1:in `'
と怒られる。これはrails環境とBlankクラスがまだロードされていないせいであろう。だけど、少し面倒なだけで手動でロードしてしまえば全く問題ない。
(rdb:1) require './config/environment'
(rdb:1) p Rails.env
"development"
大体の場合はconfig/environmentを読み込むだけで済むはずだが、カスタマイズした環境設定ファイルがあった場合も同じように読み込めばいい。Rails.envがdevelopmentを返しているが、これもデバッガ内で切り替えられるので、気にしなくていい。
次はblank.rbを読み込む。
(rdb:1) p ActiveRecord
ActiveRecord
(rdb:1) require './app/models/blank.rb'
false # 既にロードされているから
これでやっと準備が揃った。stepでBlank.get_allの中身を見てみよう。
(rdb:1) s
.../debug/app/models/blank.rb:5: self.all
(rdb:1) l
[0, 9] in .../debug/app/models/blank.rb
1 class Blank << ActiveRecord::Base
2
3 class << self
4 def get_all
=> 5 self.all
6
7 []
8 end
9 end
ここでもう一度stepコマンドで進んでみよう。(ActiveRecordの中身など見たくない場合は、nコマンドで次のラインに移動する)
(rdb:1) s
.../gems/activerecord-3.1.0/lib/active_record/base.rb:441: delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
(rdb:1) l
[436, 445] in .../gems/activerecord-3.1.0/lib/active_record/base.rb
436
437 class_attribute :_attr_readonly, :instance_writer => false
438 self._attr_readonly = []
439
440 class << self # Class methods
=> 441 delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
442 delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
443 delegate :find_each, :find_in_batches, :to => :scoped
444 delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
445 delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
(rdb:1) f
--> #1 /Users/lateau/.rvm/gems/ruby-1.9.2-head/gems/activerecord-3.1.0/lib/active_record/base.rb:441:in `all'
#2 /Users/lateau/dev/testroom/ruby/debug/debug/app/models/blank.rb:5:in `get_all'
#3 ../bundle_get_all.rb:1
フレーム3から1に移動しているのが分かる。多分、この先も何回もフレームを移動することになるだろう。ActiveRecordがどのような働きをしているかが知りたいなら、stepコマンドで1行ずつ確認してもよいだろうが、今回の目標ではない。ただstepでActiveRecordの中身をデバッグ中に確認できることだけ覚えておこう(octopusなどのdbmを調べる時に役立つ)。
(rdb:1) b ./app/models/blank.rb:5
Set breakpoint 1 at ./app/models/blank.rb:5
(rdb:1) b
Breakpoints:
1 ./app/models/blank.rb:5
ブレークポイントをかけた。これでblank.rbの5行目で必ず止まるはず。1行ずつ行くのはどうも面倒なので、continueコマンドを使う。5行目で止まったらnコマンドで次の行に移動する。
(rdb:1) n
.../debug/app/models/blank.rb:7: []
「7行目を実行すると5行目の戻り値とは関係なく[]が返ってくる」というのが分かる。これでBlank::get_allが何故空っぽの配列ばかりを返すのかが分かった。分かったら、該当コードを直し、問題を解決する。
ここまで大雑把なdebugの使い方を説明した。説明できなかった部分もあるが、一通りのデバッグはこれで充分であろう。ちなみに、ActiveSupportはActiveRecordとほぼ同じ方法でデバッグできる。ActionContollerは少し面倒な部分があるので、次回時間があったら説明する。