Railsでモデルのバージョン管理を行うというと、 acts_as_versionedを使うのが一般的でしたが、 しばらくメンテナンスされていないのと、githubでforkがカオス状態になっていて、公式ドキュメントと実装が一致しなくなっているなど、ちょっと使いにくい状況になっているようなので、それ以外の選択肢を調べてみました。

  • version_fu かなりシンプルな作り。ソースは100行程度。そのぶん、Versionedテーブルの作成などは自動でやってくれない。
  • SimplyVersioned こちらもシンプル路線。Versionedテーブルを使わずに、1つのテーブルでhas_manyアソシエーションを使って管理するタイプ。validates_uniqueness_ofとかを使っていないならこれでも良いかな。
  • acts_as_versioned_association aavでassociationを含むモデルのバージョニングが出来なかった問題を解決したものらしい。
posted by Png genki on Wed 1 Apr 2009 at 11:51

s21gブログをRails-2.2.2からRails-2.3.2に対応させました。 主に以下のようなところで作業が発生しました。

  • session :off が不要になったので削除
  • application.rb ⇒ application_controller.rb
  • Test::Unit::TestCase から ActiveSupport::TestCaseに
    • test_helper.rbの中の定義もActiveSupport::TestCaseにする
  • formatted_xxx_url(:foo)をxxx_url(:format => :foo)に
posted by Png genki on Sat 28 Mar 2009 at 23:55

Rails-2.2.2で動いているRailsアプリをRails-2.3.2に移行する場合、

   1  % rake rails:update

を行うと便利ですが、これを実行すると、以下のようなファイルが影響を受けます。

   1  # Changed but not updated:
   2  #   (use "git add/rm <file>..." to update what will be committed)
   3  #
   4  #       deleted:    app/controllers/application.rb
   5  #       modified:   config/boot.rb
   6  #       modified:   config/environment.rb
   7  #       modified:   public/javascripts/controls.js
   8  #       modified:   public/javascripts/dragdrop.js
   9  #       modified:   public/javascripts/effects.js
  10  #       modified:   public/javascripts/prototype.js
  11  #
  12  # Untracked files:
  13  #   (use "git add <file>..." to include in what will be committed)
  14  #
  15  #       app/controllers/application_controller.rb
  16  no changes added to commit (use "git add" and/or "git commit -a")

ApplicationControllerのファイル名が、application_controller.rbに変更になっています。一貫性を持たせるためでしょうね。

posted by Png genki on Sat 28 Mar 2009 at 20:46

Passengerは非常に便利なのですが、 通常の設定では、アプリケーションへのアクセスが無い状態が5分程度続くと、プロセスがkillされてしまい、次回にアクセスする時に時間がかかるようになります。

これを回避するためには、httpd.confなどで、以下のように指定します。

   1  PassengerPoolIdleTime 0

これで、プロセス数の限界に達して追い出されるまで、Idle時間によってkillされる事は無くなります。

See Also

posted by Png genki on Sat 28 Mar 2009 at 20:36

待っていたものが登場したようです。

Rails Searchable API Doc

ss

上記は Rails-2.3.2のSearchable API Docです。 RailsのAPIを、class, method等を横断してLive Searchできるようです。

MerbのAPIドキュメント と比べても、勝るとも劣らない仕上がりですね。 オフライン状態でも使えるようにダウンロードできるバージョンも用意されている模様。

posted by Png genki on Sat 28 Mar 2009 at 18:23

会社設立以来ほとんど更新していなかったのですが、 iPhoneアプリ等の開発で製品を紹介するページを置く場所が必要になったので、 Rails-2.3.2がリリースされた事もあり、I18nを使って国際化仕様のサイトを作りました。

http://ja.www.s21g.com/ 日本語 ss1

http://en.www.s21g.com/ 英語 ss2

従来通り、http://www.s21g.com/にアクセスすると、ブラウザの設定に従って、自動的に日本語か英語のサイトに振り分けられます。 localizationファイルをもっと用意すれば、ISOで定義されてる言語なら何語でも大丈夫なのですが、とりあえずはjaとenのみサポートです。

I18nの使い方

ローカライズファイルは、デフォルトではconfig/locales/*.ymlに配置します。 mutohさんの、 localelocale_rails を使うと色々と便利です。 EdgeバージョンはRails-2.3.2でも利用出来ました。

自動的にロケールを判別するために、以下のようなコードを ApplicationControllerに書きました。

   1  class ApplicationController < ActionController::Base
   2    before_filter :set_locale
   3  
   4    (..snip..)
   5  
   6    def set_locale
   7      I18n.locale = @original_locale = fallback_locale(I18n.locale)
   8      if request.host =~ /^([\w-]+)\.#{HOST.split(':')[0]}/i
   9        I18n.locale = fallback_locale($1)
  10      end
  11    end
  12  
  13  private
  14    def fallback_locale(locale)
  15      locale = locale.to_s
  16      @available_locales ||= Set.new(I18n.available_locales)
  17      until locale.empty? || @available_locales.include?(locale.intern)
  18        locale = locale.split(/([-_])/)[0..-3].join
  19      end
  20      locale.present? ? locale.intern : I18n.default_locale
  21    end

実際にローカライズする手順は、以下のような感じになります。

文字列のローカライズ

*.ymlファイルで定義されているメッセージキーに対して、

   1  ja:
   2    "hello": "こんにちわ"

こんな感じにt(...)ヘルパーメソッドを使います。

   1    t("hello") #=> "こんにちわ"

時刻などのローカライズ

時刻の場合もほぼ同様で、以下のような*.ymlファイルで定義されている設定に対応して、

   1  ja:
   2    date:
   3      formats:
   4        default: "%Y/%m/%d"
   5        short: "%m/%d"
   6        long: "%Y年%m月%d日(%a)"

以下のようにl(...)ヘルパーメソッドを使ってローカライズします。

   1  l(Date.today, :format => :short) #=> "03/22"

テンプレートのローカライズ

Viewテンプレートを丸ごとローカライズする場合は、

  • top/index.ja.html.erb
  • top/index.en.html.erb

のようなファイル名すればOKです。

posted by Png genki on Sun 22 Mar 2009 at 10:04

UL/LIタグを使ってリストを表示するときに、 コレクションが空の場合はULタグを表示したくない場合というのが頻繁にあります。

   1  <% if @posts.present? %>
   2  <ul>
   3    <% @posts.each do |post| %>
   4    <li><%= h(@post.body) %></li>
   5    <% end %>
   6  </ul>
   7  <% end %>

そういう時は、だいたいこんな感じにコードを書きます。 しかし、条件が複雑になってきたり、複数のコレクションを考えなければ行けない場合に、きれいに記述出来なくなってきます。 そんな時は、以下のようなヘルパを使って、分岐条件を遅延評価するようにすると、処理が簡潔になります。

   1  module ApplicationHelper
   2    def delayed_if(&block)
   3      flag = Object.new
   4      def flag.set; @value = true end
   5      def flag.reset; @value = false end
   6      result = capture(&proc{block.call(flag)})
   7      concat(result) if flag.instance_variable_get(:@value)
   8    end

利用法

   1  <% delayed_if do |flag| %>
   2  <ul>
   3    <% @posts.each do |post| %>
   4    <li><%= h(@post.body) %></li>
   5    <% flag.set %>
   6    <% end %>
   7  </ul>
   8  <% end %>

flag.setがよばれた時だけ、delayed_ifのブロックが表示されます。 この例だと単純すぎてあまり恩恵が分かりにくいですが、 複数のコレクションを一つのULで表示する場合などでも簡潔に記述出来るようになります。

posted by Png genki on Sat 21 Mar 2009 at 22:45

リソースを新規に作成する場合に、既存のものを再利用して作成したい場合は良くあると思います。 そんな時は、newアクションで:idを受け取れるようにして、 以下のようにすると、簡単に実現出来ます。

   1    def new
   2      @post = Post.new
   3      @post.attributes = Post.find(params[:id]).attributes if params[:id]
   4      @posts = Post.for_user(current_user).all
   5    end

そしてposts/new.html.erbの中で

   1  <% if @posts.present? %>
   2  <form action=<%= new_post_path %> method="GET">
   3    <%= select_tag :id, options_for_select(@posts.map{|i| [i.title, i.id]}) %>
   4    <%= submit_tag '読み込む' %>
   5  </form>
   6  <% end %>

こんな感じに、テンプレートの読み込みフォームを作ります。 これで完了です。 Ajaxで posts/new/1 のようなURLにGETで遷移するようにした方が格好がいいかもしれません。

newアクションが:idで指定したリソースをテンプレートとして利用して新規にリソースを作成するというのは、標準的な挙動になっても良い気がします。

posted by Png genki on Sat 21 Mar 2009 at 13:47

久々にRailsモードが続いています。 APIを眺めていたら便利そうな機能を見つけたので紹介します。

layouts/application.html.erbの中などで、

   1  <%= javascript_include_tag :defaults %>

のように書く事があると思いますが、この:defaults というシンボルを指定することで、あらかじめ登録されている expansionが展開されてincludeされます。 この:defaultsのようなものを自分で登録したい場合、

   1  ActionView::Helpers::AssetTagHelper.register_javascript_expansion :foo => ["bar", "baz"]

のようにconfig/initializers/*あたりで登録しておけばOKです。 呼び出す時は

   1  <%= javascript_include_tag :foo %>

でOK。プラグインを作るとき等に、複数のjsフィアルをまとめてincludeできるようにしておくと便利ですね。

スタイルシートの場合は、register_stylesheet_expansionという同様のメソッドを使います。

posted by Png genki on Thu 19 Mar 2009 at 11:04

昨日の時点でTwitterでは話題になっていましたが、 正式にRails-2.3.2のリリースがアナウンスされたようです。

Rails 2.3: Templates, Engines, Rack, Metal, much more!

Rails 2.3 is finally done and out the door. This is one of the most substantial upgrades to Rails in a very long time.

今回のリリースの主な特徴は、

  • Templates: 自分好みのRailsアプリケーションのひな形を作る機能。 沢山Railsアプリを作ってる人には便利そう。
  • Engines: Railsアプリをコンポーネント化して再利用する仕組み。MerbのSliceのような印象。render_componentが無くなったので、代わりにこれを使うと良いらしい。
  • Rack: Rackに対応。
  • Metal: 色々と省略して高速なレスポンスを実現する仕組み。
  • Nested forms: 頭痛への処方箋。かなり嬉しい。

全体的な印象として、Merb-1.1との差が少なくなってきた感じですね。 Rails-3(あるいはMerb-2)への道筋が見えてきた気がします。

See Also

posted by Png genki on Tue 17 Mar 2009 at 09:30