2011年12月28日水曜日

[Rails3] 国際化 I18n のまとめ(その2:ロケールの切り替え)

実行環境:
ruby 1.9.3
Rails 3.1.3
I18n
文字列を国際化する I18n の使い方のまとめです。
今回は、その2としてロケールの切り替えについて考えてみたいと思います。

表示ロケールを決定する流れ

複数のロケールを定義している場合、表示するロケールがアクセス毎に判断することになります。それをどうやって(どの情報を使って)判断するか、というお話です。
  1. ロケール値が明示的に指定されていたら、そのロケールで表示
    • ロケール値を URL のクエリ(パラメータ)で指定
    • ロケール値を URL のパス名で指定(実態はクエリと同じ)
    • ロケール値をセッション情報で指定
  2. アクセス先のURLのドメイン名で判別(複数のドメイン名で運用している場合のみ)
    • TLD(Top Level Domain)で判別
    • サブドメインで判別
  3. 接続元の情報に応じて、適切なロケールで表示
    • HTTP_ACCEPT_LANGUAGEから判断
    • IPアドレスから判断
  4. (とりあえず)デフォルトのロケール(config.I18n.default_locale)で表示

1.〜4.の方法を適当に組み合わせてやります。(4.は方法と呼べるものでもないが。) ミニマムにやるなら 1. と 4. 、ちゃんとやるんなら1. と 3. と 4.の組み合わせが一般的だと思います。 2. は「yoursite.com と yoursite.jp」(TLDで判別)、あるいは「ja.yoursite.com と fr.yousite.com] (サブドメインで判別) など複数の(サブ)ドメイン名で運用している場合のみ可能な技です。
# 純日本語なサイトでロケールを全く切り替えないのであれば、当然のことながら I18n.locale の値をデフォルト(config.I18n.default_locale)のまま変更しなくてOKです。

ロケールの切り替えのざっくりイメージは
  • ミニマムにやる場合(1. と 4.)
     - サイトに最初にアクセスするとデフォルトのロケールで表示
     - 途中で日本語での表示に変更したいユーザーは日の丸アイコンをクリック
    という感じ
  • ちゃんとやる場合(1. と 3. と 4.)
     - 接続元の情報から定義済みのロケールがあれば、そのロケールを使用して表示
     - なければデフォルトのロケールで表示
     - ユーザーが別の表示に変更したい場合は、表示したい言語の国旗アイコンをクリック
    という感じ


ロケール値の受け渡し

複数のロケールをユーザーが切り替えられるようにするには、同じユーザーの連続したアクセス(同一セッション)ではロケール値を引き継げないといけません。じゃないと、一度ロケールを切り替えてもアクセスする度にロケールの設定が元に戻ってしまうことになります。

ロケール値の受け渡しの方法のあれこれ(もないけど…)
session情報(または cookie)でロケール値を受け渡す
現在のロケール値を session 情報に格納し、以降のアクセスについては session 情報から取り出して使用します。
ロケール値が切り替わった時に session 情報に格納
session[:locale] = I18n.locale
アクセスの度に session 情報から locale 値をセットする
#{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base before_filter :restore_locale private def restore_locale I18n.locale = session[:locale] if session[:locale] end end
同一セッション内で共有する情報なので、session 情報としてとっておく、というのはストレートな考え方です。やり方も簡単。 でもあまり推奨されないらしいです(^^;
RESTful な考え方だと同じURLでアクセスしたら基本的に表示される内容は同じであるべき、ってことらしい。
# 個人的には「ま、いいんじゃない?」って気がするけど。
URLのクエリのパラメータとして毎回ロケール値を渡す
基本となるシンプルな考え方です。
アクセスする時のイメージはこんな感じ
URL の例
http://www.yoursite.com/topics?locale=ja
アクセスの度に params から locale 値をセットする #{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base before_filter :restore_locale private def restore_locale I18n.locale = params[:locale] if params[:locale] end end
URLのパラメータで渡すということは、別のページに移動したりする際に毎回 locale の値をパラメータに含めてあげる必要があります。
#{RAILS.ROOT}/app/views/people/show.html.erb
<%= link_to people_path(:locale => I18n.locale) %>
と、こんな感じでいけるでしょう。
ですが、これをすべてのリンクに追加してまわるのはちょっとイケてないので、常にパラメータに locale を追加してもらえるように ApplicationController で設定をします。
#{#RAILS.RPPT}/app/controllers/application_controller.rb
def url_options { :locale => I18n.locale }.merge(super) end
これでOKです。

でも URL に毎回「?locale=ja」とかくっついてあんまし格好よくなくね?と思った方は、もう一手間を加えて URL のパスの一部としてロケール値を渡すようにしましょう(次項へ続く)
URLのパスの一部でロケール値を渡す(実質的にはクエリで渡すのと同じ)
前項のパラメータでロケール値を渡すの続きです。Rails3の強力なルーティングの力を借りてクエリの形をしているパラメータをパスの一部の形に変えることができます。config/routes.rb をちょいと変更するだけです。

ルーティング変更前↓
#{RAILS.ROOT}/config/routes.rb
resources :people
ルーティング変更後↓
#{RAILS.ROOT}/config/routes.rb
scope "/:locale" do resources :people end
これで URL が以下のように変わります。
URL変更前↓
http://yoursite.com/people?locale=ja
URL変更後↓
http://yousite.com/ja/people
ルーティングの設定は、アクセスされたURLの解析だけでなく、Railsアプリケーション内でリンクを作る場合にも使用されるので <%= link_to 'People', people_path %> で作成されるパスも自動的にかわります(めちゃ便利!)

これで完璧ですぅ(^^)

ドメイン名(サブドメイン名)からのロケールの判定

ホスト名の一部からロケールとして使用する文字列を抜き出します。
TLD名からロケールを判定
#{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base before_filter :set_locale private def set_locale I18n.locale = extrace_locale_from_tld end def extract_locale_from_tld request.domain.split('.').last end end
サブドメイン名からロケールを判定
#{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base before_filter :set_locale private def set_locale I18n.locale = extrace_locale_from_subdomain end def extract_locale_from_subdomain request.subdomains.first end end
こんな感じでやればOKです。とりあえず引っ張ってきた文字列のロケールはRailsアプリケーション内に用意されているという前提です。そこんとこをチェックする場合は次項参照。

接続元に応じたロケールの判断

接続元の情報ですので HTTP_REQUEST の内容で判断します。
HTTP_ACCEPT_LANGUAGE で判断
HTTP_REQUEST の中の HTTP_ACCEPT_LANGUAGE の最初の言語名(=最初の2文字)をとってきます。
HTTP_ACCEPT_LANGUAGE にはカンマで区切って複数の言語が優先度とともに書いてあるので、優先度の高い順にロケールの準備の有り/無しを判定しながら決めていくのが筋のような気がしますが、ここでは省略。
#{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base before_filter :set_locale private def set_locale extracted_locale = extract_locale_from_accept_language I18n.locale = (I18n::available_locales.include? extracted_locale.to_sym) ? extracted_locale : I18n.default_locale end def extract_locale_from_accept_language request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first end end
I18n::available_locales 配列の中に定義済みのロケールのリストがあるので、そこと比較して定義されていればそのロケールを、なければデフォルトロケールを使用します。
IP アドレスで判断
HTTP_REQUEST の中のリモートIPアドレスをとってきます。
IPアドレスから国を判断する必要がありますが、MaxMind 社の GeoIP ライブラリが使えます。 ruby のライブラリや Apache モジュール(mod_geoip)、Gem も複数存在するようなので好みの方法を使用してください。
以下は cjheath/geoip の Gem を使ったサンプルです。
#{RAILS.ROOT}/Gemfile
gem 'geoip'
#{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base require 'geoip' before_filter :set_locale private def set_locale extracted_locale = extract_locale_from_ip I18n.locale = (I18n::available_locales.include? extracted_locale.to_sym) ? extracted_locale : I18n.default_locale end def extract_locale_from_ip geoip ||= GeoIP.new(Rails.root.join(“lib/GeoIP.dat”)) country_location = @geoip.country(request.remote_ip) country_location.country_code2.downcase end end


まとめ(=全部盛り)

ロケールの判定を上記の諸々をまとめてやる場合は以下のように書けばよいかと。上で説明したきたロケールの取得方法をそれぞれ準備してあげて、優先順位を決めて1つめの方法で取得できなければ、次の方法、次の方法…といった感じで extracted_locale 変数にセットします。取得したロケールが定義済みロケールのリスト(I18n::available_locales)にあるかどうかをチェックし、なければデフォルトを使う、という流れです。
#{RAILS.ROOT}/app/controller/application_controller.rb
ApplicationController < ActionController::Base require 'geoip' before_filter :set_locale private def set_locale extracted_locale = params[:locale] || extract_locale_from_subdomain || extract_locale_from_tld || extract_locale_from_accept_language || extract_locale_from_ip I18n.locale = (I18n::available_locales.include? extracted_locale.to_sym) ? extracted_locale : I18n.default_locale end def extract_locale_from_subdomain request.subdomains.first end def extract_locale_from_tld request.domain.split('.').last end def extract_locale_from_accept_language request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first end def extract_locale_from_ip geoip ||= GeoIP.new(Rails.root.join(“lib/GeoIP.dat”)) country_location = @geoip.country(request.remote_ip) country_location.country_code2.downcase end end
必要のないものは抜いてくださいまし。

2011年12月27日火曜日

[Rails3] 国際化 I18n のまとめ(その1:準備編)

実行環境:
ruby 1.9.3
Rails 3.1.3
I18n
文字列を国際化する I18n の使い方のまとめです。
ここでいう国際化とは、要するに複数言語表示対応という意味です。
※ちなみに I18n というのは、I で始まり、間に18文字を挟んで、nで終わる文字列という意味で Internationalization の略称です。

「おらぁ日本語しか使わねーから関係ねえだよ」と思う方もいるかと思いますが、View 用の日本語文字列をコードと切り離しておく場合、I18n の枠組みに乗っかると便利です。

今回は、その1として I18n を使った国際化の大まかなイメージを説明しつつ、日本語ロケールを使えるように設定します。

ロケールとは使用する言語の設定であり、1つの Rails アプリケーションの中で英語(:en)、日本語(:ja)、スペイン語(:es)などなど複数のロケールを定義することができます。

表示する時点のロケール値(I18n.locale)によって表示する言語を切り替える仕組みになっています。I18.locale の値が :en なら英語表記、I18n.locale の値が :ja なら日本語表記で表示される、という具合です。
動作確認のテスト1
適当な scaffold を作って(ここでは book とします)、View の中で以下のように書いてみて下さい。
#{RAILS.ROOT}/app/views/books/index.html.erb
<%=t :hello %>
I18n が働いていれば
hello world
と表示されるはずです。
なぜこうなるかと言えば、既に #{RAILS.ROOT}/config/locales/en.yml の中で「hello: hello world」と書かれているからです。
#{RAILS.ROOT}/config/locales/en.yml
en: hello: hello world
I18n.translation というのがキーと値の対応付けをしてくれるメソッドで :hello(もしくは 'hello')を渡すと 'hello world' を返します。
t というヘルパーメソッドが、この I18n のメソッドの別名として定義されています。

en.yml は辞書ファイルと呼ばれ、このファイルの中にロケールの定義を書いていきます。
通常、ファイル名のベースネーム部分(拡張子より前の部分)はロケールを表すアルファベット2文字にします。日本語なら ja.yml、英語なら en.yml、ドイツ語なら de.yml、フランス語なら fr.yml という具合です。こうすると1つのファイルには1つのロケールについての定義を記載し、複数のロケールについて定義する場合はそれぞれのロケール名に応じたファイルを作成することになります。
※実際にはファイル名は何でもよいです。複数のロケールの定義を1つのファイルにまとめて書いてもOK。どのロケールの定義かは、ファイル名ではなくymlファイルの中身(ハッシュ定義の最上位のキー)で判断されます。どのファイルを読み込むかも「*.yml」って感じで設定されているので拡張子さえあっていればなんでもいいのです。

デフォルトロケールの設定

I18n は表示をする際にその時のロケールの値、I18n.locale の値をみて表示するロケールを判断します。 ロケール値はコードの中で明示的に変更しない限り、デフォルトの値が使用されます。 ロケールのデフォルトは通常 :en、つまり英語になっています。 そのため上記のテスト1ではロケール値が :en となり en.yml の中で設定した内容が表示されたわけです。
動作確認のテスト2
先ほどのテスト1と同じファイルで日本語ロケールのテストをしてみましょう。 まず en.yml を同じディレクトリにコピーして ja.yml という名前にし、テキストを以下のように変更します。
#{RAILS.ROOT}/config/locales/ja.yml
ja: hello: こんにちは、世界
続いて View ファイルを編集します。
#{RAILS.ROOT}/app/views/books/index.html.erb
<%= "[%s]:"%(I18n.locale.to_s) > <%=t :hello %> <br /> <% I18n.locale = :ja > <%= "[%s]:"%(I18n.locale.to_s) > <%=t :hello %>
サーバを再起動してから実行します。(辞書ファイルを更新した場合はサーバの再起動が必要です)
[en]hello world
[ja]こんにちは、世界
と表示されたでしょうか?このようにロケール値を変えることで表示する文字列を変えるわけです。

日本語をメインで使用するので日本語をデフォルトのロケールに設定したいと思います。config/application.rb を下記のように編集します。
#{RAILS.ROOT}/config/application.rb
config.i18n.default_locale = :ja
これでデフォルトのロケールが日本語(:ja)になりました。


辞書の置き場所

辞書ファイルはたいてい各ロケールごとにymlファイルを作成します。まじめに国際化をしようとすると1ファイル内に書くと定義する項目が多くなってきて見通しが悪くなりますので、1つのロケールの定義を複数のファイルに分割したりします。ディレクトリをきってロケールの分割された辞書ファイルを格納することも可能です。
#{RAISL.ROOT}/config/locales/ 以下のディレクトリ構成(例1)
[locales] |-- defaults.en.yml |-- defaults.ja.yml |-- models.ja.yml |-- models.en.yml |-- views.ja.yml |-- views.en.yml
#{RAISL.ROOT}/config/locales/ 以下のディレクトリ構成(例2)
[locales] |-- [defaults] | |-- en.yml | |-- ja.yml |-- [models] | |-- [defaults] | | |-- en.yml | | |-- ja.yml | |-- [users] | | |-- en.yml | | |-- ja.yml | |-- [books] | | |-- en.yml | | |-- ja.yml |-- [views] |-- [defaults] | |-- en.yml | |-- ja.yml |-- [users] | |-- en.yml | |-- ja.yml |-- [books] |-- en.yml |-- ja.yml
辞書ファイルの格納場所は標準では #{RAISL.ROOT}/config/locales/ の直下となっていて locales 以下のサブディレクトリの中までは読んでくれません。locales 以下のサブディレクトリの中までをすべて読むように設定します。
変更前
#{RAILS.ROOT}/config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')]
変更後
#{RAILS.ROOT}/config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
これで準備完了です。

辞書ファイルの中身についてはその3以降でまとめていく予定です。

2011年12月26日月曜日

[Rails3] コードに日本語を書くとエラーになる件

実行環境:
ruby 1.9.3
Rails 3.1.3
Controller や Helper など ruby のコードの中に日本語を書くと
invalid multibyte char (US-ASCII)
というようなエラーが出ます。

変数名やメソッド名だけでなく、文字列として埋め込んでもダメです。
うっかりデバッグ用に日本語を埋め込むとはじかれてしまいます。

このエラーを回避する方法は、日本語を書いたファイルの先頭に
# encoding: utf-8
もしくは
# -*- encoding: UTF-8 -*-
というおまじないを書けばOKです。

※Rails2 の時は大丈夫だったのに… というか ruby 1.9.x になってからか。

ちなみに、コメント文であれば日本語問題なく通ります。
View のファイルの中(HTMLなパート)も大丈夫です。


ま、そもそもコードの中にがっつり固定文字列を埋め込むのはよろしくないので、 「i18n 化をきちんとやりなさい」というありがたい天のお告げだと思うことにします。(^^)

[Rails3] I18n による日本語化の最低限の設定

実行環境:
ruby 1.9.3
Rails 3.1.3
I18n
日本語でサイトを作る場合、目に見えるところという意味でViewのhtmlファイル(のテンプレート)はさささっと日本語にしたいと思うわけですが、モデル1つについて index ビューやら show ビューやらがあって全部日本語にしていかないといけません。頑張って直したとしても、エラーメッセージが英語で出てきたりします。Railsに標準で備わっているI18nの機能を使って、モデルの属性名を日本語化(日本語名を1箇所で設定)できる(DRYの原則)し、同じものなのにページによって呼び方が違うみたいなことも防げます。エラーメッセージも日本語化することができます。

サクっと日本語化する
手っ取り早く日本語化の設定をする場合、デフォルトのRailsアプリに以下の2つの変更を加えます。日時表現やエラーメッセージが日本語化できます。

デフォルトロケールを日本語にする
config/application.rbの中に以下を追記します。ちなみにこの設定をしないと標準では英語になっています。
#{RAILS.ROOT}/config/application.rb
config.i18n.default_locale = :ja

日本語の辞書ファイルを頂いてくる
標準的な日本語の辞書ファイルを公開されていらっしゃる方がいるので、それをありがたく頂戴しちゃいます。ダウンロードしたファイルは /config/locales/ 直下に置きます。
#{RAISL.ROOT}/config/locales/
[locales] |-- en.yml # もともとあるファイル |-- ja.yml # 頂戴してきたファイル


これで準備完了です。あとは個別のビューやモデルを日本語化していきます。

2011年12月25日日曜日

[Rails3] コントローラ名から対応するモデルオブジェクトを取得する

実行環境:
ruby 1.9.3
Rails 3.1.3
コントローラ名からモデル名を取得します。
#{RAILS.ROOT}/app/controllers/users_controller.rb
class UsersController < ActionController::Base
  def index
    @model_name = controller_name.classify           # モデル名の取得
    @all = @model_name.constantize.find(:all)        # モデルオブジェクトの取得
  end
end
コントローラ名を classify するとモデル名が得られます。
モデル名を constantize するとモデルオブジェクトが得られます。

ただし、これは Rails の規約に則って推測しているだけです。
つまり「規約に則って作られていれば対応する名前はこの名前でしょ?」という具合です。 実際にモデルオブジェクトが取得できたかどうかを判定してあげる必要があるかも。

2011年12月24日土曜日

[Rails3] Oracle DB と SQlite3 を併用した場合にエラー?

実行環境:
ruby 1.9.3
Rails 3.1.3
sqlite3 1.3.5
ruby-oci8 2.0.6
activerecord-oracle_enhanced-adapter 1.3.2, 1.4.0
1つのRailsアプリの中で SQlite3 DB と共にレガシーな Oracle DB を使おうとしてちょっとはまったのでメモ。

SQlite3 DBでアクセス(create)しようとすると、以下のようなエラーを頂戴いたしました。
undefined method `virtual?' for #<ActiveRecord::ConnectionAdapters::SQLiteColumn:0x1a477b8>

github に Connection to multiple database なんて書き込みがあり、先人がいた模様。
activerecord-oracle_enhanced-adapter 1.3.2では大丈夫だったのに 1.4.0 にあげたらエラーになった、ということなので、 1.3.2 に落としてみると SQlite3 DB への create も無事にOKになりました。


が、ここで新たな問題が発生。

set_primary_key を設定している Oracle DB のモデルに対して .find(params[:id]) とやると、
OCIError: ORA-01036: 変数の名前/数が無効です。: SELECT * FROM (SELECT "USER".* FROM "USER" WHERE "USER"."UID" = ? AND ....
と、引数で与えている id が SQL 文の中で展開されずに「?」のまま残っている模様…

しょうがないので、find ではなく where を使って Rails3 っぽく書き換えてみることに。
Rails2 的な書き方
@user = User.find(params[:id])
これを以下のように書き換える
Rails3 的な書き方
@usr = User.where("uid = ?", params[:id]).first
とりあえず動きました。

set_primary_key の意味があんまりないのでどうかな…
あと、今回は参照オンリーで検証してないので、更新作業とか問題ないのか不明です (^^;;;

※ちなみに 1.4.0 では上側の Rails2 的な書き方で通るので、そっちが正しい動きのような気がします。

2011年12月21日水曜日

[Rails3] ログイン認証の要・不要をコントローラーによって使い分ける

実行環境:
ruby 1.9.3
Rails 3.1.3
ログイン認証を実現する際、認証が必要なコントローラー内に before_filter を書いてコントローラーを実行する前にログイン状態をチェックします。

たいてい、認証が必要なコントローラーが複数あります。それぞれに before_filter とそのメソッドを書くのはダサいので、before_filter を書いたコントローラークラスを作って継承させるのですが、認証させるコントローラーの数が多いと面倒になってきます。

すると「ええぃ、全てのコントローラーの親である ApplicationController に書いてしまえ!」と思うわけですが、逆に認証させたくないコントローラーが1つ2つあると、どうしてやろうか?と悩むことになります。

実現方法は色々あると思いますが、例としていくつかパターンを上げてみたいと思います。

ここでは例として
 ・CustomerController ⇒ 認証が必要(他にもいくつもある、という想定)
 ・LoginController ⇒ 認証が不要
という状況で説明してみようと思います。


① 認証用の before_filter を組み込んだコントローラーを作って継承させる(正攻法その1)
認証用の before_filter を組み込んだコントローラー(ここでは AuthController)を作り、認証が必要なコントローラーには AuthController を継承させます。

継承のイメージ
ApplicationController   ├─→ AuthController → CustomerController   └──────────→ LoginController
このような感じで継承させます。
以下がコードの大枠です。AuthController に認証の before_filter を仕込みます。
#{RAILS_ROOT}/app/controllers/auth_controller.rb
class AuthController < ApplicationController before_filter :login_required private def login_required # ログインしていなければログイン画面へリダイレクト end end
#{RAILS_ROOT}/app/controllers/customer_controller.rb
class CustomerController < AuthController end
#{RAILS_ROOT}/app/controllers/login_controller.rb
class LoginController < ApplicationController end


② 認証用のメソッドだけを ApplicationController で定義し、認証が必要なコントローラーだけで before_filter をセットする(正攻法その2)
before_filter にセットするプライベートメソッド(ここでは login_required)を ApplicationController で定義し、認証が必要なコントローラーには、個々のコードの中で before_filter をセットする。逆に認証不要なコントローラーにはセットしない。

継承のイメージ
ApplicationController   ├─→ CustomerController   └─→ LoginController
継承関係はデフォルトのままです。
以下がコードの大枠です。ApplicationController で認証用のプライベートメソッドのみを定義します。
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base # before_filter のセットは個々のコントロール内に書くのでここには書かない private def login_required # ログインしていなければログイン画面へリダイレクト end end
#{RAILS_ROOT}/app/controllers/customer_controller.rb
class CustomerController < ApplicationController before_filter :login_required # 認証プロセスを踏ませる end
#{RAILS_ROOT}/app/controllers/login_controller.rb
class LoginController < ApplicationController # 認証は不要なので、before_filter のセットはしない end


③ 認証用の before_filter を ApplicationController に組み込み、認証が不要なコントローラー内で before_filter をスキップする

継承のイメージ
ApplicationController   ├─→ CustomerController   └─→ LoginController
このような感じで継承させます。
以下がコードの大枠です。ApplicationController に認証の before_filter を仕込み、LoginController 内で before_filter の適用をキャンセルします。
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_filter :login_required private def login_required # ログインしていなければログイン画面へリダイレクト end end
#{RAILS_ROOT}/app/controllers/customer_controller.rb
class CustomerController < ApplicationController end
#{RAILS_ROOT}/app/controllers/login_controller.rb
class LoginController < ApplicationController skip_before_filter :login_required # before_filter を適用しない end

LoginController 内に skip_before_filter を書いていますが、認証不要なクラスが複数ある場合は、skip_before_filter をつけた NoauthController クラスを作って、LoginController に NoauthController クラスを継承させるといいでしょう。

④ 認証用の before_filter を適用する・しないの条件付きで ApplicationController に組み込む
上記の②と似たようなやり方ですが、before_filter の適用・非適用の条件を ApplicationController 内で設定します。
継承のイメージ
ApplicationController   ├─→ CustomerController   └─→ LoginController
②と同様、このような感じで継承させます。デフォルトのままですね。
以下がコードの大枠です。ApplicationController に認証の before_filter を適用条件をつけて仕込みます。
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_filter :login_required, :if => :auth_target? # auth_target? が true の時のみ適用 private def login_required # ログインしていなければログイン画面へリダイレクト end def auth_target? self.controller_name != 'login' end end
#{RAILS_ROOT}/app/controllers/customer_controller.rb
class CustomerController < ApplicationController end
#{RAILS_ROOT}/app/controllers/login_controller.rb
class LoginController < ApplicationController end
注意点としては、:if => の後ろはメソッドにしないといけません。直接 true や false を書いたり、true/false を返す式を書いてもエラーになります。
:unless も使えます。

before_filter の適用条件については前回まとめてます ⇒ [Rails3] before_filter に条件を設定する


namespace を使って(例えば :adminとか)認証の要る・要らないを分ける、というのもよくやられますね。あくまでも認証の要るもの要らないものを区別しているだけなので、実現するには実質的に上のどれかの方法を使うような形になると思います。

結論としては、めんどくさがりな人(=自分?)には④が楽ちんですが、お勧めは②です。
④と違って各コントローラー内のコードで認証の要る・要らないが判断できるのが良いと思います。

まあ、どういうやり方にするかは後は趣味の問題、かな。

[Rails3] before_filter に条件を設定する

実行環境:
ruby 1.9.3
Rails 3.1.3
ログイン認証なんかを実現する際におなじみの before_filter に適用条件をつけてみようというお話です。

① アクション名で制限をかける
:only、:except オプションを使うと、アクション名で適用条件を設定できます。
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_filter :my_filter1, :only => 'index' before_filter :my_filter2, :except => ['index', 'show'] private def my_filter1 end def my_filter2 end end
親コントローラーに設定する場合は、継承する全てのコントローラーで「参照は誰でも可、更新・削除操作はログインが必要」なんて場合には効果的に使えると思います。


② 自由な条件で制限をかける
:if、:unless オプションを使うと、もっと自由に適用条件を設定できます。
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_filter :my_filter1, :if => :my_condition1? before_filter :my_filter2, :unless => :my_condition2? private def my_filter1 end def my_filter2 end def my_condition1? return true end def my_condition2? return false end end

:if や :unless の後ろはフィルタと同じようにメソッドにする必要があります。単純に true や false、あるいは真偽値を返す式を置いてもエラーではじかれてしまいます。

使い方としては、コントローラー名で判定をしたり、URL のパス文字列で判定したり、というのが一般的だと思います。

例)コントローラー名で判断する場合
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_filter :my_filter, :unless => :my_condition? private def my_filter end def my_condition? self.controller_name == 'login' end end
例)URLのパス部分の文字列で判断する場合
#{RAILS_ROOT}/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_filter :my_filter, :if => :my_condition? private def my_filter end def my_condition? request.path_info.match(/^\/admin\//) end end

2011年12月17日土曜日

[Rails3] モデルにメソッドを追加する

実行環境:
ruby 1.9.3
Rails 3.1.3
Rails3でActiveRecordのモデルにクラスメソッドを追加します。
といっても、モデルだからどうだとかいう話は全然なくて、単純にクラスにメソッドを追加するだけの話なんですが、うっかり忘れてしまったのでメモ。
#{RAILS_ROOT}/app/models/user.rb
class User < ActiveRecord::Base def instance_method # インスタンスメソッド return end def self.class_method # クラスメソッド return end private def private_method # プライベートなインスタンスメソッド return end end
というようにモデルにメソッドを追加します。

クラスに対して使おうとするとこんな感じ
× User.instance_method
○ User.class_method

インスタンスに対して使おうとするとこんな感じ
user = User.find(1)
○ user.instance_method
× user.class_method
× user.private_method

使い分けとしては、 インスタンスの状態(インスタンスが持つ変数たち)によって結果が変わるようなメソッドはインスタンスメソッド、 インスタンスに依って変わらないクラスに共通の処理を行うような場合はクラスメソッド、という感じでしょうか。

2011年12月15日木曜日

[Rails3] belongs_to で関連づけたモデルの属性値でソートするには :include を使うのだ

実行環境:
Rails 3.1.3
Rails3で、belong_to で関連付けられたモデルが持っている属性値でソートするには?

例として以下のように「本(Book)」モデルと「著者(Author)」モデルがあったとします。
{$RAILS_APP}/app/models/authors
class Author < ActiveRecord::Base has_many :books end
{$RAILS_APP}/app/models/books
class Book < ActiveRecord::Base belongs_to :Author end
2つのモデルは、has_many, belongs_to で関連づけられています。
本(Books)のリストを取得する際に著者(Author)の名前の順にソートしたいと思います。

まずは試しに
{$RAILS_APP}/app/models/books
class Book < ActiveRecord::Base belongs_to :Author default_scope :order => "author.name" end
と、has_many / belongs_to の関連付けの力を信じて、うまくやってくれるだろうと期待してみる訳ですが Book.find すると「そんなカラムは知らんぞ!」とエラーが出て怒られます。

ログを見ても Book リストの取得の際の SQL 文に Author テーブルが全く登場しません。
author.name を order by のキーとして与えているところを汲んで欲しいところですが、そこまでは面倒見てくれない、ということでしょうか。

こんな時、他のテーブルを強制的に join させる素敵なコマンドがあります。 :include です。
{$RAILS_APP}/app/models/books
class Book < ActiveRecord::Base belongs_to :Author default_scope :include => "author", :order => "author.name" end
としてやると、authorテーブルを強制的に join してくれるので上記のソートもうまくいくようになります。

2011年12月14日水曜日

[ruby] ハッシュの配列を複数のキーでソートする

実行環境:
ruby 1.9.3
rubyでハッシュの配列を複数のキーでソートする場合のやり方です。
多次元配列やオブジェクトの配列も同じ考え方でいけます。

たとえば、以下のようなハッシュの配列があったとします。
arr = [ { :name => "Dave",  :age => 7, :birthday => '12/05' },
        { :name => "Carol", :age => 5, :birthday => '08/23' },
        { :name => "Alice", :age => 6, :birthday => '05/18' },
        { :name => "Bob",   :age => 5, :birthday => '01/31' } ]

これを :age と :birthday の2つの値でソートすることを考えます。
あくまでも例なので :birthday に年まで入れろや、という突っ込みはなしで(^^;


まずは基本。:ageの値をキーとしてソートするなら
arr.sort!{ |a, b| a[:age] <=> b[:age] }
こんな感じですよね。


次に :age と :birthday の2つの値をキーでソートします。
言いかえれば、まず :age の値で比べて :age が同じなら :birthday の順で並び変える、ということです。
arr.sort!{ |a, b| (a[:age] == b[:age]) ? a[:birthday] <=> b[:birthday] : a[:age] <=> b[:age] }
こんな感じでしょうか。
:age の値の比較を2回やっているのがちょっとカッコ悪いですかね。

2011年12月12日月曜日

[Rails3] 外部の Oracle DB に接続する(その3:Passenger対応編)

実行環境:
centOS 5.7
ruby 1.9.3
Rails 3.1.3
ruby-oci8 2.0.6
activerecord-oracle_enhanced-adapter 1.4.0
Rails3で外部のOracle DBにアクセスします。
その3:Passenger対応編。Passengerでデプロイする場合に必要な対応について、です。

環境変数の設定について

Passengerでデプロイする場合、環境設定がうまくセットされません。
Oracle のアダプタを使う場合 LD_LIBRARY_PATH 環境変数が必要なため、Mongrelサーバで動いてもPassengerではうまく動かないということが起こります。その回避方法です。

まず ruby の wrapper となるシェルスクリプトを作成します。シェルスクリプトの保存場所はどこでもOKです。
/usr/local/bin/ruby_wrapper_for_passenger
#!/bin/sh export LD_LIBRARY_PATH='/usr/lib/oracle/11.2/client/lib' "/usr/local/bin/ruby" "$@"
もちろん忘れずにこのスクリプトを実行可能にします。
# chmod 755 /usr/local/bin/ruby_wrapper_for_passenger

今作った wrapper を httpd.conf の Passenger が起動する ruby として指定します。
/etc/httpd/conf/httpd.conf
PassengerRuby /usr/local/bin/ruby_wrapper_for_passenger

これで無事に動くと思います。(^^)

[Rails3] 外部の Oracle DB に接続する(その2:Railsモデル編)

実行環境:
ruby 1.9.3
Rails 3.1.3
ruby-oci8 2.0.6
activerecord-oracle_enhanced-adapter 1.4.0
Rails3で外部のOracle DBにアクセスします。
その2:Railsモデル編としてRailsで接続できるようなモデルを作成します。

Oracle DBアダプタ(Gemファイル)のインストール

Railsアプリ内の Gemfile に以下の2行を追加します。
#{RAILS_ROOT}/Gemfile
gem 'ruby-oci8' gem 'activerecord-oracle_enhanced-adapter'
bundlerでインストールします。
$ bundle install

接続設定(database.yaml)

データベース接続の設定を行います。

①標準のDB(developmentやproduction)として外部のOracle DBを使う場合
デフォルトで Sqlite3 を使う設定になっているところを Oracle 用の設定に変更します。
#{RAILS_ROOT}/config/database.yml
development: adapter: oracle_enhanced database: [サービス名] username: [ユーザー名] password: [パスワード] production: adapter: oracle_enhanced database: [サービス名] username: [ユーザー名] password: [パスワード]
ここで書くサービス名などはローカル・ローミング・パラメータ、つまり tnsnames.ora ファイルで設定したものになります。詳しくは前回の記事 [Rails3] 外部のOracle DBに接続する(その1:準備編) を見て下さいね。

②標準のDBは内部のSqlite3を使い、特定のモデルだけで外部のOracle DBを使う場合
development:, test:, production: などは残しておいて、下記内容を追記する。
#{RAILS_ROOT}/config/database.yml
oracle_db: adapter: oracle_enhanced database: [サービス名] username: [ユーザー名] password: [パスワード]

外部DBへの接続のやり方については以前まとめました ⇒ [Rails3] ActiveReocrdで外部DBを使う

Scaffoldの作成

通常の Scaffold と同様に作成する。
$ rails generate scaffold user uid:integer name:string comment:string

既存のテーブル(レガシーDB)を使う場合は migration と timestamps をスキップするオプションをつけて作成するとよいと思います。
$ rails generate scaffold user uid:integer name:string comment:string --skip-migration --skip-timestamps
ここで指定するカラム名は使用するものだけでよい。(内部的にはテーブルのカラムを全て取ってくるので、ここで指定しなかったカラムでも view 等でアクセスできる。)

モデルの修正

①標準のDB(developmentやproduction)として外部のOracle DBを使う場合
特に修正の必要はありません。

②標準のDBは内部のSqlite3を使い、特定のモデルだけで外部のOracle DBを使う場合
接続先を通常のdevelopment/productionのDBではなく、oracle_dbで定義したものを使うように設定する。
#{RAILS_ROOT}/app/models/user.rb
class User < ActiveRecord::Base establish_connection(:oracle_db) set_table_name "[Oracleのテーブル名]" set_primary_key "uid" end
endの前の2行はOracleのテーブルがRailsの規約に従っていないときの措置です。テーブル名がモデル名(ここではusers)と一致しない場合は set_table_name で実際のテーブル名を指定する。主キー名がidでない場合は、set_primary_key で主キーとなるカラム名を指定する。

サーバーとして立ち上げて
$ rails server
ブラウザからアクセスしてみましょう。

[Rails3] 外部のOracle DBに接続する(その1:準備編)

実行環境:
centOS 5.7
ruby 1.9.3
Rails 3.1.3
ruby-oci8 2.0.6
activerecord-oracle_enhanced-adapter 1.4.0
Rails3で外部のOracle DBにアクセスします。
その1:準備編として Oracle Instant Client のインストール・設定をします。

Oracle Instant Clientをインストール

OracleのサイトからInstant Clientをダウンロードします。Instant ClientはORACLEが提供しているORACLE接続用クライアントソフトウェアです。外部のOracle DBに直接接続できたりします。DB接続の下地を作ってくれるのでRailsでもその恩恵をあずかろうというわけです。まずはInstant Clientのうちのsqlplusコマンドで目的のOracle DBに接続するのが今回の目標です。

以下の3つのRPMファイルをダウンロードします。無料ですが、ユーザー登録しないとダウンロードできません。
  • oracle-instantclient11.2-basic-11.2.0.2.0.i386.rpm
  • oracle-instantclient11.2-devel-11.2.0.2.0.i386.rpm
  • oracle-instantclient11.2-sqlplus-11.2.0.2.0.i386.rpm
の3つです。

Rails3でOracle DBに接続するだけであれば basic と devel だけでいいようなのですが、sqlplus で目的のDBに接続できるかのテストもしたいので sqlplus も入れておきます。
# rpm -hiv oracle-instantclient11.2-basic-11.2.0.2.0.i386.rpm
# rpm -hiv oracle-instantclient11.2-devel-11.2.0.2.0.i386.rpm
# rpm -hiv oracle-instantclient11.2-sqlplus-11.2.0.2.0.i386.rpm

環境変数の設定

Oracle用の環境変数の設定を行います。
/etc/profile.d/oracle.sh
export ORACLE_HOME='/usr/lib/oracle/11.2/client' export C_INCLUDE_PATH='/usr/include/oracle/11.2/client' export LD_LIBRARY_PATH='/usr/lib/oracle/11.2/client/lib'
これでsqlplus用にはOKです。※Passengerを使ってデプロイすると環境変数の設定がうまくできないないので後で別途設定をします。

ライブラリの設定

以下のように記載してファイルを保存します。
/etc/ld.so.conf.d/oracle.conf
/usr/lib/oracle/11.2/client/lib

ローカル・ネーミング・パラメータ(tnsnames.ora)の設定

ローカル・ネーミング・パラメータというのは、要するにOracle DBへの接続情報(ホストアドレスやポート番号、データベース名)をサービス名でタグづけして、接続する際に接続先の情報をサービス名で一発で指定することができる、というものです。
/etc/tnsnames.ora
サービス名 = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = IPあるいはホスト名)(PORT = 1521)) ) (CONNECT_DATA = (SERVICE_NAME = サービス名) ) )
複数のOracle DBがある場合は、1つの tnsnames.ora ファイルの中に、上記の「サービス名 = (...)」を繰り返し記載することで複数のサービス名を定義できます。

SQL*Plusで接続のテスト

※上記の設定をしてから、設定を反映させるために一旦は端末を立ち上げなおして下さい。
$ sqlplus [ユーザー名]/[パスワード]@[サービス名]
でOracle DBに接続します。
SQL> select * from [テーブル名] where rownum <= 5;
SQL> select table_name from tabs;
などとSQL文を叩いてみます。

問題なく結果が返ってくれば接続の設定はOKです。

「苗字」カラムと「名前」カラムを「フルネーム」に集約する

実行環境:
ruby 1.9.3
Rails 3.1.3
composed_of宣言を使うとテーブルの2つのカラムを疑似的に1つのカラムにまとめることができます。
DBのテーブル上は、「苗字」カラムと「名前」カラムが別になっているが、Rails上はフルネームで扱いたい、なんて場合があります。あるいは住所情報が「都道府県」、「市区町村」、「それ以降の住所」と3つに分かれてテーブルに保存されているのを「住所」という1カラムとして扱いたい、なんて場合もあります。

集約によるデータの合成

例としてこんなテーブルがあったとします。
usersテーブル
カラム1id
カラム2myoji
カラム3namae
カラム4myoji_yomi
カラム5namae_yomi

【目標】:「myoji」と「namae」カラムをスペースでつないだ文字列が「fullname」で参照できるようにする

集約のためのFullNameクラスを定義する

集約のためのルールを定めるクラスを作成します。とりあえずmodelの定義ファイルの中で定義します。
#{RAILS_ROOT}/app/models/users.rb
class User < ActiveRecord::Base end class FullName attr_reader :family_name, :given_name def initialize(family_name, given_name) @family_name = family_name @given_name = given_name end def to_s [@family_name, @given_name].compact.join(" ") end end

テーブル上のカラムとの対応付けを行う

composed_of宣言を使ってfullnameという名前のプロパティ(?)を定義します。元々のテーブルのどのカラムが指定したFullNameクラスのどの変数に対応するかをマッピングします。
#{RAILS_ROOT}/app/models/users.rb
class User < ActiveRecord::Base composed_of :fullname, :class_name => "FullName", :mapping => [ [ :myoji, :family_name ], [ :namae, :given_name ] ] end class FullName # 省略 end

こうすることで、user.fullname という形で参照することができます。
#{RAILS_ROOT}/app/views/users/show.html.erb
<%= @user.fullname %>
FullNameクラスのインスタンス変数(family_name, given_name)にアクセサメソッドを定義しているので以下のような参照も可能です。
#{RAILS_ROOT}/app/views/users/show.html.erb
<%= @user.fullname.first_name %> <%= @user.fullname.given_name %>
もともとのカラムにもアクセスできます。
#{RAILS_ROOT}/app/views/users/show.html.erb
<%= @user.myoji %> <%= @user.namae %>

集約したfullnameに値を代入することはなんだかできないようです。値を変更する場合は、もともとのカラムの値を変えるようにすればいいのかな…?

集約に使うクラスを使い回す

定義したFullNameクラスを「myoji」と「namae」カラムの集約に使いましたが、「myoji_yomi」と「namae_yomi」カラムの集約にも使えます。
#{RAILS_ROOT}/app/models/users.rb
class User < ActiveRecord::Base composed_of :fullname, :class_name => "FullName", :mapping => [ [ :myoji, :family_name ], [ :namae, :given_name ] ] composed_of :fullkananame, :class_name => "FullName", :mapping => [ [ :myoji_yomi, :family_name ], [ :namae_yomi, :given_name ] ] end class FullName # 省略 end
参照はこんな感じで。
#{RAILS_ROOT}/app/views/users/show.html.erb
<%= @user.fullname %> <%= @user.fullname.first_name %> <%= @user.fullname.given_name %> <%= @user.fullkananame %> <%= @user.fullkananame.first_name %> <%= @user.fullkananame.given_name %>
複数のモデルでFullNameクラスを使い回すこともできそうです。そうなってくるとFullNameクラスの定義をする場所はもっといい場所があるかもしれません。(例えば#{RAILS_ROOT}/config/initializers/の中とか??)

結合した値の参照だけならメソッドでいいんじゃない? composed_of の使い方を書いてきましたが、単に fullname を参照するだけならモデルの中にメソッドを定義すればよいような…
#{RAILS_ROOT}/app/models/users.rb
class User < ActiveRecord::Base def fullname "%s %s"%([self.myoji, self.namae]) end end

デバッグ用の文字列をログファイルに書き出す

実行環境:
ruby 1.9.3
Rails 3.1.3
Rails3 でデバッグ用の文字列を書き出す方法。めっちゃ基本事項ですが、久々に Rails に触ったらまるっきり忘れていたのでメモ (^^;

ログファイルに書き出す

Railsの実行時に処理されるコードの中で
logger.debug("ログに吐き出す文字列")
と書いておけば、ログファイル(#{RAILS_ROOT}/log/**********.log)に書き出されます。
「************」の部分は実行モード名が入ります。
developmentモードならdevelopment.log、productionモードならproduction.logちうわけです。
ただし、デフォルトの設定だとproductionモードでは logger.debug は記録されません。
ログの内容にはレベルがありproductionモードでは実行に影響ないものは残さない、ということなのでしょう。

ログレベル
  • logger.fatal
  • logger.error
  • logger.warn
  • logger.info
  • logger.debug

productionモードで無理やりlogger.debugをログに残す場合はproductionモードの環境設定ファイルで設定します。
#{RAILS_ROOT}/config/environments/production.rb
config.log_level = :debug


オブジェクトの中身を記録する

デバッグしていると、このオブジェクトの中身はどうなっているんだろう?ってことがよくあります。
文字列や数字の変数ならそのまま var.to_s や "%s"%var で文字列化してしまえばいいですが、オブジェクトでそれをやると #<ClassName:0xXXXXXXX> みたいな訳分からん文字になってしまいます。
そんなときは .inspect を使うと便利。
logger.debug(var.inspect)
これまたド忘れしてたのでメモしておくのであります。

2011年12月9日金曜日

Passenger で development モードで動かす

実行環境:
ruby 1.9.3
Rails 3.1.3
Passenger で Rails アプリケーションを動かすとデフォルトでは production モードで動きます。

開発中であれば development モードで動かしたいところです。
開発中のテストも、rails server だと変更の度に再起動しなければならないけれど、passenger ならリロードするだけなので簡単ですね。

development モードで動かすときは httpd.conf に指定します。
/etc/httpd/conf/httpd.conf
RailsEnv development

httpdサーバ再起動。
# /etc/rc.d/init.d/httpd restart
これで development モードで動きます。

CentOS 5.7 に ruby 1.9.3 と Ruby on Rails 3.1.3 をインストール

実行環境:
CentOS 5.7
sqlite3 5.7.4
ruby 1.9.3
Rails 3.1.3
新しいマシンにOSからインストールしたので Rails を使えるようにするまでのメモです。

checkinstall をインストール

これからインストールするアプリケーションは yum で入れられないものが多いので、ソースをダウンロードしてきて入れる訳ですが、直にインストール(直接 make install)すると消したくなったときに大変なのでRPM化してからインストールしたいところ。そのRPM化をしてくれるツールがcheckinstallです。

使い方は簡単で、通常のインストール手順で
# ./configure
# make
# make install
とするところを、「make install」に変えて「checkinstall -R」コマンドをうちます。
# ./configure
# make
# /usr/local/sbin/checkinstall -R
-R オプションはRPMパッケージを作ってくれという指示です。(-R を指定しないと途中でどんなパッケージを作るか聞かれますのでそこで RPM と答えてもいいです。)後はデフォルトの選択肢を選んでいけば(リターンキー連打で)大丈夫です。

うまく行けば「/usr/src/redhat/RPM/i386/」ディレクトリに RPM ファイルができますので、後はそれを rpm コマンドでインストールすればOK。
# rpm -ivh /usr/src/redhat/RPM/i386/**********.rpm
もしアンインストールしたくなったら
# rpm -e **********.rpm
こんな感じで消せます。
使い方がなんとなく分かったところで、まずは checkinstall 自体を入れてあげないといけません。

まずはwebからソースを取ってきて普通にインストールします。
# cd /usr/local/src      #<== このフォルダにソースを展開する 
# wget http://asic-linux.com.mx/~izto/checkinstall/files/source/checkinstall-1.6.2.tar.gz
# tar zxvf checkinstall-1.6.2.tar.gz
# cd checkinstall-1.6.2
# make
# make install
これでインストール完了。

なんだけど、後々のために RPM 化して上書きインストールします。
# /usr/local/sbin/checkinstall -R    #<== 上からの続きでまだ checkinstall-1.6.2 ディレクトリにいます。
いろいろなことを言ってきますが、基本的にデフォルトの返事でOKなのでリターンキーで進みます。

ほいでもって、RPM化できたら rpm コマンドでそのファイルをインストールします。
# cd /usr/src/redhat/RPMS/i386
# rpm -ivh checkinstall-1.6.2-1.i386.rpm


sqlite3 3.7.4をインストール

CentOS5の標準でインストールされるsqlite3のバージョンが 3.3.x とちょっと古いのでアップデートします。 sqlite3 自体が古いと ruby 用の sqlite3 アダプタの新しいものがインストールできないので rails を入れる前にアップデートしておきます。 でも yum でインストールできるのは全然新しくないので、ソースをダウンロード ⇒ checkinstall ⇒ rpm の流れでインストールします。
# cd /usr/local/src 
# wget http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz
# tar xzvf sqlite-autoconf-3070400.tar.gz
# cd sqlite-autoconf-3070400
# ./configure
# /usr/local/sbin/checkinstall -R
# rpm -ivh /usr/src/redhat/RPMS/i386/sqlite-autoconf-3070400-1.i386.rpm


いよいよ ruby 1.9.3 のインストール

ruby 1.9.3 のインストールには、事前に libyaml-devel が必要との情報をゲットしたので、先にインストールしておきます。
# yum install libyaml-devel
次は本丸の ruby です。
# cd /usr/local/src
# wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p0.tar.gz
# tar zxvf ruby-1.9.3-p0.tar.gz
# ./configure
# make
んで、ここから checkinstall で RPM 化したいのですが、やってみると途中で止まったようにえらい時間がかかるようです。 Web上の評判だと8~10時間くらいかかるそう。そんなに待ってられないのでここはやむなく直にインストールします。ちぇっ。
# make install


Ruby on Rails 3.1.3 のインストール

Rails のインストールですが、ruby と一緒に gem コマンドがインストールされるのでそれを使います。
# gem install rails
さらに必要なものを gem で入れます。
# gem install sqlite3


Railsアプリケーションの公開(デプロイ)

デプロイには passenger を使いたいと思います。Apache のモジュールとして組み込めるので、システムを再起動しても勝手に起動してくれるので楽ちんです。 インストールは gem で行います。
# gem install passenger
そしてビルドとインストールを次のコマンドで実施します。
passenger-install-apache2-moduleiryo
このコマンドの最後の方で、httpd.conf に追記する設定が表示されるので、それをコピーして httpd.conf の末尾にペーストします。(下記の内容は使っている環境によって異なるので注意)
/etc/httpd/conf/httpd.conf
LoadModule passenger_module /usr/lib/ruby/gems/1.9.1/gems/passenger-3.0.11/ext/apache2/mod_passenger.so ailsSpawnServer /usr/lib/ruby/gems/1.8/gems/passenger-1.9.1/bin/passenger-3.0.11 RailsRuby /usr/local/bin/ruby

次にアプリケーションを設定します。
1つのサーバで複数のアプリケーションを走らせたいと思います。
実現方法はざっくりと
① VirtualHost を複数設定してアプリケーション毎に1つの VirtualHost を割り当てる方法
② Host は単一として、DocumentRoot にサブディレクトリを切ってアプリケーション毎にサブディレクトリを割り当てる方法
が考えられます。
最終的には趣味の問題ですが、ここでは②のやり方で行きたいと思います。

Webの公開ディレクトリ内の構成は、3つのRailsアプリケーション(MyApp1, MyApp2, MyApp3)があるとしたら
DocumentRoot
 ├─ MyApp1    #<== MyApp1のpublicディレクトリのシンボリックリンク
 ├─ MyApp2    #<== MyApp2のpublicディレクトリのシンボリックリンク
 └─ MyApp3    #<== MyApp3のpublicディレクトリのシンボリックリンク
というような形にします。

httpd.confの末尾にそれぞれの Rails アプリケーションのURI的パスを記載します。
/etc/httpd/conf/httpd.conf
RailsBaseURI /MyApp1 RailsBaseURI /MyApp2 RailsBaseURI /MyApp3
これで httpd を再起動すればOKです。
# /etc/rc.d/init.d/httpd restart

2011年6月23日木曜日

[Rails3] Gem ファイルリストを表示する

実行環境:
Rails 3.0.7

Gem ファイルリストを表示するコマンドのまとめ

しばらく使ってないと忘れてしまうのでメモ。

ローカルにインストールした Gem ファイルのリスト
ローカルにインストールした Gem ファイルのリスト
> gem list > gem list --local

リモートにある Gem ファイルのリストを取得する場合は --remote オプションをつける。
リモートにある、指定した名前から始まる Gem ファイルのリスト
●最新バージョンのみ表示
> gem list --remote gemfile_name
●すべてのバージョンを表示
> gem list --remote --all gemfile_name

指定した名前を含む Gem ファイルのリストを表示する場合
リモートにある、指定した名前を含む Gem ファイルのリスト
●最新バージョンのみ表示
> gem search --remote gemfile_name
●すべてのバージョンを表示
> gem search --remote --all gemfile_name

2011年6月20日月曜日

[Rails3] form_for と form_tag、text_field と text_field_tag の違い

実行環境:
Rails 3.0.7

ftext_field と text_field_tag の使い分けについて


Scaffold を作った時に自動的にできてくる View(_form.html.erb)を見ると

<%= form_for(@hoge) do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>
ってな感じで f.text_field を使っています。

特定のモデルに結びついたフォームならこれでいいんですが、そうでない場合は form_for ではなく form_tag を使う、ってとこまでは理解していたんですが、form_tag の中の部品については text_field_tag を使う?でも text_field タグも使えちゃうみたいよ、どうすんだ??と漠然と疑問だったので調べてみました。

その結果は

まぁ、その通りでございますね (^^;;; やっぱり form_for の時は text_field グループを使う、form_tag の時は text_field_tag グループを使うってことで基本的にはいいようです。

しか〜し、これでは再確認しただけで当初の疑問は解決してまへん。もうちょっとつっこんで調べてみると、どうやら params への値の渡り方にポイントがあるようです。

結論としては

  • text_field グループ
    考え方:モデルを対象として、モデルの値を受け渡すためのフォーム部品
    振舞い:params 変数に二次元のハッシュとして値を渡す(一次元目のキーがモデル名)
  • text_field_tag グループ
    考え方:モデルには関連づけず、値のみを単純に渡すフォーム部品
    振舞い:params 変数に一次元のハッシュとして値を渡す
ということになります。

 

まとめるとそういうことなんですが、一応それぞれの場合を整理してみます。

form_forの中でform_tagの中で
text_fieldグループを①(標準的)②(特殊?)
text_field_tagグループを④(特殊?)③(標準的)

① text_field グループを form_for 内で使う(標準的)
Scaffold として作られる以下のようなフォームの場合、
<%= form_for(@hoge) do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>
受け側で受け取るフォームの値は params[:hoge][:name] となります。 通常、モデルの作成や更新に使う場合
create アクション
@hoge = Hoge.new(params[:hoge])
update アクション
@hoge = Hoge.find(params[:id]) @hoge.update_attributes(params[:hoge])
といった具合で便利にできるので params の中身をことさらに気にする必要はないんですね。

② text_field グループを form_tag 内で使う場合(特殊ケース)
form_tag の中で text_field を使うときは少しパラメータの与え方が違って
form_tag の中で text_field
<%= form_tag url, options do %> <%= text_field :hoge, :name, :value => 'default_name' %> <%= submit_tag %> <% end %>
という感じになります。form_tag の中で text_field という形で使うと1番目と2番目のパラメータが名前として使われ、params[:hoge][:name] に値が渡されます。 HTML としてはこんな感じで展開されます。
展開された HTML
<input type="text" id="hoge_name" name="hoge[name]" value="default_name" />
params にハッシュで構造化した値を渡したいときは便利かもしれません。どんな用途があるかパッと思いつきませんが。 ハッシュにしたくないよってときは強引ですが
form_tag の中で text_field
<%= form_tag url, options do %> <%= text_field nil, nil, :id => name, :name => name, :value => 'default_name' %> <% end %>
とすれば
展開された HTML
<input type="text" id="name" name="name" value="default_name" />
と展開されますが、それならおとなしく text_field_tag 使えばって話です (^^;

③ text_field_tag グループを form_tag 内で使う(標準的)
form_tag の中で text_field_tag
<%= form_tag url, options do %> <%= text_field_tag :name, :value => 'default_name' %> <% end %>
とすると params[:name] に値が渡ります。

④ text_field グループを form_for 内で使う(特殊だけど使い道ありそう)
まず、以下のような書き方はできません。
form_for の中で text_field_tag
<%= form_for(@hoge) do |f| %> <%= f.text_field_tag :extra_text %> <% end %>
form_for の中でも form_tag の中でも使い方や挙動は同じです。
form_for の中で text_field_tag
<%= form_for(@hoge) do |f| %> <%= text_field_tag :name, :value => 'default_name' %> <% end %>
とするとやはり params[:name] に値が渡ります。注意すべきは params[:hoge][:name] のように params[:hoge] 配下に入らないということです。
form_for の中で text_field_tag
<%= form_for(@hoge) do |f| %> <%= text_field_tag :extra_text %> <%= f.text_field :name %> <%= f.submit %> <% end %>
という書いてやると、Hoge モデルに対応したパラメータ params[:hoge][:name] と、モデルとは関係ない params[:extra_text] を同時に渡せることになります。 場合によっては便利に使える気がします!(たぶん)

2011年6月16日木曜日

[Rails3] FasterCSVを使う(その3)

実行環境:
Rails 3.0.7
FasterCSV 1.5.4

FasterCSVを使う(その3):CSV出力編

CSV形式のテキストを出力するわけですが、出力のやり方は2通りあります。1つは文字列として取得する方法、もう1つはCSVファイルとしてローカルに(Railsアプリが動いているサーバ上に)書き出す方法です。
  • CSV形式の文字列を取得
  • CSVファイルに書き出す

CSV形式の文字列として取得する
使い方は簡単で、二次元配列を作ってあげるとCSV形式に組み立ててくれる、といった感じです。
csv_string = FasterCSV.generate do |csv|
  csv << ['1', 'Edward', 'steel']
  csv << ['2', 'Roy', 'flame']
  csv << ['3', 'Alex', 'iron-armed']
end
直接CSVファイルに書き出す
ファイルに書き出す場合も似たような書き方で、ファイル名を指定してファイルに書き出します。
FasterCSV.open('output.csv', 'w') do |csv|
  csv << ['1', 'Edward', 'steel']
  csv << ['2', 'Roy', 'flame']
  csv << ['3', 'Alex', 'iron-armed']
end
出力のオプション
デフォルトの設定ではカンマ区切りのCSVが出力されますが、区切り文字をタブに変更したり、といったことができます。
タブ区切り
csv_string = FasterCSV.generate({:col_sep => "¥t"}) do |csv| csv << ['1', 'Edward', 'steel'] end # csv_string: 1(tab)Edward(tab)steel
レコード区切りも変更
csv_string = FasterCSV.generate({:col_sep => ":", :row_sep => "|"}) do |csv| csv << ['1', 'Edward', 'steel'] csv << ['2', 'Roy', 'flame'] end # csv_string: 1:Edward:steel|2:Roy:flame
各フィールドを引用符で囲む場合
ダブルクォーテーションマーク(")で囲む
csv_string = FasterCSV.generate({:quote_char => '"', :force_quotes => true}) do |csv| csv << ['1', 'Edward', 'steel'] end # csv_string: "1","Edward","steel"
囲む文字 :quote_char はデフォルトで「'」にセットされていて :force_quotes => true を指定すると初めて表示されます。:quote_char を空文字列にすると1文字にしろとおこられるので、囲みたくない場合は :quote_char を消すのではなく :force_quotes => false (デフォルト値) にするということですね。

ドキュメントにはオプションが CSV 読み込みと共通って書いてあるけど、使えるオプションはこれくらいかと。

[Rails3] FasterCSVを使う(その2)

実行環境:
Rails 3.0.7
FasterCSV 1.5.4

FasterCSVを使う(その2):読み込み編

[Rails3] FasterCSVを使う(その1)

実行環境:
Rails 3.0.7
ruby 1.8.7
FasterCSV 1.5.4

FasterCSVを使う(その1):インストール編


FasterCSV は CSV 形式ファイルの読み書きができる gem です。
標準でも CSV の読み書きができる CSV クラスがありますが、FasterCSV の方が速いらしいです。(でも比較してないので、そこは気分で ^^;)

ちなみに ruby 1.9.x になって FasterCSV が標準の CSV クラスとして採用されているので FasterCSV をわざわざインストールする必要はありません。普通に require 'csv' して FasterCSV クラスの代わりに CSV クラスを使えばOKです。

FasterCSV のインストール
Bundler を使えば簡単にインストールできます。

①アプリケーションのルートディレクトリのGemfileに
./Gemfile
gem 'fastercsv'
を追記

②Bundlerでインストール
> bundle install

※Bundlerでのインストールについては、[Rails3] Bundlerを使ってGemファイルをインストールも見てね。

③requireも忘れずに
アプリケーション全体で頻繁に使うなら、config/application.rb に書いていつでも使えるようにします。
config/application.rb
class Application < Rails::Application # ... require 'fastercsv' end

特定の controller や特定の action でしか使わないよ、ということなら個別の controller の中に書いてもいいです。
app/controller/user_controller.rb
class UserController < ApplicationController require 'fastercsv' # controller の中に書く場合 def index require 'fastercsv' # action の中に書く場合 # ... end end
もしくは
app/controller/user_controller.rb
class UserController < ApplicationController end

これで準備OKです。

2011年6月14日火曜日

[Rails3] デフォルト以外のViewのテンプレートを使う

実行環境:
Rails 3.0.4
デフォルトでは action と同名の view のテンプレートが使われる。
敢えて別のテンプレートを使用する場合のやり方。

class UsersController < ApplicationController
デフォルトのテンプレートを使う場合
def index respond_to do |format| format.html # アクションと同名の index.html.erb を使用 end end
他のアクションのテンプレートを使う場合
def show respond_to do |format| format.html { render :action => :index } # 指定したアクションのview テンプレートを使用 end end
テンプレートを直接指定する場合
def edit respond_to do |format| # view テンプレートを app/views/ 以下の相対パスで指定(拡張子は省略する) format.html { render :template => 'user/index' } end end end

2011年6月8日水曜日

[Rails3] ActiveReocrdで外部DBを使う

実行環境:
Rails 3.0.4

Railsでは基本的に1つのDBを使用するように設計されています。デフォルトで使用するDBは config/database.yml 内で、実行モード(Rails_Env:development/test/production)別にDBが指定されていますが、これらのデフォルトのDB以外のDBに接続するやり方です。

用途としては例えば、他のシステムで使用しているユーザテーブルを参照する(いわゆるレガシーDBというやつですね)、アクセスログデータを別のサーバ上に保管する、などといった場合があるでしょう。

ここで想定する外部DB/テーブルの条件

  • 使用しているデータベースのアダプタがある
    MySQL, PostgreSQL, SQLite3, Oracle, DB2, SQL Server, ...

  • 使用するテーブルに、整数型の単独の主キーが存在する
  • Railsアプリケーションのサーバからアクセスが可能
    別サーバ上のSQLiteなどのSQLサーバになっていないDBは対象外
    (そんな場合はこんな方法も ⇒ 新規RailsアプリからレガシーなDBを参照する方法 - 今日とは違う明日


モデルを外部DBと結び付ける


1. モデル定義で外部DB接続を指定
普通のモデル定義の中でestablish_connectionというメソッドを呼び出します。connectionはシングルトンで動作するのでestablish_connectionが呼び出されると、デフォルトのDB接続の代わりに指定されたDB接続が使われます。
app/models/users.rb
class User < ActiveRecord::Base establish_connection( :adapter => "mysql", :host => "dbserver", :database => "dbname", :username => "user", :password => "opensesame" ) # ... end

テーブル名や主キー名を指定する
外部DBのテーブル名や主キー名がRailsの規約に沿っている場合は上記でOKですが、他システムのDBを参照する場合などテーブル名や主キー名が規約通りでない場合、使用するテーブル名や主キーのカラム名を指定してあげる必要があります
app/models/user.rb
class User < ActiveRecord::Base establish_connection( # ... ) set_table_name "USER_TBL" set_primary_key "UID" # ... end
こうするとデフォルトのDBのテーブルと同じように扱えます。
@user = User.find(1)

2. DB接続の設定をconfig/database.ymlに設定する
モデルの中にDB接続設定を直接記載するのではなくアプリケーションの設定として記載する方がスマートです。config/database.ymlの中にデフォルトのDB接続情報に加えて外部DBの接続情報を追加します。
config/database.yml
development: # ... test: # ... production: # ... legacy_db: adapter: mysql encoding: utf8 database: dbname username: user password: opensesame host: hostname

app/models/users.rb
class User < ActiveRecord::Base establish_connection(:legacy_db) # ... end

3. 外部DB接続の定義ファイルを独立させる
例えばconfig/legacy_database.ymlという別ファイルにDB接続の設定を保存するとする。
config/legacy_database.yml
adapter: mysql encoding: utf8 database: dbname username: user password: opensesame host: hostname

app/model/user.rb
class User < ActiveRecord::Base config_legacy = YAML.load_file(Rails.root.join('config/legacy_database.yml')).symbolize_keys establish_connection(config_legacy) # ... end

同じ外部DBに接続するモデルが複数ある場合
各モデルに上記の方法(1, 2, 3のいずれか)の通りに書けばアクセスはできるようになります。が、コネクションプーリングを有効に活用するという観点からは、外部DB接続する親となるモデルを作り、親モデルを継承させて各モデルを作る方がよいようです。

以下のようにすると、User1とUser2は接続先のDBは同じでも別々の接続を使います。(コネクションプールが2つ使われてしまいます。)
app/models/user1.rb
class User1 < ActiveRecord::Base establish_connection(:legacy_db) end
app/models/user2.rb
class User2 < ActiveRecord::Base establish_connection(:legacy_db) end
以下のように継承を使うとUser1とUser2は同じコネクションを使ってくれます。
app/models/legacy_base.rb
class LegacyBase < ActiveRecord::Base establish_connection(:legacy_db) end
app/models/user1.rb
class User1 < LegacyBase end
app/models/user2.rb
class User2 < LegacyBase end

[Rails3] Viewでコントローラ名/アクション名を取得する

Viewの中でコントローラ名やアクション名を参照するには、Controllerクラスのインスタンスであるcontrollerを使って取得できます。
#{RAILS.ROOT}/app/views/xxxx.html.erb
  <%= controller.controller_name %>  # コントローラ名
  <%= controller.action_name %>  # アクション名

ただしこの方法では namespace を取得できません。
つまり UsersController も Admin::UsersController も、controller_name は同じく users を返すってこと。残念な人には残念かも…


ちなみに Controller の中であれば、メソッド名だけで呼べます。
#{RAILS.ROOT}/app/controllers/xxxx_controller.rb
  ctrlname = controller_name  # コントローラ名
  actname = action_name  # アクション名

2011年6月3日金曜日

[Rails3] Spreadsheet でセルの結合を扱いたい!

実行環境:
Rails 3.0.7
Spreadsheet 0.6.5.4

Spreadsheet でセルの結合を扱いたい!

と思って色々調べてみたのですが、現状では残念ながらできないみたいです。

SpreadsheetでExcelファイルを開いてフォーマットクラス(Spreadsheet::Format)やらの中身を見てみましたが、セル結合の情報はどうも取得されていないようで…

RubyForge: spreadsheet: helpに「セルの結合はきちんとインプリメントできてないよ。0.7 を待っててね」との書きこみもあったので、いまのところは無理そうということですね。

2011年6月1日水曜日

[Rails3] Spreadsheetを使う(その2)

実行環境:
Rails 3.0.7
Spreadsheet 0.6.5.4

Spreadsheetを使う(その2):Excelファイルを読む

今のバージョンでは、読み込めるExcelファイルはExcel2003形式(.xls)だけのようで、Excel2007形式(.xlsx)は読み込めません。

Excelファイルを開く
book = Spreadsheet.open('public/test.xls', 'rb')
ファイル名の起点はアプリケーションのルートディレクトリ(#{Rails.app})になります。したがってファイル名はルートディレクトリからの相対パスで指定します。

ワークシートを開く
ワークシートは0から始まる番号で指定するか、ワークシート名で指定します。
番号開く(引数がInteger)
sheet = book.worksheet(0)
名前で開く(引数がString)
sheet = book.worksheet("sheet1")

存在しないシートを指定すると nil が返ります。
if book.worksheets(500).nil? then
  ...
end

ワークブックに含まれるワークシートすべてにアクセスするには worksheetsメソッドを使います
book.worksheets.each do |sheet|
  ...
end

データが空っぽのワークシートをスキップするなら…
book.worksheets.select{|s| s.row_count>0 }.each do |sheet| ... end


シート内の各セルのデータを取得する
特定のセルの値にアクセスする方法。行番号(i)、列番号(j)はいずれも0から始まります。つまり"A1"セルの座標(i,j)は(0,0)、"B1"セルなら(1,0)です。
セルの座標を直接指定する
cell = sheet[i,j] cell = sheet.cell(i,j)
行クラスのインスタンスを取得し、そのj番目を指定
row = sheet.row(i) cell = row[j]
列クラスのインスタンスを取得し、そのi番目を指定
column = sheet.column(j) cell = column.to_a[i] # Columnクラスには[]が定義されていない

eachを使って各セルの値を取得する
sheet.each do |row|
  row.each do |cell|
    value = cell
  end
end
cellにはセルの内容によって
・Stringクラス
・Floatクラス(整数の場合もfloatになる)
・Dateクラス
などのインスタンスが入ります。数字や日付の場合でもExcel上でセルの値の頭に「'」をつけておくStringとして取得します。表示形式を文字列にしても変わりません。
セルが空白の場合は Nil が返ります。
数式の場合はSpreadsheet::Formulaクラスのインスタンスが入ります。cell.valueで最後に計算された値が取得できます。

各セルの値を取得する(数式の入ったセルにも対応)
sheet.each do |row|
  row.each do |cell|
    value = cell.instance_of?(Spreadsheet::Formula) ? cell.value : cell
  end
end

sheet.each、row.each の走査範囲
シート内のデータを走査する場合に、値の入っていない行を65535行も走査するとしたら無駄ですね。でも、上記の sheet.each や row.each ではちゃんとデータが入っている範囲だけを走査してくれます。たとえば最初の行がどのセルにも値が入っていない空行であれば2行目から読みます。列方向も同様です。

ということは sheet.each で現れる最初の行が1行目とは限らない、ということになります。
行/列の番号を気にする場合はカウンター変数を使った方がいいでしょう。Worksheet.dimensions メソッドでシート内のデータが入っているセル範囲が取得できます。
first_used_row, first_unused_row, first_used_col, first_unused_col = sheet.dimensions

(first_used_row...first_unused_row).each do |i|
  row = sheet.row(i)
  (first_used_col...first_unused_col).each do |j|
    cell = row[j]
  end
end
ちなみに、データが入っていない行/列であっても書式が設定されていると有効範囲として認識されます。

[Rails3] Bundlerを使ってGemファイルをインストール

実行環境:
Rails 3.0.7
Bundler 1.0.12
Rails3アプリケーションで必要な gem ファイルをインストールする場合、bundler を利用するととても便利です。

手順は簡単で、アプリケーションのルートディレクトリ(Rails.root)内にある Gemfile に必要な gem ファイルを記載し、bundle コマンドを実行すると Gemfile の記載に従ってインストールしてくれます。

Gemfileにインストールするgemファイルの情報を記載

実行モードを問わずインストールする場合はこんな感じで書く
#{Rails.root}/Gemfile
gem 'ruby-debug' gem 'ruby-debug19', :require => 'ruby-debug' gem 'jquery-rails', '>= 0.2.6'

特定の実行モードでインストールするときはこんな感じで書く
#{Rails.root}/Gemfile
group :development, :test do gem 'rspec' gem 'rspec-rails' end


bundle コマンドを実行してインストール

まずはカレントディレクトリをアプリケーションのルートに変更しておきます。bundle コマンドはカレントディレクトリの Gemfile を読み込むからです。

次にインストールする前に gem のインストール先についてぜひ考えておきましょう。つまりサーバ全体にインストールするか、それともアプリケーション内にインストールするかということです。


単純に
bundle install
とうつと、カレントディレクトリにあるGemfileの内容を元に ruby/gems 配下にインストールします。ですのでサーバ内の全環境にインストールします。

インストール先の情報を指定して
bundle install --path vendor/bundle
とすると、#{Rails.root}/vendor/bundle 配下にインストールされ、そのアプリケーションの中だけにインストールされることになります。(インストール先は別にこの名前じゃなくてもよいみたいですが…)

1つのサーバ内で1つのアプリケーションしかないのであればどちらでも大差ないですが、複数のアプリケーションが、複数の Rails のバージョンで動いていると、様々なバージョンの gem が混在することになって気持ち悪いです。なので、というかメンテナンス性という点からも、アプリケーションの中にインストールした方がよいかと思われます。

ちなみに一度、インストール先を指定して bundle install コマンドを実行すると、Rails.root 配下に .bundle/config というファイルが作成されます。
.bundle/config
--- BUNDLE_PATH: vendor/bundle BUNDLE_DISABLE_SHARED_GEMS: "1"
インストール先を記憶してくれているので、次回以降は単に
bundle install
とするだけで同じディレクトリにインストールしてくれます。


bundle でインストールされた gem ファイル一覧

アプリケーションのルートディレクトリ(Rails.root)に移動して
bundle list
コマンドを実行すると、bundleでインストールされたgemリストが表示されます。

Gems included by the bundle:
  * abstract (1.0.0)
  * actionmailer (3.0.7)
  * actionpack (3.0.7)
  * activemodel (3.0.7)
  * activerecord (3.0.7)
  * activeresource (3.0.7)
  * activeresupport (3.0.7)
  * arel (2.0.9)
  * builder (2.1.2)
  * bundler (1.0.12)
  * erubis (2.6.6)
   (以下略)

[Rails3] Spreadsheetを使う(その1)

実行環境:
Rails 3.0.7
Spreadsheet 0.6.5.4

Spreadsheetを使う(その1):インストール編


Spreadsheet は Excel ファイルの読み書きができる gem です。
Excel2003形式(.xls)のファイルの読み書きができます。そして、ruby で書かれていて Windows じゃなくても使えるのがいいところ。Rails アプリケーションの中で Excel ファイルを読み書きする必要があり Spreadsheet を導入してみました。

Spreadsheet のインストール
Spreadsheet をインストールするのは Bundler を使えば簡単。

①アプリケーションのルートディレクトリのGemfileに
./Gemfile
gem 'spreadsheet'
を追記

②Bundlerでインストール
> bundle install

※Bundlerでのインストールについては、[Rails3] Bundlerを使ってGemファイルをインストールも見てね。

これで準備OKです。

[Rails3] rake db:fixtures:load FIXTURES=xxxxx が失敗する件

実行環境:Rails 3.0.7

rake db:fixtures:load FIXTURES=xxxxx が失敗してしまう


個別のテーブル(categories)についてfixtureデータのロードを行おうとして
rake db:fixtures:load FIXTURES=categories
としたところ、エラーが出て catego.yml も catego.csv も見付からないと怒られてしまった。ま、確かにそんな名前のファイルは用意してないんだけど…

試しに
rake db:fixtures:load FIXTURES=menus
をやってみると、m.yml、m.csv が見つからないと言われた…
どうやら FIXTURES で指定した名前の末尾4文字を削除して、そこに .yml か .csv をくっつけてファイルを探している模様。

拡張子をちゃんとつけてやればいいってことかなと思い
rake db:fixtures:load FIXTURES=categories.yml
としたら無事動きました!

ついでに当然ながら
rake db:fixtures:load FIXTURES=categories.xxx
としても普通に動いちゃいました…


拡張子までちゃんとつけなきゃだけど、拡張子を差し替えて探すのかぁ。
機械的に4文字消しちゃうってどうなのかなぁ? 拡張子だって3文字とは限らないし。

※Rails2では拡張子なしでloadできたけど、個人的にはその動作の方がよいような気が…

ま、とにかく拡張子までつけるってことで!

[Rails3] HTMLエスケープがデフォルトになった

Rails2 では view で h ヘルパーを使うことでHTMLエスケープ(例えば、"<" ⇒ "&lt;" ということ)されるが、Rails3 ではHTMLエスケープがデフォルトになっている。つまり単純に <%= %> とするだけでHTMLエスケープされた状態で出力される。 Rails3では
HTMLエスケープあり
<%= ... %>
HTMLエスケープなし
<%= raw ... %>


Rails2では
HTMLエスケープあり
<%=h ... %>
HTMLエスケープなし
<%= ... %>

実行環境:Rails 3.0.7

[Rails3] ルートディレクトリの参照

Railsアプリケーションのルートディレクトリ参照
Rails3のアプリケーション内でアプリケーションのルートディレクトリのパスを参照する場合、Rails.root というクラスメソッドで参照できます。
※Rails2では RAILS_ROOT という定数で参照する。


Railsアプリケーションの実行モード参照
ちなみにRails3のアプリケーション内でアプリケーションの実行モード ( development / test / production ) については Rails.env というクラスメソッドで参照できます。
※Rails2では RAILS_ENV という定数で参照する。

実行環境:Rails 3.0.4

2011年5月31日火曜日

[Rails3] グローバルな定数を定義する

実行環境:Rails 3.0.4

グローバルな定数を定義する

アプリケーション内で使用するグローバルな定数を定義する方法としてはいくつか流儀があるようです。

① app/controllers/application_controller.rb 内で定義
② config/initializers/constants.rb 内で定義


① app/controllers/application_controller.rb 内で定義
applicationコントローラは通常すべての controller のスーパークラスとなっているので、application コントローラ内で定義された定数は、アプリケーション内のすべてのコントローラ内で参照できます。
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base FAVARITE = 'Nadal' end

② config/initializers/constants.rb 内で定義
config/initializersフォルダ内に配置しておくとアプリケーションの起動の際に実行されるので、定数だけを1つのファイルにまとめて定義しておくのも手です。(contants.rb というファイル名はデフォルトでは存在しないものなので好きな名前でOKです。)
すべてのコントローラやモデルで参照できます。
config/initializers/constants.rb
FAVARITE = 'Nadal'


ちなみに定数名はアルファベット大文字で始まります。ruby の約束事です。


Rails2では、config/environment.rb 内で定義するという方法もありますが、ここで記載していたものは Rails3 では①のapplication_controller.rbに移すことになったみたいです。

個人的には②の方法が分かりやすくて好きですが、実行環境の設定に関する定数は①で、それ以外の定数は②で定義する、というのもいいかも知れません