Sunday, December 14, 2008

PDFPack.app

名刺をデザインしてA4の用紙に複数面割り付けて印刷しようと思ったけど、どうすればいいのかわからない。

DTPの知識(と高価なソフトウェア?)があれば簡単なことだろうとは思うけれど、適切なフリーソフトも見つからない。Mac OS X プリンタダイアログ標準のレイアウト機能の複数ページを1枚に割り付ける方法ではあらかじめ同じデザインのページのコピーを作成してPDFなどにまとめる必要があるし、そもそも名刺などの場合はサイズが重要なので1ページ分の出力サイズを指定できないので使えない。

やりたいことは実にシンプルで、名刺やカードなど小さいサイズで同じデザインのものを、出力する用紙(A4など)1枚に対して複数印刷したいだけなので、RubyCocoa のリハビリを兼ねて、その目的を達成するためだけのフィルターのようなツール PDFPack.app を作ってみました。

単体のアプリケーションとしても動きますが、インストールすると PDF Service (プリントダイアログの左下の "PDF" ボタンで出てくるメニュー)として登録されるので、以下のような使い方ができます。

使用例として名刺を印刷する場合


1. カードをデザインするツール (僕は OminiGraffle を使いました)のページ設定(新しいアプリケーションではプリントダイアログに統合されているようです)の用紙サイズのカスタムサイズを管理で 5.5 x 9.1cmの用紙を追加して選択

2. そのサイズにあわせて名刺をデザインしてプリントダイアログ(Cmd+P)を開いて、PDFメニューから "PDFPack" を選択


3. PDFPack.app が起動するので必要ならマージンを調整して印刷

ちなみに、用紙はエーワンマルチカード#51002、プリンタはHP Deskjet 5551を利用。このA4サイズの用紙では 5.5x9.1cm のサイズを10面印刷するときに左右の余白は1.4cm、上下の余白が 1.1cm になるのですが、PDFPack のバグかプリンタの仕様か、bottom margin を 0.9cm と 0.3mm 小さく設定しないとうまい具合に印刷できませんでした。

蛇足ですが、似たようなツールに Hipster PDA を印刷する PagePacker というものがあります。PDFPack も1枚の紙に異なる種類のカードを印刷できると便利かもしれません。

Monday, November 24, 2008

mysql_xml2google_notebook_atom

Google Notebook を使うようになってから、以前に作ったメモを溜める web application が必要なくなったけれど、メモの内容は参照したいことがあったので、Google Notebook にインポートしてみました。

その web application は mysql を使っていたので、 mysql コマンドの --xml オプションで出力した結果の XML を Google Notebook がインポートできる Atom フォーマットの XML に変換するスクリプト mysql_xml2google_notebook_atom.rb を書き、変換してからインポート。

余談ですが、Google Notebook には Atom フォーマットでエクスポートする機能もあります。しかし、残念なことに XML の encoding を iso-8859-1 (latin-1) から変更する方法が分からないので、日本語のノートは Atom ではエクスポートできない。
どこにフィードバックを送ればよいのかわからないので、Google Notebook Official Blog の記事にコメントしました。オープンソースでないとこういうところがもどかしい。

Friday, November 14, 2008

Polymorphic_url should compact given array

ネストした URL にしたい場合、たとえば /groups/1/users だと、routes.rb は、こんな感じ。
map.resources :groups, :has_many => :users

さらに、 group を指定しないユーザーの一覧を取得したい場合 (/users) には resources を追加。
map.resouces :users


polymorphic_path を使うと
polymorphic_path([@group, :users]) # A
polymorphic_path(:users) # B


ここで、 @group が nil だったら、 A は B と同じ解釈をしてほしいいのだけど、
"NoMethodError: undefined method nil_class_users_url'"

のようにエラーになってしまうので、patch を投げたら、 commit された。

非常に小さな変更だけど、パッチが受け入れられるとうれしいもんですね。

Thursday, October 30, 2008

How to skip ActionController::Filters with block

before_filter :foo
def foo
...
end

メソッド名のシンボルを与えてフィルターを指定する場合、
skip_before_filter :foo

でスキップできるけど、
before_filter do |controller|
...
end

ブロックを使った場合スキップする方法がわからないので ActionController::Filters の実装を調べたら、
before_filter :identifier => :foo do |controller|
...
end

とすることで、
skip_before_filter :foo

できることがわかった。

:identifier オプションは ActiveSupport::Callbacks::Callback のアトリビュートなので、 ActionController::Filters の実装が ActiveSupport::Callbacks::Callback を使っていない 2.0.X 以前のバージョンでは使えないと思われるので注意。

Monday, October 27, 2008

Fixing false latin1 encoding MySQL database into utf8

Rails 2.2 には mysql pure ruby adapter が含まれていないので、アップデートしたときに mysql native adapter を入れていない事に気がついた。
 sudo gem install mysql

その後、アプリケーションを再起動したら、データベースに保存されている文字列の表示が思いっきり文字化けしてしまった。
database.yml をよく見てみると、
 encoding: utf-8

しまった。 MySQL adapter の場合、
 encoding: utf8

と書かなければいけなかったのだ。
MySQL native adapter のデフォルトエンコーディングは latin1 らしいので、一度、 latin1 としてダンプして、
 mysqldump --default-character-set=latin1 -u root foo_production > foo.mysql

ダンプファイルの SET NAMES を書き換え
- /*!40101 SET NAMES latin1 */;
+ /*!40101 SET NAMES utf8 */;

database の再作成
mysql -u root -e "drop database foo_production; create database foo_production"

そして、ロード
mysql -u root foo_production < foo.sql


つまらないバッドノウハウで恐縮ですが、以前にも同じ事をやらかしてしまったことがあったので役に立つこともあるかと。

Wednesday, October 22, 2008

Passenger: Keeping ApplicationSpawner alive speeds up spawning an instance

アクセス頻度が少ないサイトで Passenger を使っている場合、しばらくしてからアクセスするとデプロイ直後や apache のリスタート直後と同じくらいレスポンスが遅くなることがあります。

Passenger は fork 時の copy-on-write により複数のアプリケーションプロセスが消費する実メモリのサイズが小さくなるようになっていて、その fork も以下のような3段階で行っているようです。

  1. 最初に起動する spawn server から fork して、 Rails をロード (framework spawner)

  2. framework spawner から fork して、アプリケーションをロード (application spawner)

  3. application spawner から fork して、リクエストを処理する過程で必要なファイルをオートロード (application instance)


spawn server は apache が終了するまで生きているようなんですけど、 framework spawner と application spawner にはタイムアウトする時間が設定されていて、 Passenger 2.0.3 のデフォルトではそれぞれ、30分、10分となっています。
したがって、前回のアクセスから10分過ぎると、再度 application spawner から生成し直すのでレスポンスに時間がかかります。

ということは、 application spawner のタイムアウト時間を十分に長くすればよいということで、無理矢理 passenger のファイルを変更。

/usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/lib/passenger/constants.rb:
-   FRAMEWORK_SPAWNER_MAX_IDLE_TIME = 30 * 60
- APP_SPAWNER_MAX_IDLE_TIME = 10 * 60
+ # keep them alive for a week
+ FRAMEWORK_SPAWNER_MAX_IDLE_TIME = 7 * 24 * 60 * 60
+ APP_SPAWNER_MAX_IDLE_TIME = 7 * 24 * 60 * 60


Passenger 2.0.3 の段階では上記のような変更が必要ですが、 将来のバージョンでは apache の設定ファイルで RailsFrameworkSpawnerIdleTime, RailsAppSpawnerIdleTime を指定すれば変更できるようになるようです。

また、 Phusion Passenger users guide - 8.3 Capistrano Recipe の note に書いてあるように、デプロイ後に新しい application spawner が生成されても古いのが残ったままになるので、デプロイするときに kill。

config/deploy.rb:
namespace :passenger do
namespace :application_spawner do
desc "kill Passenger ApplicationSpawner"
task :kill do
run "kill $( passenger-memory-stats | grep 'Passenger ApplicationSpawner' | awk '{ print $1 }' ) || true"
end
after "deploy:update_code", "passenger:application_spawner:kill"
end
end

Passenger のドキュメントに書かれているように "Passenger spawn server" を殺すには root 権限が必要なので代わりに application spawner を kill。なんとなく不安定な気もするけど、これで様子を見ることにします。

参考資料:
Wishlist — passenger — GitHub
RailsFrameworkSpawnerIdleTime and RailsAppSpawnerIdleTime - Phusion Passenger Discussions | Google Groups
Modrails ... Slow first request ... - Phusion Passenger Discussions | Google Groups

Sunday, October 19, 2008

URL options for polymorphic_url

routes.rb:
 map.resources :users 

というリソースへのルートを定義している場合、

 polymorphic_url(:users) # => "/users"

という具合になります。

そこで、追加のパラメータをあたえると
 polymorphic_url(:users, :foo => 1) # => "/users?foo=1"

となることを期待する訳なんですけど、 Rails 2.1.1 ではそうはならなくて、URLパラメータが付かない一つ前の例と同じパスが返ってくる。

#880 URL options for polymorphic_url - Ruby on Rails - rails

edge では修正されてますけど、リリースするまでは、以下のような汚いパッチでごまかします。

if Rails::VERSION::STRING == "2.1.1"
# polymorphic_url should accept extra parameter options
# polymorphic_path(:projects, :foo => 1) # should be => /projects?foo=1
# This portion of code fixes this issue in a quick and dirty way.
ActionController::PolymorphicRoutes.module_eval do
def polymorphic_url_with_params(record_or_hash_or_array, options = {})
url = polymorphic_url_without_params(record_or_hash_or_array, options)
url_options = options.except(:action, :routing_type, :format)
unless url_options.blank?
url += "?" + url_options.to_param
end
url
end
alias_method_chain :polymorphic_url, :params

%w(edit new formatted).each do |action|
module_eval <<-EOT, __FILE__, __LINE__
def #{action}_polymorphic_url(record_or_hash, options = {})
polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
end

def #{action}_polymorphic_path(record_or_hash, options = {})
polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
end
EOT
end
end
end