Sunday, September 18, 2011

ruby debugでActiveRecordをデバッグ

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は少し面倒な部分があるので、次回時間があったら説明する。

Saturday, September 10, 2011

OS X AppleScriptでスクリンロック

出かけ用のノートをマックにしてから色々便利ではあるが、やっぱりショートカットの足りなさにはどうしようもない。例えば、スクリンロックとか。

で、Google神に祈ってみた。

Lock Screen?

神様に感謝。

要はAppleScriptでスクリンロックできるってこと。ってか、ScreenSaverEngineを実行させるスクリプトを書けばいいってことかな。その中でもkaiという人が書いた方法が一番シンプルでしっかりと動いてくれた。

Note that, if the user attempts to dismiss the screen saver immediately after the activate command has been sent, a Process Manager error number -609 [connectionInvalid] may occur: "ScreenSaverEngine got an error: Connection is invalid."

This can usually be avoided by using the launch command, instead:
launch application "ScreenSaverEngine"
けど、このまま入れるとScreenSaverEngineを参照できないというエラーが起きるので、絶対パスを指定する。

launch application /System/Library/Frameworks/Screensaver.framework/Versions/A/Resources/ScreenSaverEngine.app
これを名前を付けてアプリケーションとして保存。勝手にソースを開かれるのを防ぐにはバイナリにしてもいいだろう。後はSpotlightで実行可能だ。自分は重複してもしょうがないと思いつつ、Lockという名前にしちゃった。だってすぐ忘れちゃうしな。

別の方法としてはサービスとして登録する方法があった。けど、こちらは格アプリケーションと相性が悪いのか、ショートカットが効かなかったりするのでパス。