2011年2月27日日曜日

[Rails3] whenever

whenever】はRubyとCronの橋渡しをしてくれるようなgemで、DSLで処理を記述しておくだけで crontab に書き込んでくれるという嬉しい代物です。bash経由で実行するようです。

Railsでももちろん使えて、rakeやrunnerなどを実行できますが、runnerの場合、うちの環境では bash のオプションに -l (--login) がついているとうまく動作しませんでした。

ので、手動で crontab -e して -l を削除しましたとさ、という報告でした。


2011年2月26日土曜日

Ruby on Rails と Thread

Rails2.2 の頃からスレッドセーフだとか何とか騒がれていましたがボクは知りません。知らないので、ネイティブスレッドではないRubyの Thread クラスを Rails で使っちゃおうという計画。delayed_jobではちょっと力不足だったのです。

やりたいことはただひとつ。
  • サーバで起こっている作業の途中経過をAjaxで取得したい。
コレ。簡単そうに思えましたが、ところがどっこい苦労しまくって体調崩すほどでした。誰か他にいい方法があったら教えてください。

つまりですよ。
  1. クライアントがファイルをアップロードする。
  2. サーバはレスポンスを返す。
  3. と同時に、サーバは新しく Thread.new して非同期に処理を開始し、処理をしながら進捗情報をどこかに保存しておく。
  4. クライアントは setInterval とかでサーバにリクエストを送信する。
  5. サーバは保存されている進捗情報を返す。
こんなことがしたかったんですね。

超問題になったのが「進捗情報の保存場所」。ぶっちゃけると session は使えませんでした。値が保持されるかと思ったら、スレッドの外と内でスコープが異なるようです。つまり 5 の時点で謎の値が返るわけです。

次に考えたのが app/models/ に新しくクラスを作成して、クラス変数に保持しておく、ということ。でもこれも駄目でした。development環境ではリクエストのたびにクラスがロードされるらしいので、予測不可能な段階でクラス変数が吹っ飛びます→NameError。production環境では動くかも知れないけど、さすがにそんな危ない橋は渡れません。

で、さんざん悩んだ結果、 lib/thread_manager.rb を作る、ということでした。

class ThreadManager
  @@progress
  def self.progress
    @@progress
  end
  def self.progress= value
    @@progress = value
  end
end


クラス変数に対するアクセサメソッドはマクロで定義できないようです。そんなに使う機会が無いとはいえ、あればもうちょっとスマートにできましたかね。

ともあれ、これで何とか凌げそうです。苦労はしましたが、得るものも大きかった…とは言い辛いぞ!正直Scala+Liftに乗り換えたいぞ!でもそれって、「隣の芝は…」ってやつですかね…。


2011年2月24日木曜日

ActionDispatch::Session::CookieStore RuntimeError

になって困ってたんですよね。

前回の記事】で「Flashからのセッション情報を gem で解決する」と記述しましたが、どうもこれがうまく動かないときがあるので、やっぱりRackミドルウェアとして実装することにしたのです。

まずは app/middleware/flash_session_cookie_middleware.rb:


require 'rack/utils'


class FlashSessionCookieMiddleware
  def initialize app, session_key="_session_id"
    @app = app
    @session_key = session_key
  end


  def call(env)
    if env["HTTP_USER_AGENT"] =~ /^(Adobe|Shockwave) Flash/
      req = Rack::Request.new(env)
      env["HTTP_COOKIE"] = [@session_key, req.params[@session_key]].
                            join("=").freeze unless req.params[@session_key].nil?
      env["HTTP_ACCEPT"] = "#{req.params['_http_accept']}".
                           freeze unless req.params['_http_accept'].nil?
    end


    @app.call(env)
  end
end

次は config/application.rb:

# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/app/middleware)

最後に config/initializers/session_store.rb:



MyApplication::Application.config.session_store :active_record_store, :key => '_MyApplication_session'


Rails.application.config.middleware.insert_before(
  ActionDispatch::Session::CookieStore,
  FlashSessionCookieMiddleware,
  Rails.application.config.session_options[:key]
)

としたんですが、どうも「ActionDispatch::Session::CookieStoreなんてミドルウェアは知らないよ」と、タイトルにもあるとおりRuntimeErrorを吐かれてサーバが起動できない。散々調べた結果、収穫はゼロ。このエラーに悩まされているのは世界中でボク一人だったようで、なんとも。

で、試しに :active_record_store から :cookie_store に戻してみたんですよね。

MyApplication::Application.config.session_store :cookie_store, :key => '_MyApplication_session'


Rails.application.config.middleware.insert_before(
  ActionDispatch::Session::CookieStore,
  FlashSessionCookieMiddleware,
  Rails.application.config.session_options[:key]
)

動きました。どうやらこいつが原因だったみたいですが…それでも :active_record_store で Uploadify なんかを使っている人は多いと思うんですよね。

新しくアプリケーションを作成したときにこのエラーが発現するのかどうかが気になるところですが、もうなんか疲れたのでこれまで、ということで。気が向いたら RailsTalk に投稿するかも知れませぬ。

2011年2月23日水曜日

Rails3とUploadify

「Railsでアップロードの進捗を表示させたいなぁ…」と思っていたところ、【Uploadify】というものが見付かりました。Flashこそ使えどjQueryのプラグインなので導入も簡単です。と思いきや手間取りまくったのでメモメモ(本当は jQuery-ui のプログレスバーを使うものがあったらいいなと思ったのだけれど、実装する勇気も知識もない)。

まず知らなかったのは、Flash経由でリクエストするとセッション情報が送られない、という事実。常識なのかも知れないけどFlashから遠い位置にいたんだから仕方ないよねっ!

で、作成しているアプリケーションは「ログインしてからでないとファイルをアップロードしちゃだめ」なので、当然セッション情報は無視できないのです。半日Google先生と相談してわかったのは、【flash_cookie_session】という、読んで字のごとくFlashからCookieとかセッション情報を送ってくれる gem があることでした。結論から言うと試したら動いたので、今回はこれを使って Uploadify を設置していきます。

Flashからのセッション情報送信をRackレベルで動作するミドルウェアとして手作業で実装するのが(検索した結果)メジャーのようでしたが、アプリケーションごとに書くのも「わざわざだなぁ」と思ったので、っていうかやってみたんだけど動かなかったので、今回は gem で解決です。

2011年2月20日日曜日

Rails 3.0.4 と csrf

Rails がセキュリティアップデートされてました。最新版は 3.0.4 です。詳しいことは「こちら」へ。

元記事にもある通り CSRF や SQL Injection など非常にヤバい単語が並べられているので則アップデートしたものの、Ajax系の動作がうまく動かなくなり(link_to :remote でGET以外のリクエストをAjax経由で送るとセッションがクリアされてしまう)、頭を抱えていたところ、はたと rails.js に原因があるのではないかと思い立って jquery-rails をアップデートしてみたところ、ビンゴだったので詳細を記録。

ちなみに CSRF はクロスサイトリクエストフォージェリ(Cross site request forgeries)の略記で、外部からの意図しないリクエストによってWebサービスの動作を改竄する攻撃手段のひとつ。らしいです、Wikipediaによると。

SQL Injection は、フォームの内容に SQL 文を埋め込むことでサーバのデータを改竄する、これも攻撃手段のひとつですね。

Railsでは app/views/layouts/application.html.erb<head> タグ内に <%= csrf_meta_tag -%> を埋め込むことでワンタイムトークンを生成し、フォームの送信時に透過的にこれを hidden パラメータとして送信することで外部からの無作為なデータの送信を防ぐ(ことを自動的にやってくれる)わけで、トークンが合致しない場合は InvalidAuthenticityToken 例外が投げられていたのですが、今回は Java や Flash などのリクエストで同じことをやろうとしたときに、なぜだか忘れましたがとにかく例外を投げるだけではよくないらしいです。ので、セッションをクリアすることにしたんだそうで。

SQL Injection については、ActiveRecordlimit に脆弱性があったらしいです。「limitなんて数字だけじゃん」とついつい思ってしまいますが、攻撃する側も防御する側もよく気付くなぁ…と感心してしまいます。そんな悠長な事態じゃないんですけど。

で、肝心の対策。

ボクは jquery-rails を使っているので、

$ sudo gem update jquery-rails
$ rails generate jquery:install

こうして jquery.js, jquery.min.js, rails.js を上書きして事無きを得ました。 rails.js だけでいいのかなと思ったけれど、それでまた動かなかったら嫌なので jquery ライブラリも上書きしてしまいますた。多分バージョンが違うだけだと思うけど(「だけ」ってレベルじゃねーですけど)、jQueryで本格的に開発してる人にはオススメしません。あでも、jsUnitとか使ってるのかしら。いいのかしら。

そんなわけでした。

--

2011年2月14日月曜日

[Rails3] [will_paginate] Array インスタンスを WillPaginate::Collection に変換

SomeKindOfActiveRecordModel.paginate からではなくて、何らかの読み出しで Array オブジェクトになってしまった SomeKindOfActiveRecordModel を動的に WillPaginate::Collection 型(ActiveRecord::Base.paginateで返ってくるやつ)に変換するには、Array#paginateを単に呼び出してやればいいらしい。

ちゃんとスーパークラスをRailsプラグインらしくバリバリ拡張してくれているのは大助かりですね。
--

2011年2月13日日曜日

デザイン変えてみたのだよ

もう随分長いこと更新してなかったので、ということが理由になるのかどうかは知らんけど、ブログのデザインを変更してみた。

CSS3も本腰入れて勉強してるけど、結局(正式にサポートすると発表された)IE9が出るまでは冒険的に実装する気にはなれないんだな。そんな言い訳をしつつBloggerのテンプレートはAwesomeより拝借。

自作曲の設置がHTML5だけで出来たら楽しいっていうか楽そうだなー。

最近はコーディングが楽しいけれど、コーディング === 言葉を書き下すことだと本気で考えている文系脳なので、そっちが忙しいときはどうしてもこっちの更新をしなくなってしまうのではないかという考察。あと、はてなに移行したい。かも。

ただ、そういったスピンサイクルに慣れてきたので今後は読書録でもしていこうかな、と。

自分で翻訳した本を完全に理解してないのはおかしい。絶対におかしい。

--