ホーム > タグ > ruby

ruby

Rails に XML をボディとするリクエストを JavaScript で送る

Rails における XML でのリクエスト

Rails に、Content-Type: application/xml で、ボディが XML のリクエストを送ると、コントローラ中では params に展開される。

# リクエスト # ヘッダはいろいろ省略してます POST /entries HTTP/1.1 Content-Type: application/xml <?xml version="1.0" encoding="UTF-8"?> <entry> <body>New blog entry</body> </entry> # EntriesController def create p params # => {"entry" => {"body" => "New blog entry"}, ... ...

これは主に ActiveResource のための仕様だろうが、XML によるレコードのエクスポートとインポートを行う目的で、同じインターフェイスを HTML から使いたいと考えた。

JavaScript によるリクエスト

HTML の form で Content-Type を application/xml にすることはできない (よね?) ので、jQuery を使って JavaScript で送る。

// url に URL 文字列、 // xml に XML 文字列が格納されているものとする。 $.ajax({ type: "POST", url: url, dataType: "xml", // Accept ヘッダをセットする。レスポンスを XML で受けとるなら必要。なくてもよい。 contentType: "application/xml", // Content-Type を XML にする data: xml, success: function(res, type) { alert(res); }, error: function(req, message, error){ alert(error); }, });

しかし、HTTP メソッドが GET 以外の場合、リクエストに含まれる authenticity_token をチェックし、無いか session[:authenticity_token] と一致しないと、session が空になってしまう (※)。

これは具合が悪いので authenticity_token を送信する方法を考える。

※ ApplicationController などで protect_from_forgery メソッドが呼ばれている場合。普通呼ばれてます。

CSRF プロテクショントークンを送る

form_for で作ったフォームには authenticity_token が hidden フィールドで格納されているので、そのまま submit すればよい。また、Ajax 通信でも、JSON で送るなら、送信するオブジェクトに authenticity_token プロパティを付与すればよい。この場合 authenticity_token は meta タグから取得する。

<!-- 最近の Rails (3 以降?) なら application.html.erb の head 要素にこんな記述があるはず --> <%= csrf_meta_tag %> <!-- これにより下記のような meta 要素が生成される --> <meta content="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" name="csrf-token"> // jQuery でこれを取得する var csrf_token = $("meta[name=csrf-token]").attr("content");

しかし、XML はルート要素が 1 つと決められているし、ルート要素は params の 1 要素に納められてしまうため、リクエストボディに authenticity_token を含めることはできない。

そこで、authenticity_token をチェックしているコードをみてみる。

# actionpack/lib/action_controller/metal/request_forgery_protection.rb def verified_request? !protect_against_forgery? || request.get? || form_authenticity_token == params[request_forgery_protection_token] || form_authenticity_token == request.headers['X-CSRF-Token'] end

どうやら X-CSRF-Token ヘッダにトークンを書いてもいいらしい。

jQuery.ajax は beforeSend オプションで、XMLHttpRequest オブジェクトを変更できるので、XMLHttpRequest.setRequestHeader() を使って X-CSRF-Token ヘッダを追加する。

var csrf_token = $("meta[name=csrf-token]").attr("content"); $.ajax({ type: "POST", url: url, dataType: "xml", // Accept contentType: "application/xml", beforeSend: function(xhr) { // X-CSRF-Token ヘッダのセット xhr.setRequestHeader("X-CSRF-Token", csrf_token) }, data: xml, success: function(res, type) { alert(res); }, error: function(req, message, error){ alert(error); }, });

これで OK。

Processing 環境の比較

このところ、花火シミュレートアプリ HANABICS (Chrome で見てね) というのを開発している (仕事じゃないよ)。

Processing というフレームワークで動かしてるんだけど、開発の過程でいろいろな環境を渡り歩いたので、それぞれの特徴についてまとめてみる。

続きを読む

RubyKaigi 2011 メモ (2 日目)

RubyKaigi 2011 のメモ、2 日目です。rubykaigi.org に録画があることに気づいたので、リンクをつけてます。

続きを読む

RubyKaigi 2011 メモ (1 日目)

7 月 16 日から 18 日まで開催された RubyKaigi 2011 に参加してきました。非常に楽しく、ためになる 3 日間でした。

セッションをきいている間、メモをとっていたので (多少整形して) 公開します。

理解できていない部分、誤って理解している部分、抜けている部分など多々あるので、備忘録としての役割が主です。これを読んで気になる部分があれば、他のレポート (RubyKaigi2011 スペシャルレポートとか) を読むなり、ust みるなり、登壇者が発信してる情報をさがすと良いと思います。

続きを読む

Picup を利用して Rails アプリに iPhone / iPad から画像をアップロードする

ご存じ iPhone / iPad のブラウザである Mobile Safari は、HTML5 の機能を多数実装してるわりに、<input type=”file” /> を使ったファイルのアップロードができない。

もちろん専用のネイティブアプリを作成すれば可能だけど、まあ、ほとんどの場合めんどくさいよね。

そこで、多少ユーザを選ぶけど、便利そうなのが William Lindmeier 氏制作の iOS アプリ Picup である。

ここでは、Picup の仕組みと、Rails アプリでの実装例を紹介する。

Picup の仕組み

iOS や MacOS (X?) では、URL スキームにアプリケーションを関連づけられる。Picup の場合 fileupload:// に続けて、アプリに渡したいパラメータをつけて Safari 等で開けば、Picup にパラメータが渡され起動する。

サーバ側では、Picup の Web サイトで配布しているスクリプト (picup.js) を利用して、<input type=”file” /> を、fileupload://… を開くボタンに書き換える (別にこのスクリプトを使わなくても fileupload://… へのリンクを自前で作ってもいい)。

このボタンがクリックされると、POST 先の URL (postURL)、フィールド名 (postImageParam) などのパラメータを渡して Picup が起動される。そして、Picup 上で、ユーザーが、カメラで撮影するか既存の写真を選択し、Done をタップすると、さきほど渡されたパラメータをもとに POST してくれる。

さらに、callbackURL を指定すると、POST 後に任意の URL を Safari で開くこともできる。その際に # 以降にいくつかパラメータが渡されるので、それを利用して、ユーザーを誘導することもできる。

Rails での作例

以下では、Ruby 1.9.2 + Rails 3.0.7 + carrierwave で Picup による写真のアップロードに対応した Rails アプリを作る。

とりあえず初期設定。

$ rails new picup_demo -d mysql -J # Gemfile - gem 'mysql2' + gem 'mysql2', '0.2.7' + gem 'carrierwave' + gem 'jquery-rails' $ bundle $ rails g jquery:install

jquery や後述の picup.js をロードするようにする。

# app/views/layouts/application.html.erb - <%= javascript_include_tag :defaults %> + <%= javascript_include_tag :all %>

写真のモデル・コントローラ・ビューを scaffold でつくる。

$ rails g scaffold photo file:string $ rake db:create $ rake db:migrate

アップローダをつくり、photos.file に設定する。

$ rails g uploader photo # app/models/photo.rb class Photo < ActiveRecord::Base + mount_uploader :file, PhotoUploader end

フォームのフィールドを、type=”file” にする。show で写真を表示する。

# app/views/photos/_form.html.erb - <%= form_for(@photo) do |f| %> + <%= form_for(@photo, html: {multipart: true}) do |f| %> - <%= f.text_field :file %> + <%= f.file_field :file %> # app/views/photos/show.html/erb - <%= @photo.file %> + <%= image_tag(@photo.file.url) %>

この時点で PC の Web ブラウザからアップロードできるか確認しておくとよい。

$ rails s # http://localhost:3000/photos/new へ Web ブラウザでアクセス

Picup のヘルパースクリプトを導入。http://picupapp.com/picup.js.zip をダウンロード、展開し、picup.js を public/javascripts 下に移動する。

type=”file” のフィールドを Picup を開くボタンに置き換える。

# app/views/photos/_form.html.erb <script type="text/javascript" charset="utf-8"> $(function(){ window.name = "Picup_demo_new_photo"; // アップロード後表示されるページから遷移するためにウィンドウ名をつけておく Picup.convertFileInput( "photo_file", // <input type="file" /> の ID { 'referrername': escape('Picup Demo'), // Picup に表示されるアプリケーション名 'purpose': escape('Upload A Photo'), //Picup に表示されるメッセージ 'postImageParam': 'photo_file', // フィールド名、この場合、rails では params[:photo_file] でアクセスできる。 'postURL': 'http://192.168.1.10:3000/photos.json', // post先 'callbackURL': 'http://192.168.1.10:3000/uploaded' // アップロード後に開く URL } ); }); </script> # app/controllers/photos_controller.rb def create - @photo = Photo.new(params[:photo]) + @photo = Photo.new(file: params[:photo_file])

IPアドレスは適当に書き換えて。 ここまででとりあえずアップロードはできる。 (アップロード後表示されるページは RoutingError になっちゃうけど、/photos を見ると、正しくアップロードできているのが確認できる)

アップロード後、アップロードされた写真のページへ遷移させたいので、create のレスポンスに、そのページの URL を含ませる。

# app/controllers/photos_controller.rb format.html { redirect_to(@photo, :notice => 'Photo was successfully created.') } format.xml { render :xml => @photo, :status => :created, :location => @photo } + format.json { + imgur_like_response = { + rsp: { + stat: "ok", + image: { + original_image: photo_url(@photo) + } + } + } + render :json => imgur_like_response + }

{rsp: …} となっているハッシュは、imgur に画像をアップロードした際のレスポンスを模したものである。いままで触れなかったが、Picup は postURL の指定がない場合、imgur.com に画像をアップロードする。その際、レスポンスに含まれる original_image の値は、アップロード後開かれる URL の # 以降に remoteImageURL=… の形で渡される (imgur.com からのレスポンスでは他にもいろいろな情報が付与されているが、無視されている)。

# public/uploaded.html <!DOCTYPE html> <html> <head> <title>Picup Demo</title> <script> <!-- var redirect = function() { var keys_values = window.location.hash.substring(1).split("&"); var params = {}; for(var i = 0; i < keys_values.length; i += 1){ var key_value = keys_values[i].split("="); params[key_value[0]] = key_value[1]; } if (params.remoteImageURL) { window.open(unescape(params.remoteImageURL), 'Picup_demo_new_photo'); window.close(); } }; redirect(); //--> </script> </head> <body> <h1>アップロード完了</h1> <a href="javascript:redirect();">自動的に移動しない場合はここをタップしてください。</a> </body> </html>

これが、アップロード後開かれるページである。URL の # 以降の remoteImageURL=… の値を読み出し、もともとフォームを表示していたタブ (Picup_demo_new_photo) で開き、このページ自身は閉じるようにしている。いちおうこれで、PC のブラウザからアップロードした際と似た挙動を実現できる。

なお、<input type=”file” /> の置き換えを、UA が Mobile Safari であることを確認した上で (Apple 公式の DetectingWebKit を使えば簡単) 行えば、PC のブラウザでも Mobile Safari でもほぼ同じように使える。

気になるところ

先述のとおり Picup.convertFileInput には postImageParam オプションで、送信されるファイルのフィールド名を指定できる。ここは、Rails の規約に従って photo[file] としたいところだが、実際に試してみると、Rails にはフィールド名が photo%5bfile%5d として渡されてしまい、params[:photo][:file] のようなかたちではアクセスできない。無用なエスケープがされちゃってるんですね。ちょっと残念。

あと、POST のレスポンスをもうすこし利用できるとうれしい。imgur のレスポンスに特化してるのがもったいない。

iBooks で日本語の辞書を引くように EPUB ファイルを変換するスクリプト

iPhone / iPad などの iBooks で、EPUB ファイルを読むとき、文字列を選択して辞書を引ける。ただ、ここで引ける辞書は (なぜか) EPUB の言語に依存するので、英語の本で辞書を引くと、英英辞書を引くことになる。

英和辞書を引くためには、EPUB 内のファイルを編集し、言語を日本語 (ja) にする必要がある。この手順は下記ページに詳しい。

ただ、この手順、結構めんどくさいので Ruby でスクリプト書いた。

Ruby (1.8.7 か 1.9.2) と RubyGems (1.8 の場合) があるなら、

gem install zipruby

で、必要な gem を導入して、上記スクリプトをダウンロード。スクリプトのあるディレクトリで、

ruby change_lang_of_epub.rb (変換したい EPUB ファイル)

とすれば、(元のファイル名).ja.epub というファイルが生成されます。

ruby change_lang_of_epub.rb (変換したい EPUB ファイル) (言語コード)

とすれば、日本語 (ja) 以外の言語コードも指定できます。

Ruby はこの手のスクリプトがさくっと書けてよかですね。

無限大を含む Date の Range で include? (include_with_range?) に失敗する

Ruby + ActiveSupport で、ある期間と別の期間との関係を調べるメソッドを書いてたところ、期間に無限大を含む場合に失敗した。

# ruby 1.8.7 require "date" require "rubygems" require "active_support" (Date.today..(1.0 / 0)).include?(Date.today..Date.tomorrow) # true これは問題ないが、逆にすると... (Date.today..Date.tomorrow).include?(Date.today..(1.0 / 0)) # NoMethodError: undefined method `<=' for nil:NilClass

ActiveSupport のコードを追ってみると、原因は Float <=> Date で nil が返ってしまう点にあるらしい。

Date.today <=> 1.0 # 1 1.0 <=> Date.today # nil

Date が演算子の左側にある場合、Date#<=> が呼ばれる。実装は下記。

# File lib/date.rb, line 1267 def <=> (other) case other when Numeric; return @ajd <=> other when Date; return @ajd <=> other.ajd end nil end

演算子の右側が Numeric なら、@ajd と比較している。@ajd は、紀元前4713年1月1日からの日数を示す数値。数値なので Float と比較できる。

いっぽう、Float が演算子の左側にある場合は、Float#<=> が呼ばれるため、Date をどう扱っていいかわからず、比較できない。

そこで Date#coerce を実装した。

require "date" class Date def coerce(other) return [other, @ajd] if other.is_a? Numeric nil end end

Numeric は、比較対象と型が異なる場合、比較対象の coerce メソッドを (あれば) 呼び出して、型を合わせてから比較する。これにより、@ajd を使って比較できるようにする。
これで演算子の左側が Float でも比較できるようになり、include? や overlaps? も動く。

1.0 <=> Date.today # -1 (Date.today..Date.tomorrow).include?(Date.today..(1.0 / 0)) # false

また、始点に Float を、終点に Date を持つ Range も作成できる。

(-(1.0 / 0)..Date.today) # 無限遠の過去から今日までの Range

include? や overlaps? 含め、おおむね期待どおりに動くが、この場合、Date の範囲でなく Float の範囲になってしまうので、Enumerable にならないなどの制限がある。

しかし coerce なんて、ごく最近、プログラミング言語 Ruby 読むまで知らなかった。記述細かいなあと思ってたけど、役に立つもんですね。

IMSLP の楽譜を市販の楽譜っぽく製本するまで (ソフトウェア篇) | あるいは JRuby + iText による PDF の面付け (トンボ付き)

通常 IMSLP などに保存されている楽譜は、実物の 1 ページが PDF の 1 ページに対応している。そのため、A3 ノビの紙に印刷して中綴じ製本するためには、適切にページを並べ替え、片面 2 ページづつ、両面で 4 ページを配置する – 面付けと呼ばれる – 必要がある。

簡易な面付けは Adobe Reader やプリンタドライバでも行える (N-up などと呼ばれる) が、これらの多くは紙面を均等に分割し、それぞれの中央に元のページに配置する。そのため、余白がノド (見開きの中央) にも来てしまい、三方裁ちをするような製本には向かない。

また、もとの PDF が菊倍版など楽譜に適した判型でなく、A4 などの判型になっている場合がある。この際は、一度菊倍版に配置しなおした上で、面付けを行う必要がある。

こうした処理が簡単にできる無償のソフトウェアが見つけられなかったので、書くことにした。

JRuby + iText を使う

PDF の作成・編集ライブラリには、Java で書かれた iText というライブラリが使いやすそうなので、これを使う。

ただ、私は Java での開発経験がないため、JRuby から利用することにした。JRuby は Ruby の Java による実装で、Java のクラスが扱えるのが大きな特徴。(今回はじめて使ったが、あまりにも簡単に Java のクラスが扱えて驚いた。今後も Java のライブラリが必要になったら使いたい。)

JRuby + iText のインストール

JRuby のインストール

今回は Mac OS X で開発 / 使用するので、Java のインストールは必要ない (標準でインストールされているため)。

JRuby は MacPorts を使ってインストールするので、必要なら MacPorts をインストールしておく。

MacPorts がインストールされていれば JRuby のインストールは下記のコマンドだけで完了する

sudo ports install jruby

インストールが終ったら、jirb コマンド (irb の jruby 版) などで、ちゃんと動くか試してみよう。

iText のインストール

iText 2.1.7 を利用する。最新版の 5 系より情報が多いのがその理由である。

SourceForge.net の iText のページから、下記の 3 つのファイルをダウンロードし /opt/local/share/java/jruby/lib 下にコピーしておく。

スクリプトの作成

今回は2つのスクリプトを作成した。

1in1.rb

入力ファイルのページを、指定したサイズのページ中央に配置して出力する。

jruby 1in1.rb [-with-tombo] 出力サイズ 入力ファイル名 [出力ファイル名]

2in1.rb

入力ファイルの2ページづつを、指定したサイズのページ中央に2つ横に並べて出力する。中綴じ製本用にページは並べ変えられ、出力の1ページ目が1枚目の表、2ページ目が1枚目の裏・・・となる。

jruby 2in1.rb [-with-tombo] [-first-page-is-left] 出力サイズ 入力ファイル名 [出力ファイル名]

引数

-with-tomboトンボ (トリムマーク) を出力する
-first-page-is-left1 ページ目が見開きの左側であることを指定する。省略時は 1 ページ目が見開きの右側になる。
出力サイズ’210×297′ のように ‘幅x高さ’ で指定する (単位は mm) か、下記のいずれかを指定する。
a3, a3+, a4, a5, a6, b3, b4, b5, b6, a3r, a3+r, a4r, a5r, a6r, b3r, b4r, b5r, b6r, kikubai, kikubai-alt
※ a3+ は A3 ノビ。r のついたものは長辺が横向き。kikubai は 227x304mm、kikubai-alt は 218x304mm。
入力ファイル名入力ファイル名。
出力ファイル名出力ファイル名。省略時は ‘出力サイズ_入力ファイル名’

使用例

菊倍版に変更。

jruby 1in1.rb kikubai src.pdf kikubai.pdf

菊倍版のページをA3ノビに配置(トンボつき)

jruby 2in1.rb -with-tombo a3+r kikubai.pdf a3+r.pdf

上例のサンプル

コード

主に自分で使うために作ったものなので、例外処理などほとんどなし。著作権は放棄しますので、ご自由に。

参考リンク

ホーム > タグ > ruby

検索
フィード
メタ情報

ページの上部に戻る