自分の興味の赴くままにIT技術系のネタを取りとめもなくメモっています。
Ruby言語やLinuxのネタが多いです。

July 05, 2007 [長年日記]

[Rails] Ruby-GetText+Railsのキャッシュ

rubyonrails-talkでRuby-GetTextでキャッシュってどうやるの?という質問があった。そういえば、この質問、過去にも何回かあったなぁ、と思ってちょっと見てみたら、Page/Action/Fragmentの各キャッシュのうち、Action/Fragmentについてはあっさりと対応できることがわかった。具体的には以下のコードをどこかしら(プラグインやapplicaton.rbあたり)に追加すればOK。

module ActionController
  module Caching
    module Fragments
      def fragment_cache_key_with_gettext(name) 
        fragment_cache_key_without_gettext(name).gsub('?', '.').gsub(/:/, ".") << "_#{Locale.current}"
      end
      alias_method_chain :fragment_cache_key, :gettext
 
      def expire_fragment_with_gettext(name, options = nil)
        return unless perform_caching
 
        key = %r{#{fragment_cache_key_without_gettext(name).gsub('?', '.').gsub(/:/, ".")}}
        self.class.benchmark "Expired fragments matching: #{key.source}" do
          fragment_cache_store.delete_matched(key, options)
        end
      end
      alias_method_chain :expire_fragment, :gettext
    end
  end
end

上記はFragmentキャッシュの実装なんだけど、実はActionの方もこちらを呼んでるのでこれだけでよいはず。

使い方はRails本体とまったく変わらないけど軽く説明しておこう。念のため言っておくけど、この例ではArticleというモデルを利用している。あ、そうそう、init_gettextとか、Ruby-GetTextの諸々の設定は先に済ませておくように。

# app/controller/articles_controller.rb
class ArticlesController < ApplicationController
  caches_action :show   #showのアクションをキャッシュ
   :
   :
  def show
    @article = Article.find(params[:id])
  end
  #とりあえずcreate時にキャッシュをクリアする。
  def create
    @article = Article.new(params[:article])
    if @article.save
      expire_fragment(:action => "list")  #Flagmentキャッシュをクリア
      expire_action(:action => "show")   #Actionキャッシュをクリア
      :
      :
  end
# app/views/articles/list.rhtml
<h1>Listing articles</h1>
<% cache do %>   これ以降がキャッシュされる
  :
  :
<table>
  :
  :
</table>
  :
  :
<% end %>   ここまで

要はまったくGetTextを意識する必要がなく、オリジナルのRailsの意識のままで使うことが出来る。ステキ。

Pageキャッシュが最強なのは言うまでもないんだけど、こちらは難しそうなのでちょっと棚上げ。でも、Scaffold直後(+GetText化済み)の10レコードくらいのサンプルアプリケーションの一覧表示でも、Actionキャッシュは2.4倍, Fragmentキャッシュは、1.5倍程度高速化できたので、ちょっと複雑なビジネスロジック、DBアクセスがあればもっと高い効果があるはず。

思いもかけず簡単に実装できてしまったのでちょっと不安も残るけど、せっかくのなのでこの機能を次バージョンのRuby-GetTextに含める予定。

ひとまず、どなたか追試してみません?

[Rails] Ruby-GetText+Railsのキャッシュ(2)

もうちょっとだけ。キャッシュが実際に効いてるかどうかを目で見るには、キャッシュファイルが作成されたり削除されたりを確認するのが手っ取り早い。で、config/environment.rbの一番下に以下の記述を追加。

ActionController::Base.fragment_cache_store =
  ActionController::Caching::Fragments::FileStore.new("/home/mutoh/rails/cache/")

こうしておくと前述の例では以下のようなファイルが生成される。

/home/mutoh/rails/cache/
`-- localhost.3000
    |-- articles
    |   |-- list_en.cache
    |   |-- list_fr.cache
    |   |-- list_ja.cache
    |   `-- show
    |       `-- 24_ja.cache
    `-- articles_ja.cache


July 07, 2007 [長年日記]

[Fedora] FC7でJavaアプレットが起動しない

ハードディスクを高速なものに換装したんだけど、その際にディスクの中身もクリーンにしようと思ってFC7(x86_64)を一からインストールしなおした。

いろいろと順調にいって、Java Appletを使うためにSun Java(i386版。amd64版だとjre/plugin配下が無いんだよね)とを入れ、Firefoxのi386版を入れ直し、

ln -s /usr/java/latest/jre/plugin/i386/ns7/libjavaplugin_oji.so /usr/lib/mozilla/plugins/.

した.....んだけどアプレットが表示されない。うーん、なぜだろうと思っていろいろと調べてるうちに/usr/java/latest/bin/ControlPanelがlibstdc++.so.5がないよ、とエラーになることに気づいた。なるほどそういうことか、ということで

yum provides libstdc++.so.5

としたところ、compat-libstdc++-33 i386 というのが見つかったのでこれを

yum install compat-libstdc++-33.i386

したところ無事動作した。

[Ruby] Ruby-GetText-Package-1.10.0

リリースしました。今回の目玉は高速化です。

まず、_(), n_()で、一度変換された文字列が言語毎にメモリ上にキャッシュされるようになりました。

繰り返し呼び出される場合(Railsもそうですよね)、2度目以降の呼び出しが(手元の計測では)1.3〜1.8倍程度高速化できました。当然、一画面で複数回メソッドを呼び出すわけですから複雑な画面では更なる高速化が期待できるでしょう。

なお、キャッシュをクリアしたい場合はGetText.clear_cacheメソッドを呼び出します。また、デバッグモード(ruby -d or $DEBUG=true)か明示的にGetText.cached = falseをしておくとキャッシュされません。Ruby on Railsの場合はdevelopmentモードではこのフラグがfalseで起動されますので、サーバを起動したままmoファイルをアップデートした場合、今まで同様反映されます(されなかったらバグ(^^;))。

次に、Ruby on Railsのキャッシュ(Action/Fragment)に対応しました。これはこの前のエントリを参照してください。これを使うと結構Railsアプリの高速化を図れると思います。

もう一つ、GetText.ns_()というメソッドを追加しました。これはGetText.s_, n_の合体版です。

以下のようにして使います。

ns_("Foo|There is an apple", "There are %{num} apples", i) % {:num => i}

ns_("Bar|There is an apple", "There are %{num} apples", i) % {:num => i}

どちらも英語の場合は、"There is an apple", "There are %{num} apples"に展開されます。

なお、この"Foo"、"Bar"は単数の方にだけつければ良いです。

#まぁ、ここまで使い分けたいということはそうそう無いと思いますが。

あと、これはもともとRuby-GetTextのバグでGNU Gettextに合わせただけなのですが、

(1) _("a")

(2) n_("a", "b", i)

(3) n_("a", "c", i)

の3種類があった場合の挙動が変わりました。まず、(1), (2)があったとすると、これはmsgid = "a", msgid_plural = "b"の1つの組になり、(1)は(2)の単数形と同じ翻訳文字列が使われます。

次に、(2), (3)があったとすると、まず、rgettextではWarningが出て(2)のみが翻訳対象になり、(3)はpoファイルに出てきません。"c"に該当する場合でも"b"であるかのように変換されます。なので、簡単に言えばこのような使い方はNGです。もし、どうしても、aの部分は同じで、b, cの部分を使い分けたい、という場合は前述したns_()を使うようにしてください。

その他の変更点ですが

・ベトナム語、ボスニア語、クロアチア語、ノルウェー語の追加

・error_messages_forが複数のモデルを引数にとることができるようになった

・各種不具合の修正

です。あー疲れた。

ぜひ、使ってみてくださいね!

おっと書き忘れ。Windowsの場合、msgmergeをインストールするために、今まではgladewin32をインストールすることをお勧めしてたんだけど、これからはRuby-GNOME2 Win32 GUI InstallerをインストールすればOK。Ruby-GetTextもRuby-GNOME2も両方楽しめてお買い得(ホントは無料です(^^;))。


July 11, 2007 [長年日記]

[Ruby] Amrita2のビューをGetTextでL10n化

by essaさん。普通にViewで文章を書いていけば(特に意識せずに)ローカライズができる、というもの。テンプレートエンジン自体がL10nをまともにサポートするって実はあまり知らないんだけど、これはすごいね。

例えば、英語のアプリがAmrita2で書かれていたとすると、開発者本人は意図していなかったとしても外部の人が勝手に(本体に手を入れることなく)日本語化できてしまう(文章のローカライズだけだしerb使ってるところは_("")しないといけないとか多少の制限はあるけど)。

こういうのを見てしまうと、erbで(ローカライズのためだけに)<%= _('foo') %>と書くのはちょっと美しくないよなぁ、と思ってしまう。

(1)最初は一つの言語でガリガリ書いてしまって(2)後で一つずつ文字列を_("")で囲んでいく、っていう流れで開発していた面倒くさがりやさんにとっては、Amrita2では(2)の作業がいらなくなる分、楽できるよね。

というわけでAmrita2の今後の展開に期待!

蛇足だけど、Ruby-GetTextでは文字列抽出時、標準のパーサ(ruby, erb, activerecord, glade2)の他に自分でパーサを定義できる

おそらくAmrita2ではこの機能を使っていただいていると思われるんだけど(まだソースが未公開なのかな。RubyForgeのAnonymous CVSからは取得できなかったのでまだ未確認)、我ながらこの機能の拡張性はすばらしいと思う。本家(GNU Gettext)のxgettextよりも使い勝手が良いかもしれない(自画自賛(苦笑))。

#ここまで言っておいて違ったりして。その時はスルーの方向で(苦笑)。


July 14, 2007 [長年日記]

[Ruby] Ruby-GetText-Packageについての誤解

暇だったのでちとリファラを辿ってRuby-GetText-Packageについて言及しているブログ等をいくつか拝見させていただきました。

まだまだ誤解している人もいる模様ですが、とりあえず2つほどこちらに書いておきます。FAQ扱いかな。

知り合いに似たような誤解をしている人がいたら是非指摘してあげてくださいまし。

1. Shift_JISで出力したい場合でも、rails.poを手作業でShift_JISに変換する必要はありません

出力文字コードはrails.po内の文字コードに依存しません。環境に応じた文字コードで出力されます.これはGetTextのウリの一つです。

Railsの場合、init_gettextで

init_gettext "foo", :charset => "Shift_JIS"

とさえすれば、Shift_JISで出力されるようになります。
ちなみに、app/views/layouts/foo.rbのheadタグのcontent-type指定は以下のように変更すると良いでしょう。

<meta http-equiv="content-type" content="text/html;charset=<%= GetText.output_charset %>" />

ここはShift_JISと固定で書いてもOK。

ViewについてはShift_JISでベタ書きしても良いのですが、GetTextの_(), n_()等を使ってpoファイルを使うようにすればこちらも同様に出力文字コードが変換されます。

Modelのカラム名等も変換されます。

注意する必要があるのはDBの文字コードをShift_JISにするということですね。

蛇足ですが、もし、DBの文字コードをUTF-8にして出力だけをShift_JISにしたい場合は、hメソッドをオーバーライドすれば大方大丈夫なような気がします(未確認)。

どこかに以下のコードを追加。どこが良いのかな。application.rbあたり?
alias orig_h h
def h(str)
  begin
    ret = Iconv.iconv(GetText.output_charset, "UTF-8", str).join
  rescue Iconv::Failure
    #異常処理
  end
  ret orig_h(str)
end

でも、まぁ、Shift_JISを使いたいというのはそれなりの理由があるでしょうし、毎回変換かけると性能に影響もあるでしょうからやっぱりDBの文字コードは出力文字コードに合わせた方がよいのではないかな、と思います(Ruby-GetTextの場合、適切な文字コードに変換された後の文字列がキャッシュされるので変換による性能上の影響はほとんどありません)。

2. GNU Gettextが入っていない環境ではrmsgmergeを使う

rmsgmergeは確かにRuby-GetText-Packageが提供するmsgmergeの代替品ではあります。が、fuzzyに対応していない等、機能的にかなり劣っていますので、少なくとも現時点では極力GNU Gettextを入れるべきです。通常の環境ではGNU Gettextのバイナリをすぐに用意できると思います。Windowsの人は是非、Ruby-GNOME2 Win32 GUI Installerをインストールしてください。GNU GettextだけではなくRuby-GNOME2もおまけでついてくるという優れものです(いや、本当はGNU Gettextの方がおまけなんですが(苦笑))。

以前、これについてはもう少し詳しく書いたのでこちらを参照してください。


July 21, 2007 [長年日記]

[] OSS開発者はSが多いかMが多いか

この前、受注アプリ開発のSI/SE/PGって基本的にMじゃないとやってられないよね(なぜかという詳細は略)、という話をして内輪で盛り上がった。

その時はなるほどなぁ、と納得。

で、OSSの開発者はそれで生活していない分(してる人もいるかもしれないけど)さらに、ってか、つまり、ドM向きなのではないだろうか。

この考察が正しいとすると、オレは仕事も趣味も実は向いていないことばかりをやっているのかもしれない(苦笑)。


July 28, 2007 [長年日記]

[Ruby] hash.keys.include?とhash.has_key?

Ruby-GetText-Package-1.10.0で実施したメッセージキャッシュの実装の中に、キャッシュがあるかないかの判定でhash.has_key?(key)と書けば良いところをhash.keys.include?(key)と書いてあるところが2ヶ所あり、これがパフォーマンス(レポートではCPU使用率)にかなり影響を与えているというレポートがあった

もし、同様の問題を抱えている人がいたら、Ruby-GetText-PackageのCVS版を使うか、lib/gettext.rbでkeys.include?と記述しているところをhas_key?と置換してみてほしい。

実際にベンチマークをとってみると相当差がある。毎回配列生成するんだからそりゃそうだよなぁ。なんでkeys.include?なんて書いたんだろ(^^;)。

require 'benchmark'
 
hash = {}
5000.times{|i| hash[i] = "foo#{i}"}
 
Benchmark.bm(20){|x|
  x.report("hash.keys.include?"){ 10000.times{|i|
    hash.keys.include?(i)
  } }
  x.report("hash.has_key?"){ 10000.times{|i|
    hash.has_key?(i)
  } }
}

結果

% ruby test.rb
                          user     system      total        real
hash.keys.include?    5.230000   0.000000   5.230000 (  5.232516)
hash.has_key?         0.000000   0.000000   0.000000 (  0.003292)

そうそう、レポート先では実際にRuby-GetText-Packageを使ってそれなりの規模のサイトを構築・運用してるらしい。実際に使ってくれてるって聞くとやっぱりうれしいよね。