Browse Tag: rails

Rails + MySQL Sandbox

Rails と MySQL Sandbox 5.5.24 の環境で、次のようなエラーが表示されてしまった。


dlopen($HOME/.rvm/gems/ree-1.8.7-2012.02/gems/mysql-2.8.2/lib/mysql.bundle, 9): Library not loaded: libmysqlclient.18.dylib Referenced from: $HOME/.rvm/gems/ree-1.8.7-2012.02/gems/mysql-2.8.2/lib/mysql.bundle Reason: image not found - $HOME/.rvm/gems/ree-1.8.7-2012.02/gems/mysql-2.8.2/lib/mysql.bundle

libmysqlclient.18.dylib が別の場所にあって見つけられないエラーだということだと思います。
次のコマンドを実行したところ治すことができました。

$ install_name_tool -change libmysqlclient.18.dylib $MYSQL_SANDBOX_HOME/5.5.24/lib/libmysqlclient.18.dylib $HOME/.rvm/gems/ree-1.8.7-2012.02/gems/mysql-2.8.2/lib/mysql.bundle

mysql – Library not loaded: libmysqlclient.16.dylib error when trying to run ‘rails server’ on OS X 10.6 with mysql2 gem – Stack Overflow を参考にしました。

[ruby/rails] オフラインドキュメントを生成する

ruby や rails を使ってウェブアプリケーションなどを開発するとき、必須なものにオフラインドキュメントがあります。その都度、インターネット検索したりするのはインターネット接続がある前提になったり、時間の浪費につながるため、できる限り必要な情報は手元に置いておくのが重要だと思います。

ruby や rails のオフラインドキュメントを生成するには、sdoc を使うと便利です。
sdoc は、gem 経由で簡単にインストールすることができます。


$ rvm 1.9.3
$ gem install sdoc

sdoc 3.0.16 だと、github を使っているときエラーが発生しているため、パッチをあてる必要があります。


$ diff $HOME/.rvm/gems/ruby-1.9.3-p194/gems/sdoc-0.3.16/lib/sdoc/github.rb
30c30
< s = Dir.chdir(File.join(basedir, File.dirname(path))) do --- > s = Dir.chdir(File.join(base_dir, File.dirname(path))) do
39c39
< s = Dir.chdir(File.join(basedir, File.dirname(path))) do --- > s = Dir.chdir(File.join(base_dir, File.dirname(path))) do
47c47
< absolute_path = File.join(basedir, path) --- > absolute_path = File.join(base_dir, path)

まずは、rails 3.2.3 のオフラインドキュメントを生成してみます。

$ git clone https://github.com/rails/rails.git
$ cd rails
$ git checkout v3.2.3
$ sdoc --format=sdoc --github --output rails-3.2.3 --hyperlink-all rails

かなり警告がでますが、しばらくすると rails-3.2.3 ディレクトリの中に html なドキュメントが生成されています。僕の手元の Macbook Air だと 15 分くらいかかりました。

次に ruby-1.9.3 のオフラインドキュメントを生成してみます。

$ git clone https://github.com/ruby/ruby.git
$ cd ruby
$ git checkout v1_9_3_194
$ sdoc --format=sdoc --output ruby-1.9.3p194

ruby-1.9.3p194 ディレクトリの中に約 5 分くらいでオフラインドキュメントを生成することができました。

あとは、これからのドキュメントを sdoc-merge を使うとまとめることができます。たしめにまとめてみたのですが、僕は個別で参照することが多いので、使っていません。

$ sdoc-merge --title "Ruby 1.9.3-p194 and Rails 3.2.3" --op merged --names "Ruby,Rails" ruby-1.9.3p194 rails-3.2.3

sdoc かなり便利ですね。

参照
Create local Ruby on Rails documentation for offline use | Shynnergy

Rails 2.0 から 2.3 へバージョンアップしたときの話

仕事の Rails なアプリケーションが昔からあってバージョンが 2.0.2 くらいだったけれど、Rails3 も無事リリースされているし、そろそろ重い腰を上げてバージョンアップしないといかんと思って、まずは 2.3.11 へバージョンアップしてみたときの話。メジャーバージョンが 2 系のままなので、それほど大変ではなかったけれど、既存で使っている gem の差し替えがけっこう大変だった。

まずは、config/environment.rb を、次のように変更する。

RAILS_GEM_VERSION = '2.3.11' unless defined? RAILS_GEM_VERSION

Rails 2.3 から、bundler がサポートされているので、公式ページをみながら作業する。config/preinitializer.rb を作成して、RAILS_ROOT に Gemfile を作成する。Gemfile は、仕事の環境では自前の gem を含めた gem のミラーを作成してあるので、gem のインストール先を変更してあります。下記の URL は例です。

Gemfile

source 'http://rubygems.example.com/rubygems'
gem 'rails', '2.3.11'
gem 'mysql', '2.8.2'
gem 'nokogiri', '1.4.4'
gem 'rmagick', '2.13.1', :require => 'RMagick'
gem 'will_paginate', '2.3.15'

そして、RAILS_ROOT で bundle install するだけだけれど、mysql gem が mysql_config コマンドの場所が分からなくてエラーになってしまうので、事前に次のコマンドを実行する。


$ cd RAILS_ROOT
$ bundle config build.mysql --with-mysql-config=/usr/bin/mysql_config

これで bundle install すれば、Rails 2.3.11 がインストールされる。bundler を使えば、ちゃんと特定のバージョンの gem を使ってくれるし、RAILS_ROOT 以下に gem がインストールされるし、サーバ側で gem を管理しなくても済むので、とても便利です。(今まで一生懸命 puppet manifest に記述してあったので、ごそっと削除した。)サーバ側で管理する gem は、bundler と passenger くらいになった。

Rails アプリケーションをバージョンアップするのも忘れずに。

$ rake rails:update

次に、既存の gem を入れ替え作業にした。まずは、データベースまわりは仕事環境で 2 系統の mysql master – slave 構成になっているため、Rails 2.0 の頃は ActiveRecord cluster というのを使っていた。このライブラリはもうメンテナンスされていないので、探してみたところ data_fabric という gem を見つけた。この他にも 1 系統の master – slave 構成をサポートしている gem は見つかったけれど、2 系統の master – slave 構成をサポートしているのは data_fabric だけの様子だった。

data_fabric の使い方は、github のページに詳しく書かれているし、サンプルの Rails アプリケーションもあるので、すぐに使うことができると思う。まず、Gemfile に、data_fabric を使うように追加する。

gem 'data_fabric', '1.3.1'

database.yml は、次のようにする。次の例では、メイン系統のデータベース(master: 192.168.1.1, slave: 192.168.1.2)と cluster1 という系統のデータベース(master: 192.168.1.10, slave: 192.168.1.11)が、それぞれ master – slave 構成存在している場合の設定方法です。いくつか設定が重複しまうのは微妙だけれど、これは仕方がない。

development:
adapter: mysql
database: test_dev
host: localhost
username: root
password:
encoding: utf8
hoge_development:
adapter: mysql
database: test_dev
host: localhost
username: root
password:
encoding: utf8
production:
adapter: mysql
database: test_production
host: 192.168.1.1
username: hoge
password: hoge_password
encoding: utf8
production_master:
adapter: mysql
database: test_production
host: 192.168.1.1
username: hoge
password: hoge_password
encoding: utf8
production_slave:
adapter: mysql
database: test_production
host: 192.168.1.2
username: hoge
password: hoge_password
encoding: utf8
cluster1_production:
adapter: mysql
database: cluster1_production
host: 192.168.1.10
username: hoge
password: hoge_password
encoding: utf8
cluster1_production_master:
adapter: mysql
database: cluster1_production
host: 192.168.1.10
username: hoge
password: hoge_password
encoding: utf8
cluster1_production_slave:
adapter: mysql
database: cluster1_production
host: 192.168.1.11
username: hoge
password: hoge_password
encoding: utf8

application_controller.rb に、次のコードを追加する。

around_filter :select_shard
private
def select_shard(&block)
yield
end

開発環境のときは slave がないため、その設定を吸収する関数を定義しておく。

def get_replicated
(RAILS_ENV == 'production') ? true : false
end

あとはモデルを ActiveRecord Cluster を使った記述から data_fabric を使った記述に変更するだけとなる。
メイン系統のモデルは、次のようにする。

class Model1 < ActiveRecord::Base data_fabric :replicated => get_replicated
...
end

cluster1 系統のモデルは、次のようにする。prefix をつけるだけ。

class Model10 < ActiveRecord::Base data_fabric :prefix => 'cluster1', :replicated => get_replicated
end

そして、Passenger で動かすと「undefined method disconnect! at DataFabric::PoolProxy」というエラーが出てしまうということで作者の人に問い合わせてみたら、すぐにバージョンアップ(バージョン 1.3.1)にしてくれた。このバージョンで問題なく、Rails 2.3.11 + Passenger の環境で今でもちゃんと動いている。

次に gettext まわり、Rails 2.3 だと i18n という仕組みを使う方式に変更になっている。既存の gettext の仕組みを使う必要があったので、Gemfile に次の gem を追加した。

gem 'gettext', '2.1.0'
gem 'gettext_activerecord', '2.1.0'
gem 'gettext_rails', '2.1.0'
gem 'i18n', '0.5.0'
gem 'locale', '2.0.5'
gem 'locale_rails', '2.0.5'

さらに application_controller.rb に、次を追加した。

init_gettext "hoge"

before_filter :set_locale_info
def set_locale_info
set_locale("ja")
end

この他、最初に読み込まれる方法がかなり変わっていたので、config/environement.rb や config/initializers を整理した。

あと db の migrate ファイル名の形式が変わっているので、念のため既存の db migrate のファイル名を変更する即興プログラムを書いて対応した。もちろん、本番サーバでは db:migrate はリスクが大きすぎて実行はしていない。
あわせて、schema_info テーブルから schema_migrations テーブルに変わっているため、念のため本番サーバを修正しておく必要がある。
schema_migrations テーブルを作成する。

mysql> CREATE TABLE `schema_migrations` (`version` varchar(255) NOT NULL) ENGINE=InnoDB
drop table schema_info;

そして、また即興プログラムで db/migrate にあるファイル名を取得してバージョン情報を追加するプログラムを本番サーバで実行する。

こんな感じで、無事 Rails 2.0 系から Rails 2.3 系にバージョンアップすることができた。
次のターゲットは、Rails 2.3 系から 3.1 系へのバージョンアップを行う予定。

will_paginate tips

Rails で、ページングをいれるとき will_paginate が便利です。will_paginate は、ページングの際、page というリクエストパラメータでページング処理をしますが、このパラメータが空や数字でない文字が入ってしまったとき、「WillPaginate::InvalidPage」 という例外が発生します。

本番環境では、例外が発生したとき、例外の詳細をメールで送信するようにしているのですが、このエラーが何度が頻発して対策を講じるにしました。

まず、考えたのは will_paginate 側本体を変更して、例外を閉じ込めてしまうことでした。will_paginate の過去のチケットをみていると、まったく同じチケットがあって結論的に無効なチケットとなっています。その内容では、Rails2 だと 404 になるとありましたが、僕の環境では例外として表示されていました。

どうやら、僕の環境では ApplicationController 内で、rescue_action を定義しているため、例外として表示されてしまったようです。

また、will_paginate でも、ページ番号がおかしいときの挙動がちゃんと考えられて、Rails2 から追加された rescue_responses を使うことで 404 にするのか、あるいは特定のページにするのか挙動が決められるようになっています。

Exception: WillPaginate::InvalidPage

ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] =
:not_found

rescue_action を定義している場合、上の仕組みではうまくいかないので、自前のrescue_action を次のように変更しました。


def rescue_action(exception)
if RAILS_ENV == 'production'
case exception
when ActionController::RoutingError,
ActionController::InvalidAuthenticityToken,
WillPaginate::InvalidPage
# Do not send error email
render :partial => '/system/notfound', :layout => true, :status => 404 and return
else
begin
UserMailer.deliver_email_snapshot(exception,
clean_backtrace(exception),
session.instance_variable_get("@data"),
params,
request.env)
rescue => e
logger.error(e)
end
end
render :partial => '/system/error', :layout => true, :status => 500
else
super
end
end

本番環境のときにみ、特定の例外が発生したときはメールを送らず 404 ページ、それ以外の例外のときはメールを送信してから 500 ページを表示するようになっています。

この仕組みなら、発生してもよい許容する例外をフィルターしつつ、それ以外の例外が発生したときはメールを送信して対策できるため、便利ですね。
あとは、テスト環境がほとんど整っていないので、ちゃんと整備して、品質を上げていきたいところです。

Rails でメンテナンス画面を表示する方法

今日は、Rails でメンテナンス画面を表示する方法を紹介したいと思います。今のところ、次のような方法で行っています。

環境は、次のとおりです。

  • OS: CentOS 5.x x86_64
  • ロードバランサー: LVS + keepalived
  • ウェブサーバ: Apache + Passenger + Rails

サービスをメンテナンス状態にしたいとき、次の方法が考えらます。

  1. ロードバランサー側の iptables を使って、ロードバランサー側でメンテナンス表示画面を返す
  2. ウェブサーバ側で、メンテナンス表示画面を返す

1 だと、メンテナンスへの切り替えが面倒なので、僕は 2 の方法で行っています。

ウェブサーバ側で、次のようなルールを記述しておきます。

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^(.*)$ /$1 [R=503,L]

DocumentRoot 以下に maintenance.html というファイルがあるとき、そのファイルを返すというシンプルなルールです。

メンテナンス表示に切り替えるには、maintenance.html を置くだけなので、プログラムのデプロイにも使っている capistrano の ::deploy::web の disable / enable を、次のように定義します。

namespace :web do
task :disable do
on_rollback {
run “rm #{current_path}/public/maintenance.html”
}

run “cp #{current_path}/templates/maintenance.html #{current_path}/public/”
end

task :enable do
run “rm #{current_path}/public/maintenance.html”
end
end

あとは、このタスクを使って、次のコマンド一発でメンテナンスへの切り替えをすることができます。

$ cap deploy:web:disable

メンテナンス状態を解除する場合には、次のコマンド一発です。

$ cap deploy:web:enable

また、メンテナンス表示のとき、画像を返さないといけない場合は、mod_asis を使って、次ようなルールを定義しています。

AddHandler send-as-is asis

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
RewriteRule ^(.*)$ /images/transparent.gif.asis [NC,L]

transparent.gif.asis は、1×1 の白い、次のヘッダーがついた gif です。

Status: 200 OK
Content-Type: image/gif
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-store
Cache-Control: no-store