Rails Metal と Controller のセッション共有 ― 2010/02/26
ここが詳しい。
Controller クラスのインスタンスメソッド session で参照できる Hash と同じものが、 Metal クラスでは env['rack.session'] で参照できる。
(env は call の引数)
Rails Metal の URIマッピング ― 2010/02/26
発端
passenger + Rack 環境にアプリを置こうと思ったのだけど、 いろいろあって、「ActiveRecord があった方が良くね?」という意見が発生。
「それだったら、Rails Metal で良くね?」という結論が、脳内会議で決議された訳だけど、そこで悩んだのが、URIマッピングのルールが良く判らない、という点。
ソースはあんまり見てない(特にroutes関係)なので、あまり信用しない方が良いけど、何かの役に立つかも知れないので、晒しておく。
テンプレート
$ ./script/generate metal foobar
の結果作成される app/metal/foobar.rb のテンプレートは
# Allow the metal piece to run in isolation require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) class Foobar def self.call(env) if env["PATH_INFO"] =~ /^\/foobar/ [200, {"Content-Type" => "text/html"}, ["Hello, World!"]] else [404, {"Content-Type" => "text/html"}, ["Not Found"]] end end end
こんな感じになる。 これは(require行とその作用を除けば)、非常に単純なRackアプリケーションそのものなので、 詳細は省略する。
これで、
$ ./script/server & $ curl http://localhost:3000/foobar Hello, World!
foobar 以下のURIがこの metal で処理されていることが判る。
routes.rb ?
Rails アプリケーションのURI情報の設定なら、config/routes.rb が正道だ、と思ったが、 どうも routes.rb には metal 関係のキーワードは定義されていない。
ルーティング情報はどこに?
となると、怪しいのは
if env["PATH_INFO"] =~ /^\/foobar/
この if文だという訳で、regexpを書き換えたりしてみたところ、どうもこいつがルーティング情報らしいことが判明。
# Allow the metal piece to run in isolation require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) class Foobar def self.call(env) if env["PATH_INFO"] =~ /^\/hogehoge/ [200, {"Content-Type" => "text/html"}, ["Hello, World!"]] else [404, {"Content-Type" => "text/html"}, ["Not Found"]] end end end
だと
$ curl http://localhost:3000/foobar (略) Routing Error (略) $ curl http://localhost:3000/hogehoge Hello, World!
になる。
まとめ
- metal は routes.rb に書いた規則よりも前に実行される。
- metal は全てのURIに対して実行される。
- metal が、404のレスポンスを返した場合は、他の metal(あれば)、routes.rb の規則の適用の順にリクエストが取り回される。
- 複数の metal が存在する場合の実行順は未定義(ファイルのiノード順っぽい)
ということみたいだ。
演繹できる事実として、同名の metal と controller が居た場合は metal が勝つのと、 controller に落ちるまでには metal が全て実行されるので、極端に metal のクラスが多い場合は要注意かも知れない。
あと、passenger と共に使用する場合は、metal の中で、request.body() とかやってしまった後に、404で次の metal や controller に処理を 回すと大変な事になるような気がする。
(passenger の Rackインターフェイスに入ってくる request.body()はrewindableなオブジェクトではないため)
追記
長々と書いた後で、config/config.ru に rackup用のconfigが書けることに気がついた。
ここなら、 マッピングルールを自在に書ける。
Heroku Garden is no longer supported ― 2009/08/28
Herokugarden から、近日中にサービスを終了するので、後継サービスのHerokuへ移行するようにとのメールが来た。
Herokugarden は無料のRailsホスティングサービスの一つ。
Web上でRailsプロジェクトの作成、ファイルの編集、DBのマイグレーション等を完結できる、どちらかと言うとネタ系に近いサービスだった。(javascriptによるエディタが多少残念な感じだったので、テストプロジェクトを作成したまま放置状態だった)
対するHeroku は、Web上での編集機能はオミットされ、純粋なRailsホスティングサービスになったようだ。
5MBまでのストレージと、1サーバプロセスが無料。それぞれ追加は有料。
フルオプションだと、ストレージ2TB、24サーバプロセスで、月$2428。
4サーバプロセスで、大体普通(って何?)のCPU1つ分らしい、これに50MBストレージで月$123。 サービスの品質次第ではアリかなあ。
後で評価する。
ActionMailer でテンプレートを指定する ― 2009/08/11
ActionMailer のRDocでは何故か解説が無いようですが
Model側で
class FooMailer < ActionMailer::Base def bar from 'foobar@example.com' recipients 'foobar@example.com' template 'hoge' body :mes1 => 'hogehoge', :mes2 => 'foobar' end end FooMailer.create_bar # foo_mailer/bar.erb ではなく foo_mailer/hoge.erbが使用される
と、templateメソッドを利用すると、当該のメールがcreate!される際のテンプレートが明示的に指定できます。 引数は、APP_ROOT/app/views/foo_mailer からの相対パスです。 拡張子は、例のルールで補完されます。
テンプレートをファイルではなく String で与えたい場合に適切なメソッドは無いのですが、 ActionMailer::Base#create! は実行された際、@body が 既にStringのインスタンスならレンダリング処理はスキップして @body の内容をそのままメール本文とするようです。
class FooMailer < ActionMailer::Base def piyo from 'foobar@example.com' recipients 'foobar@example.com' body 'piyo piyo' end end FooMailer.create_piyo.body #=> 'piyo piyo'
あとは、自前でerbのコンパイルをするか、 ActionController::Base#render に、:inlineを与えたときのようなメソッドを実装すればいけるはず。 (ActionMailer::Base#render はファイル名しか取れない)
とりあえず、今日はここまで
Rails と Thread と autorequire ― 2009/05/28
忘備録として
Railsというか、autorequire が効いている環境で、Threadを使う場合には注意が必要だ。
模擬コード
def send_many_mail(recipients) recipuents.each_slice(1_000) do |recipients_sub| Thread.new do recipients_sub.each do |recipient| TestMailer.deliver_test_mail(recipient) end end end end
意図としては、大量にメールを送信したいという要件があって、それらを多重でなんか頑張ってみようというもの。
コードを簡単にするために、受け取り人の数で each_sliceしているけど、実際にはスレッド数を予め決めておいて、その数に受け取りメールアドレスを分割すべき。
また、これらのスレッドがちゃんと天寿を全うするには(全てのメール送信が完了するまで生き続けるためには)、これらスレッドにjoinするか、素でこれらのスレッドより長いライフタイムを持つプロセスの下で生成される必要がある。
要は、単純に controller のメソッド内で、Thread.new しても、大抵の場合は上手くいかないという話し。
小さな不特定多数のプロセスをspinするのが目的なら、BackgrounDrbが便利だし、今回のようにバッチっぽい処理をブラウザからキックしたい場合は、予め 自前でdrb のプロセスを立てておくのが良いと思う。
このあたりは機会があったら別のエントリを書くかも。
問題点
で、先程のコードは、上手く動くかもしれないし、動かないかもしれない。
実際、大抵の場合は上手くいくのだけれど、プロセスを(再)起動した直後に、メール送信に失敗することがある。
何が問題かというと、autorequireがthread safeでないということで、
プロセスの(再)起動時において、 Thread.new に突入した時点では TestMailer は未定義である
ということである。
そこで、autorequire が がりがりと TestMailer のソースファイルを読み込んでクラスを定義する
のだが、
複数のスレッドで autorequire がクラス定義を始めてしまうことになる
為に
Object is not missing constant TestMailer!
で落ちる訳だ。
対処法
要は Thread.new する前にクラス定義が終了していれば良いので、模擬コードはこんな感じになる。
def send_many_mail(recipients) TestMailer recipuents.each_slice(1_000) do |recipients_sub| Thread.new do recipients_sub.each do |recipient| TestMailer.deliver_test_mail(recipient) end end end end
もっと前に明示的に require を書くのが正しい気がしないでもない。
最近のコメント