2011年2月23日水曜日

Rails3とUploadify

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

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

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

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


まずは Uploadify をダウンロード。バージョンは v2.1.4 です。zipファイルを展開すると:

com/
cancel.png
check.php
expressInstall.swf
jquery-1.4.2.min.js
jquery.uploadify.v2.1.4.js
jquery.uploadify.v2.1.4.min.js
swfobject.js
uploadify.allglyphs.swf
uploadify.css
uploadify.fla
uploadify.php
uploadify.swf

こんな感じのものが出てきました。

手軽に設置できるようにという配慮なのか、 uploadify.php なんかも含めて適当にWebサーバに置いておくと即座に使えるようになっているみたいです。が、今回は Rails なので、必要最低限のものだけ然るべき場所に配置します。

必要なものは cancel.png, jquery.uploadify.v2.1.4.min.js, swfobject.js, uploadify.css, uploadify.swf の5つ。他(特に com/ 以下)は何のためにあるのかわかりませんでした。swf を改造したい人のため?

cancel.pngpublic/images/ へ、各種 JavaScript は public/javascripts/ へ、uploadify.csspublic/stylesheets/ へ、uploadify.swfpublic/ へそれぞれ配置。って当たり前ですよねごめんなさい。

それから app/views/layouts/application.html.erb にでも JavaScript と CSS を読み込むように指示しまして、次に Gemfile

gem "flash_cookie_session"

と書いて

$ bundle

もしくは

$ sudo gem install flash_cookie_session

とします。

次は実際の html です。

<%= form_tag({:action => :upload}, {:multipart => true}) do %>
  <%= file_field_tag :file, :id => :file %>
<% end %>
<div id="fileQueue"></div>


<script type="text/javascript">
  <% key = Rails.application.config.session_options[:key] -%>
  $(function() {
    $("#file").uploadify({
      uploader: "/uploadify.swf",
      script: "#{url_for upload}",
      cancelImg: "/images/cancel.png",
      folder: "/tmp",
      auto: true,
      scriptData:{
        "<%= request_forgery_protection_token -%>": "<%= form_authenticity_token -%>",
        "<%= request_forgery_protection_token -%>": encodeURIComponent("<%= u form_authenticity_token -%>"),
        "<%= key -%>": "<%= cookies[key] -%>"
      }
    });
  });
</script>


2011/02/26追記:
encodeURIComponentを呼び出さないとcsrf_tokenがうまく届かないことが判明しました。


:multipart => true が必要なのかどうかは知りませぬ。そもそも form_tag で囲む必要さえあるのかどうか…。でも個人的に他のオプションをくっつけたいので、ご都合主義的にとりあえずこのまま。

$("#file").uploadify(); の時点でフォームがFlashのボタンに置き換えられます。<div>タグは見た目空っぽですが、ファイルのアップロードを開始した時点で進捗バーに切り替わります。多分。

uploadify関数に渡す引数についてはドキュメントを参照してもらうとして、重要なのは scriptData 以下です。request_forgery_protection_token"authenticity_token" に置き換わって、form_authenticity_tokencsrf_meta_tag で生成されたトークンに置き換わります。これが無いと、前回の記事でも述べたようにセッションが空になるので注意のこと。

key には Rails.application.config.session_options[:key] が入ってます。これはアプリケーションが使うセッションのキー、が格納されている Cookie のキー、です。多分。わかりづらいのはボクも理解してないからです。これは Rails3 以降でのみ有効なので、 Rails2 系のキーの取り出し方については他の人の記事を参照してくだし。

で、これを {Cookie のキー:セッションのキー} という形で(幾分ややこしく)送るわけですが、 authenticity_token と違って、こちらは ActionDispatch が透過的に処理してくれるわけではありません。「Flash経由だから」という理由しかボクにはわかりません。

そこで冒頭で述べたように、 ActionDispatch::Session::CookieStore が呼び出される前に、自分で作成した Rack レベルのミドルウェアを呼び出すようにするのが一般的のようですが、今回は【flash_cookie_session】という gem を使っているのでその必要はありません。Gemfile にロードするよう指示しておけば透過的に作用してくれるみたいです。

…ってあれ、解説終わっちゃった。

えーと、あとはサーバを再起動するなり何なりして動作を確かめてください。以上!

と思ったらうまく動かなかったので
2011/02/24 追記→http://ruler-sandmark.blogspot.com/2011/02/actiondispatchsessioncookiestore.html

0 件のコメント:

コメントを投稿