MySQL 5.5でのスローログ

Homebrewで入れたMySQL 5.5.10のスローログの設定。

$ sudo vim /etc/my.cnf

[mysqld]
(いろいろ)
slow_query_log=1
long_query_time=0.02
slow_query_log_file=slow.log

$ mysql.server restart
$ tail -f /usr/local/var/mysql/slow.log

Rack::Profilerを使う

Rack::Profilerを使うのに少し躓いたのでメモ。

Gemfile:

gem "ruby-prof"
gem "rack-contrib", :require => "rack/contrib"

app.rbのconfigureブロックあたり:

require "rack/contrib/profiler"
Rack::RubyProf = RubyProf
use Rack::Profiler

Rack::RubyProf = RubyProfをしているのはエラーが出るから。

アクセスする際にURL末尾に

?profile=process_time

を付けるとプロファイル結果がダウンロードできる。

あとはKCacheGrindとかQCacheGrindで解析する、らしい。 (qtのビルドで時間がかかってるのでまだできていない…)

参考


Node.js(V8)は4バイトのUTF-8に未対応

現状、Node.js(V8)は4バイトのUTF-8に対応していない。

$ node
> code = 0x1F614
128532
> char = String.fromCharCode(code)
''
> char.charCodeAt(0).toString(16)
'f614'

先頭の1が削られてしまっている。

ちなみにV8コミッタの@koichikさんから直接リプライをもらった


RestClientの返り値はStringとちょっと違う

インターフェースが簡潔で使いやすいRestClientだけれども、各種HTTPメソッドの返り値をto_iするとステータスコードを返すみたいだ。

たとえば次のようなSinatraアプリが起動していたとする。

get '/' do
  "1"
end

このgetルーティングに対してRestClientでアクセスすると、次のような結果が得られる。

require "rest_client"

res = RestClient.get("http://localhost:9292/")
#=> "1"

# Stringなのにto_iはステータスコード200を返す
res.class #=> String
res.to_i  #=> 200

# to_strしても変わらない
str = res.to_str  #=> "1"
str.to_i          #=> 200
res.class         #=> String

# 期待される値はと言うと
"1".to_i  #=> 1

内部でどうやっているかまでは確認できていないけど、 このようにRestClientの返り値のto_iはステータスコードを返す。

で、具体的にどんなときに不具合があるかというと、

  • Web APIからRestClientを使って取得した値をActiveRecord::Base.findに渡す
    • 内部的にto_iしているのか、毎回id=200を探しに行く
  • json gemによるエスケープ処理JSON.generate([val])が無限ループしてしまう

ではどうやっていつものStringとして扱うのかというと、Stringで包みなおせばいい。

res = RestClient("http://localhost:9292")
str = String.new(res)
str.to_i              #=> 1
str.class             #=> String

なんでこんな変な仕様になってるんだろう。 せめて返り値には別のクラスを使用して欲しかった。

そんな感じで、RestClientは変なところではまるので要注意。


LionでPostfix+Gmail

OSX LionにはPostfixがインストールされているんだけど、これを使ってgmail経由で送信する際に一筋縄では行かなかったのでメモ。

まず /etc/postfix/main.cf に以下を追記して、gmailの587番ポートに接続するよう設定する。

relayhost = [smtp.gmail.com]:587
smtp_use_tls = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_tls_security_options = noanonymous
smtp_sasl_mechanism_filter = plain
smtp_tls_security_level = secure
smtp_tls_CApath = /etc/postfix/certs

次に /etc/postfix/sasl_passwd にgmailアカウントの情報を記述する(YOUR.ACCOUNTとYOUR_PASSWORD部分は自分のものに書き換える)。

[smtp.gmail.com]:587 YOUR.ACCOUNT@gmail.com:YOUR_PASSWORD

そしてpostmapでデータベースを作成し、元のファイルは削除する。

# postmap /etc/postfix/sasl_passwd
# rm /etc/postfix/sasl_passwd

次に、GmailのSSL証明書を取得する。下記がコマンドの出力結果(+注釈)だけど、 注釈の通り証明書の鍵部分を /etc/postfix/certs/gmail.pem と /etc/postfix/certs/equifax.pem に保存する。

$ openssl s_client -connect pop.gmail.com:995 -showcerts
CONNECTED(00000003)
depth=1 /C=US/O=Google Inc/CN=Google Internet Authority
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=pop.gmail.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority
### ここから
-----BEGIN CERTIFICATE-----
MIIDWjCCAsOgAwIBAgIKaNGwUQADAAAirzANBgkqhkiG9w0BAQUFADBGMQswCQYD
VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu
dGVybmV0IEF1dGhvcml0eTAeFw0xMTAyMTYwNDQwMzdaFw0xMjAyMTYwNDUwMzda
MGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw1wb3Au
Z21haWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc8H+gCR0/95Tb
Lkj7jdj0oXmfGzsSswdSjo6yWdcHpjv6cbaakHakclxBs47M8Gs3fJxuNqVMpt2Q
YorJuoBuVZjM59/rIeJwmOwSlzf1POxpEFUvm1ASAeBnBheBt0Fv+BCfI8DjZKgn
SvHCqQJfNgxpJBXOLbFVjBsPqF8w6wIDAQABo4IBLDCCASgwHQYDVR0OBBYEFHzY
GJFnJ+gmONoIX6cyTAEl9ePYMB8GA1UdIwQYMBaAFL/AMOv1QxE+Z7qekfv8atrj
axIkMFsGA1UdHwRUMFIwUKBOoEyGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29v
Z2xlSW50ZXJuZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3Js
MGYGCCsGAQUFBwEBBFowWDBWBggrBgEFBQcwAoZKaHR0cDovL3d3dy5nc3RhdGlj
LmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJbnRlcm5ldEF1dGhv
cml0eS5jcnQwIQYJKwYBBAGCNxQCBBQeEgBXAGUAYgBTAGUAcgB2AGUAcjANBgkq
hkiG9w0BAQUFAAOBgQB0GTFAoMNxCFJ3aVMzvrZgCD9hG2Ee3Sx5GMQ0yDjcFvzS
ZIci9Gr18HMVBusIuFLw8TvBD7PEXtZLhh6hbj+Xdg9CIqlIjdqJFRixJU06xqKH
akIhNBDxy5ky3VugqlGdysbMjo/aXCFdl42GproXdUPH24Erljur885LklMk/g==
-----END CERTIFICATE-----
### ここまでを /etc/postfix/certs/gmail.pem に保存
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
### ここから
-----BEGIN CERTIFICATE-----
MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3
WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ
R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf
NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb
qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB
oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk
MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB
Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v
Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde
BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN
0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml
UUIuOss4jHg7y/j7lYe8vJD5UDI=
-----END CERTIFICATE-----
### ここまでを /etc/postfix/certs/equifax.pem に保存
---
Server certificate
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=pop.gmail.com
issuer=/C=US/O=Google Inc/CN=Google Internet Authority
---
No client certificate CA names sent
---
SSL handshake has read 1714 bytes and written 316 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-SHA
Server public key is 1024 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-SHA
    Session-ID: 763AA8A1EF5D5DDDF4C2B21BFBFF07D9F823908254EB257FA06E9B90A64F7199
    Session-ID-ctx: 
    Master-Key: 903CDFFC9EE4A6ACDC636BE72F8A82AFE7E088DA74918789653DC25978D4123F9333E2C6DE8F42A913C9A9CFE66857A3
    Key-Arg   : None
    Start Time: 1323177726
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
+OK Gpop ready for requests from 113.197.147.115 o7pf55469179pbh.0

c_rehashでエイリアスを作成。

# c_rehash /etc/postfix/certs

次に /etc/postfix/submit.cred を作成する。

# vim /etc/postfix/submit.cred

submitcred version 1
hostname|username|password

# chmod 600 /etc/postfix/submit.cred

以上で設定は終わったので、launchctlに登録する。

# vim /System/Library/LaunchDaemons/org.postfix.master.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>org.postfix.master</string>
  <key>OnDemand</key>
  <false/>
  <key>Program</key>
  <string>/usr/libexec/postfix/master</string>
  <key>ProgramArguments</key>
  <array>
    <string>master</string>
  </array>
  <key>QueueDirectories</key>
  <array>
    <string>/var/spool/postfix/maildrop</string>
  </array>
  <key>KeepAlive</key>
  <true/>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>

# launchctl unload -w /System/Library/LaunchDaemons/org.postfix.master.plist
# launchctl load -w /System/Library/LaunchDaemons/org.postfix.master.plist

これでメールが送信できるようになったはずなので、mailコマンドで送信してみる。

$ date | mail -s "Hello" "test.to@example.com"

ログは /var/logs/mail.log に吐かれる。

$ tail -f /var/logs/mail.log

キューはmailqで確認。

$ mailq

キューに溜まっているメールが削除したければpostsuper -dを実行。

# postsuper -d ALL

certificate verification failed が出たら

上記のようにSSLの設定をしても、次のようなエラーが出るかもしれない(実際自分は出た)。

certificate verification failed for smtp.gmail.com[74.125.53.109]:587: untrusted issuer /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
1DD8918C646A: Server certificate not trusted
certificate verification failed for smtp.gmail.com[74.125.53.108]:587: untrusted issuer /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
1DD8918C646A: to=<test.to@example.com>, relay=smtp.gmail.com[74.125.53.108]:587, delay=380, delays=363/0.07/17/0, dsn=4.7.5, status=deferred (Server certificate not trusted)

その場合は Fixing Postfix “certificate verification failed for gmail untrusted issuer” Error Message に書かれているEquifaxとThawteの2つの証明書をコピーしてくる。

# vim /etc/postfix/certs/equifax.pem

-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
70+sB3c4
-----END CERTIFICATE-----

# vim /etc/postfix/certs/thawte.pem

-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh
Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/
qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t
UCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----

もう一度c_rehashでエイリアスを作成してpostfixを再起動する。

# c_rehash /etc/postfix/certs
# launchctl unload -w /System/Library/LaunchDaemons/org.postfix.master.plist
# launchctl load -w /System/Library/LaunchDaemons/org.postfix.master.plist

equifax.pemは前述の通り最新の情報で作成したけど、なぜか内容が異なっている。でもこれで動いている。Thawteはもう使えなくなったというから使われないはずだし…。ちょっとよくわかっていない。

参考


git reset直前のコミットはORIG_HEADに保存されてる

間違えてgit reset(–soft/–hard問わず)してしまった場合とか、取り消すのはいいんだけどコミットログは見たいとか思ったら、

git show ORIG_HEAD

すれば良い。

ORIG_HEADはgit resetする直前のHEADの状態を指しているので、git diffで差分を見るといったこともできる。

git diff ORIG_HEAD

参考


ActiveRecord3.1/mysql2でTRUNCATE

DatabaseCleanerを使うと 複数データベース(各種RDBMSやMongoDB、CouchDBなどなど)に対応した方法でデータを削除できるんだけど、複数のDBにコネクションをはっている場合にはうまく動いてくれないことがあるらしい

でも自分の場合、mysql2でうまく動いてくれればそれでいいし、わざわざDatabaseCleanerを使う必要はないよなぁ…。ということで、ActiveRecord::ConnectionAdapters::Mysql2AdapterにTRUNCATEを発行してくれるメソッドを生やしてみた。実質DatabaseCleanerのコピペ。

module ActiveRecord
  module ConnectionAdapters
    class Mysql2Adapter < AbstractAdapter
      def truncate_tables
        tables_to_truncate.each do |table_name|
          truncate_table(table_name)
        end
      end
      def truncate_table(table_name)
        execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
      end
      private
        def tables_to_truncate
          tables - views - ["schema_migrations"]
        end
        def views
          @views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
        end
    end
  end
end

次のようにtruncate_tablesを呼ぶと、ビューとschema_migrations以外に対してTRUNCATEを発行してくれる。

ActiveRecord::Base.connection.truncate_tables

ActiveRecord3.1/MySQLでBIGINT UNSIGNEDなid

ActiveRecord 3.1でMySQL2アダプタを使っている場合に、idカラム(プライマリキーのカラム)にBIGINT UNSIGNEDを使う方法。

まずCREATE TABLE時のidカラムの型を変更するために、ActiveRecordが読み込まれた直後、rakeタスク等でテーブルの作成が行われる前に次のように上書きする。

ActiveRecord::ConnectionAdapters::Mysql2Adapter::NATIVE_DATABASE_TYPES[:primary_key] = \
  "BIGINT UNSIGNED DEFAULT NULL auto_increment PRIMARY KEY"

あとは各マイグレーションファイルで、外部キー(*_idってやつ)のカラムの型にBIGINT UNSIGNEDを指定する。

t.column :user_id, "BIGINT UNSIGNED"

これでidカラムも*_idカラムもBIGINT UNSIGNEDで作られる。

もし既存のテーブルの型を書き換えたい場合には、

change_column :users, :id, "BIGINT UNSIGNED"

でいけると思う(未確認)。

ただ、BIGINT UNSIGNEDだと0から1846京まで使えるらしいので、DB単体では破綻するような気がする。

参考


Object#presenceが便利

ActiveSupport 2.3で導入されたObject#presenceが便利。

たとえば、

time = options[:timestamp].presence || Time.now

で初期値の代入に使えるし、

time = options[:timestamp].presence && Time.at(options[:timestamp])

で値がある場合に限って加工に使える。

組み合わせれば、

time = (options[:timestamp].presence && Time.at(options[:timestamp])) || Time.now

で値があれば加工して、なければ初期値を代入することができる。

でもこれはさすがにわかりづらいので、三項演算子を使って

time = options[:timestamp].present? ? Time.at(options[:timestamp]) : Time.now

としたほうがカッコが減ってわかりやすい。

ActiveSupportがないと生きていけないゆとりになってしまった…。


sinatraでbefore :onlyみたいなこと

SinatraのbeforeフィルタだとRailsみたいに:onlyや:exceptで適用するルーティングを指定できない。 ただそれだとbeforeフィルタのなかでガリガリとifで分岐する必要があるのでそれもちょっと微妙。

で、A Sinatra Before Only Filterにbefore :onlyについて処理するプラグイン(?)を書いている人がいたので、ちょっと真似してみた。

module Sinatra
  module BeforeOnlyFilter
    def before_only(*routes, &block)
      before do
        routes_regex = routes.map do |route|
                          route.is_a?(Regexp) ? route : /^#{route.gsub(/\*/, '[^/]*')}$/
                        end
        instance_eval(&block) if routes_regex.any? {|regex| (request.path =~ regex) != nil}
      end
    end
  end
  register Sinatra::BeforeOnlyFilter
end

使うときはこんな感じ

require 'sinatra/before_only_filter'

before_only %r{/users/.+} do
  # /users/aaa とか
  # /users/aaa/bbb とか
  # /users/aaa.ccc とか
end

before_only "/users/*" do
  # /users/aaa はマッチするけど
  # /users/aaa/bbb や
  # /users/aaa.ccc はマッチしない
end

でも、ちょっと不便なところが。

  • HTTPメソッドでの区別ができない
  • URLからパラメータを取り出すには自分でパースする必要がある

というか、sinatraのソースを見るとそもそもbeforeメソッドの引数でパスが指定できる…?

def before(path = nil, options = {}, &block)
  add_filter(:before, path, options, &block)
end

というわけでちょっとsinatraのソースを追ってみた。

ここの pathはどうやら正規表現が使えるっぽい。 で、add_filterされたあとはどうなるかというと、

def add_filter(type, path = nil, options = {}, &block)
  path, options = //, path if path.respond_to?(:each_pair)
  filters[type] << compile!(type, path || //, block, options)
end

compile!の引数から察するにpathは正規表現が使えるらしい。さてそのcompile!はというと、

def compile!(verb, path, block, options = {})
  options.each_pair { |option, args| send(option, *args) }
  method_name             = "#{verb} #{path}"
  unbound_method          = generate_method(method_name, &block)
  pattern, keys           = compile path
  conditions, @conditions = @conditions, []

  [ pattern, keys, conditions, block.arity != 0 ?
      proc { |a,p| unbound_method.bind(a).call(*p) } :
      proc { |a,p| unbound_method.bind(a).call } ]
end

なんか難しい…。 いまverbはadd_filterのtype、つまり:beforeがセットされているから、ブロックを処理する”before //”みたいなインスタンスメソッドがつくられているんだろう。

ここで呼ばれているパスを正規表現に返還するためのcompileメソッドはbeforeやafter以外のget/post/put/deleteといったルートコンテキスト(?)でも使われているみたいだから、実はbeforeでも/users/:idみたいなものが指定できてparamsから取得できちゃったりするっかも…? でもやっぱりHTTPメソッドでの切り分けは対応していなさそうだ。

ちょっと力尽きたので、次回試してみよう。

ちなみに呼び出しはdispatch!、さらにその中のfilter!で行われる。 見た感じbase.filters[:before]で複数フィルタの処理がされてるようなので、beforeは何度呼び出しても大丈夫。before_onlyも同様。

def dispatch!
  static! if settings.static? && (request.get? || request.head?)
  filter! :before
  route!
rescue ::Exception => boom
  handle_exception!(boom)
ensure
  filter! :after unless env['sinatra.static_file']
end

def filter!(type, base = settings)
  filter! type, base.superclass if base.superclass.respond_to?(:filters)
  base.filters[type].each { |args| process_route(*args) }
end

追記 2011-11-15

今年3月に出た1.2.0の時点でbefore/afterのパターンマッチ機能はサポートされてたみたい。(@komagata さん、ありがとうございます!)

というかREADMEにサンプルとして載ってる…何度も見たのに気づかなかった…。