ひげろぐ

技術者として仕事人としての思うところや覚え書きやらです
Home      Profile      Works     
2010-05-25

Rails 2.3.6 & Rails 2.3.7 リリース ついでに2.3.8もでたよ

Ruby on Rails 2.3.6 Released

http://weblog.rubyonrails.org/2010/5/23/ruby-on-rails-2-3-6-released

Ruby on Rails 2.3.7 Released

http://weblog.rubyonrails.org/2010/5/24/ruby-on-rails-2-3-7-released

Rails 3の正式版も見えてきた中(見えてきてるよね?)立て続けにリリース。
2.3.6にバグが残ってたのでさくっとフィックスして2.3.7を出したってことみたい。

2.3.6のリリース文句によれば

  • 半年に及ぶバグフィックス
  • 便利な新機能
  • Rails 3への強力な架け橋

ということのようで、ちょいちょい便利そうな小物的機能がいくつか。

またRails 3を控えて今後使えなくなる機能にdeprecatedが出るようになった。
deprecatedの警告を消しておけばRails 3にスムーズに移行できるとのことで、架け橋的リリースというわけです。

Railsのバージョンのライフサイクルからして、長く運用を続けていくつもりのシステムであればRails 3への移行は必須と思われるので、早々に2.3.7へ移行しておくとよいでせう。

追記

Ruby on Rails 2.3.8 Released

http://weblog.rubyonrails.org/2010/5/25/ruby-on-rails-2-3-8-released

毎日バージョン上がるとかナンダコレw

2009-12-11

位置情報を扱うためのSpatial Adapter for Rails

RailsでMySQLのgeometry型などを扱うためのちょいメモ。

Spatial Adapter for Railsを使うとマイグレーションとかでgeometry型を扱えるようになる。
geometry型とか絡むところは生のSQL書いてますよという場合でも、schema.rbにテーブル定義がちゃんと出力されるようになるので、それだけでも便利。

PostgreSQLでも使えるらしいです。

インストール

GeoRubyに依存しているのでまずGeoRubyを入れる。
READMEではgeorubyになっているが、これは情報が古いようだ。

sudo gem install GeoRuby

そのあとプラグインを入れる

ruby script/plugin install git://github.com/fragility/spatial_adapter.git

マイグレーション

マイグレーションは以下のように。

create_table :facilities, :options=>'ENGINE=MyISAM' do |t|
  t.string :name, :null => false, :limit => 50
  t.string :kana, :limit => 50
  t.column :latlng, :geometry, :null => false
end

add_index :facilities, :latlng, :spatial => true 

なつかしのt.columnを使わなくてはならないのは仕様っぽいです。
型は:geometryでも:pointでも。

インデックスを張るときは「:spatial => true」をつける。

なおMySQLのバージョンが5.0.16以下の場合はMyISAMにしないとダメ。
それより後のバージョンではInnoDBでも問題ない。

データの取得とか

作成

 Facility.create(:name => 'なめ', :kana => 'かな', :latlng => Point.from_x_y(43.194, 140.351))

取得

f = Facility.find_by_latlng([[43, 140], [42, 141]])
fs = Facility.find_all_by_latlng([[43, 140], [42, 141]])

参照

f.latlng.x #=> 43.194
f.latlng.y #=> 140.351

ほかREADME参照。

2009-09-09

Rails 2.3.4で追加されたseeds.rbについて

Seed Dataをマイグレーションから隔離するための仕組みが追加された。
これの一番の意図としては

「マイグレーションの中にデータをいじるコードを含めるのやめようぜ。な!」

ってことのようだ。

参考: http://github.com/rails/rails/commit/4932f7b38f72104819022abca0c952ba6f9888cb

Seed Dataって?

まあなんとなくニュアンスは分かるけど、そもそもSeed Dataって何なんだという疑問。

上記を参考にすると

  • ほとんど変更されないデータ
  • 例: Userテーブルの管理者アカウントのような初期データ
  • 例: アプリケーションの設定のためのデータ

ざっくり言うとアプリケーションの動作に必要な初期データ。
この中には都道府県マスタや権限マスタみたいなマスタも含めていいと思う。

db/seeds.rbの書き方

seeds.rbを開けば中に書いてあるサンプルコードですぐ分かるけど、単にseeds.rbにレコードを追加するコードを書いていくだけという単純な仕組みだ。

Category.create(:name => 'テント')
Category.create(:name => '寝袋')

とか

%w{テント 寝袋}.each do |name|
  Category.create(:name => name)
end

とかひたすらRubyのコードを好きなように書くべしとなっている。
フォーマットをCSVとかXMLとかYAMLとかに限定するでもなく、新しいDSLを定義するでもなく、好きなようにコードで書けよという投げっぱなしぶりが潔い。

まあもともとマイグレーション内にまざってたデータ作成/編集コードを分離させたものに過ぎないわけだね。

rake db:seed

seeds.rbの適用はrake db:seedという新しいタスクで行う。

又はrake db:setupを実行した際に読み込まれる。
ただrake db:setupはデータベースをイチから作り直すという破壊と創造を司るタスクなので稼働中の本番環境でうっかり使ったりしないように注意。

rake db:seedの挙動はきわめてシンプル

rake db:seedを叩くとseeds.rbの内容がその都度実行される。
この時既存のデータベースの内容は加味してくれず、今までに何回rake db:seedが実行されたのかなどの考慮もない。

つまりマスタデータを追加するつもりで

%w{テント 寝袋}.each do |name|
  Category.create(:name => name)
end

のようなコードを書いているとrake db:seedするたびにこれが何回も実行されてしまうので、ここらへんの面倒は自分で見ないといけないということだ。
例えば

Category.delete_all
%w{テント 寝袋}.each do |name|
  Category.create(:name => name)
end

みたいな。
ただこの例の場合はIDが変わってしまうのでIDを固定にしたければもうちょっと凝ったコードにしないといけない。

しかしながら実際のところseeds.rbにはあんまり複雑なコードは書かない方がいいだろう。
複雑になりそうならモデルにメソッドを実装してseeds.rb内では単純にそれを呼び出すだけとかにした方が幸せになれるはず。

rake db:seedするタイミングについて

rake db:seedは最初に一回だけ実行するべきものなんじゃないか?
という説も考えられるがそれだと運用途中でマスタを追加した時とか使えないし、結局マイグレーションファイルの中にデータ追加コードを書きたくなってしまう。
ので多分それは違うだろう。

マスタの初期化や再構築のトリガーとしてはちょうどいいので、アプリケーションを修正したときなど運用中の任意のタイミングで使いたい。
ということでseeds.rbはrake db:seedがいつ実行されてもデータに破綻が起きないように書いておいた方が良さそうだ。

まとめ

こうして見ると「便利な仕組み追加しました」というより「マイグレーションの中でデータいじるな」って言うスタンスを明確にしたいというのが一番の目的なかんじ。

  • データを作成/編集するコードはマイグレーションの中に書かない
  • データを作成/編集するコードはdb/seeds.rbに好きなようにRubyのコードで書く
  • rake db:seedで実行

seeds.rbのたぶんベターなプラクティス。

  • 複雑な処理はモデルに実装する
  • いつ実行されてもデータが破綻しないようにしておく
  • 何度実行されてもデータが破綻しないようにしておく
2009-09-04

ふたつの脆弱性に対処したRailsの2.2.3と2.3.4がリリース

本日リリース。
もうgemで入れられるようになってます。

主な内容はXSSとCookie storeに関する脆弱性の修正。
2.3.4では加えて100個ぐらいのバグ修正の他、いくつかの機能追加がされているようだ。

いつの間にか2.1系はセキュリティフィックスもなしっぽい。(リリースはないけどパッチは出ている)
自分も昔作ったやつは放置状態でまだ2.1のやつとかあるんだけど、そろそろ上げろってことかな。

XSS脆弱性

こちらはフォームヘルパーのバグによるXSS脆弱性。

対象は2.0.0以降のすべてのバージョン(ただしruby 1.9を使っている場合は影響なし)
修正済みバージョンは2.3.4、2.2.3。

Cookie storeの脆弱性

セッションストアにCookie storeを使っている場合、Cookieに含まれている値が改ざんされてないかどうかを検証するダイジェストを攻撃者が生成した任意のものにできる。
って書いてある気がする。
Cookie storeを使ってない人、そもそもセッションを使ってない人は慌てる必要なし。

対象は2.1.0以降のすべてのバージョン。
修正済みバージョンは2.3.4、2.2.3。

db/seeds.rbの追加

これは新機能。
各種マスタのデータとかをマイグレーションじゃなくてこれ使って入れなさいってことだと思う。たぶん。

よさげなのであとで使ってみよう。

2.0系と2.1系にはパッチ有り 2009/09/15追記

対応したバージョンのリリースはないけどパッチは出てたみたい。

InfoQ: Ruby on Railsのセキュリティの脆弱性

2009-08-05

Passengerがメモリを食いすぎるとき

Passengerを動かしているサーバのメモリ使用量が突然跳ね上がってスワップをガリガリ発生させることしばしばだったので最近いろいろ調整していた。
結論としては二つ原因があった。

Railsインスタンスプロセスの立ち上がりすぎ

PassengerMaxPoolSizeを適切に設定してないとそうなることがある。

PassengerMaxPoolSizeのデフォルトは6なのでRailsインスタンスが一個につき400MBのメモリを食っていたら最大で2.4GBのメモリを食うことになる。
というわけでメモリが2GBのサーバでも撃沈する。(まあ400MB消費すること自体がおかしいけど)

インスタンスひとつあたりのメモリ使用量を把握するにはしばらく動かしてみるしかないと思うので(何か方法あるかな?)最初は小さめに設定しておくのが無難かもしれない。
この値が1とか2くらいでも小さなサービスでは全く問題ないし。
今回のケースでもこれを4以下にしておけばスワップ発生はなかっただろう。

とはいえ2GBのメモリを搭載しているサーバでPassengerMaxPoolSizeが6で破綻するというのは残念すぎる。
今回の場合はこれは主原因ではなく副原因と言うべきだろう。

一気に大量のレコードを取得しすぎ

そこでもうひとつの問題。

たまにPassengerのRailsインスタンスプロセスのサイズが300MBとか400MBくらいに肥大化しているときがあるので何でかと思って調べてみた。
何となくカンでアタリをつけて動作を確認していると、案の定サイトマップのために数千レコード取得してるのが原因だった。

ビューテンプレートで以下のようにして一度に取得するレコード数を制限したら90MB台をキープするようになった。

Item.paginated_each(:per_page => 100) do |item|
  xml.url do
    xml.loc(url_for(:controller => 'item', :action => 'show', :id => item.id, :only_path => false))
  end
end

善哉。
本当はなるべくビュー側でモデルを直接取得しない方がいいのだろうけど、背に腹は代えられない。
なお、こういう場合はカンじゃなくてログとか調べて時間のかかってる処理からアタリをつけるのがほんとはいいと思う。

ちなみにPassengerじゃなくてthinとかでも同じ問題は起きる。
アプリの実装次第でメモリ使用量が大分変わるという例でもあるということで。

2009-06-25

:selectで取得するカラムを絞ったらパフォーマンスが倍に

最近管理しているDBサーバで継続的にスロークエリが出るようになったので、チューニングしてみたら気持ちの良い結果が出た。
結論から言うとカラム数が多いテーブルに対しては:selectで取得するカラムを絞るのがかなり有効かと思う。

現状把握

今回スロークエリの発生していたテーブルの状況を整理したのが以下。

  • レコード件数は110万件くらい
  • カラム数は30程度
  • インデックスは効いている(explainで確認済み)
  • 処理の性質的にキャッシュは使えない

スロークエリになっているのはもっぱら以下のクエリ。

select * from pages order by updated_at limit 100;

Railsのコードで見るとこんなかんじ。

Page.all(:order => 'updated_at', :limit => 100)

こんな単純なクエリが実行に2秒から10秒程度もかかってスロークエリとして記録されているのは切ない。
インデックスは効いているので問題解決には他のアプローチが必要になる。

考えるに対象は30以上カラムがあってレコードサイズもそこそこ大きいテーブル。
そこで取得するカラムを絞って余計なカラムを取得しないようにしてみたらどうかと思った。

というかクエリが単純すぎてまずはそれくらいしか浮かばなかったわけだけど。

ベンチマークとチューニング

計測なくしてチューニングなしということでベンチマークで使ったのはmybench。

ベンチマークとチューニングは手元の開発環境で実行した。
こちらレコード件数は3万件程度。本番環境より大幅に少ないが十分だろう。たぶん。

全カラム取得とカラムを絞った結果の比較が以下。
10クライアントから100回ずつ、計1000回のリクエストを送るというのを試行回数3回ずつ行った結果。
serialは経過時間(秒)です。

まずは全部まるごと取得している現状のクエリ。

select * from pages order by updated_at limit 100;
# Page.all(:order => 'updated_at', :limit => 100)
  serial  : 29.173278
  serial  : 29.433684
  serial  : 30.258237

これを取得カラムを絞ったものにしてみると。

select id, updated_at from pages order by updated_at limit 100;
# Page.all(:select => 'id, updated_at', :order => 'updated_at', :limit => 100)

  serial  : 16.422306
  serial  : 17.562543
  serial  : 16.070013

倍近く速くなった。
うつくしい。

いやぁ、チューニングって本当に気持ちがいいものですね。
これを本番環境にアップしたらスロークエリもパッタリなくなり幸せになれました。

以下詳しく見たい人向け
select * from pages order by updated_at limit 100;
# Page.all(:order => 'updated_at', :limit => 100)

test: 1000 0.001631 0.060372 0.029173278 29.173278 342.779443571614
  clients : 10
  queries : 1000
  fastest : 0.001631
  slowest : 0.060372
  average : 0.029173278
  serial  : 29.173278
  q/sec   : 342.779443571614

test: 1000 0.001535 0.06981 0.029433684 29.433684 339.746801657584
  clients : 10
  queries : 1000
  fastest : 0.001535
  slowest : 0.06981
  average : 0.029433684
  serial  : 29.433684
  q/sec   : 339.746801657584

test: 1000 0.00298 0.065291 0.030258237 30.258237 330.488521191767
  clients : 10
  queries : 1000
  fastest : 0.00298
  slowest : 0.065291
  average : 0.030258237
  serial  : 30.258237
  q/sec   : 330.488521191767

select id, updated_at from pages order by updated_at limit 100;
# Page.all(:select => 'id, updated_at', :order => 'updated_at', :limit => 100)

test: 1000 0.000327 0.037233 0.016422306 16.422306 608.927881382797
  clients : 10
  queries : 1000
  fastest : 0.000327
  slowest : 0.037233
  average : 0.016422306
  serial  : 16.422306
  q/sec   : 608.927881382797

test: 1000 0.001182 0.050836 0.017562543 17.562543 569.393623691057
  clients : 10
  queries : 1000
  fastest : 0.001182
  slowest : 0.050836
  average : 0.017562543
  serial  : 17.562543
  q/sec   : 569.393623691057

test: 1000 0.000301 0.04706 0.016070013 16.070013 622.277032383234
  clients : 10
  queries : 1000
  fastest : 0.000301
  slowest : 0.04706
  average : 0.016070013
  serial  : 16.070013
  q/sec   : 622.277032383234
2009-05-19

Railsのconsoleにて大量のレコードを少ないメモリで処理する

RailsのconsoleでActiveRecordを使って大量のレコードを処理しようとする場合、とりあえずコントローラ内に書くのと同じように以下のように打ってみると思う。

Item.all.each {|item| item.update_price }

ところが経験ある人も多いと思うけれども、これをやるとレコード数が1000程度でも大量のメモリを食う。
自分の経験を言えば、開発マシンのMacbook Proがスワップを大量発生させてコマ送りのようなレスポンスになった。

それぞれのレコードに対応するオブジェクトがループ終了までガベージコレクションによって破棄されないのでこんな風になる模様。
処理が進むにつれだんだんメモリ消費量が増えていく。
メモリリークのようだけどメモリリークではないんですと。

で、この敵に立ち向かうためにしばらくの間は勤勉さを発揮してある方法を採った。
一度に取得するレコード数にlimitを指定して↑キーとエンターを連打するという頭が悪いけど力技で確実な方法。

Item.all(:limit => 100, :order => 'updated_at').each {|item| item.update_price }

しかしながらさすがにこれを数十回繰り返さないとダメとなった段に勤勉さが底をついた。
というわけでいろいろ試して、これを以下のように書いてみたらメモリ消費は少ないということに気がついた。

(Item.count).times { Item.first(:order => 'updated_at').update_price }

これだとループごとにガベージコレクションがちゃんと働いてくれるみたいだ。

2009-04-25

Rails 2.2以降でのメールの日本語化

UTF-8をうまく処理してくれないメーラーがまだまだ多いのでiso-2022-jpで送らなければならない。
自前で変換処理を行うことも可能だが、いろいろとめんどいのでプラグインのお世話になるのが吉。

Ruby-GetText

2.1までは定番だったこれ。
2.2では使えなくなって困っていた。

2.3ではまた使えるようになりありがたい限り。(使い方が変わっているので注意)
しかしながら2.2ではやはり使えない。

ActionMailerJa

こちらは2.2でも使える。
スバラシイ。
携帯電話用のテンプレ振り分けとかもやってくれるらしいです。

2009-03-29

『Railsデプロイ』は良い本

読んだ。

Railsを本番環境で運用していく方法についてかなり丁寧に書いてある。
単にこうすればうまくいくというだけではなく、なぜそうなるのか、そうしなかったらどうなるのかというところまで掘り下げて書かれている。
しかも読みやすい。さくさく読める。
これはかなりいい本だ。

原書は出た時期がちょっと昔なので情報がちょっと古い面もある。
対象とするRailsのバージョンが2.0.2であったりPassengerに関する言及がなかったり。
けれども全般的に今でも通じるしこれからも通じるだろうということがたくさん書かれている。
決して陳腐化していない。

序盤の章は、運用環境でアプリをちゃんと動かすための諸々の準備やマイグレーションの解説、運用環境にするホストの選び方と設定方法などに割かれている。
このあたりはそれなりに経験を積んだ人なら飛ばしてもいいような内容。
だがたしかに悩んだ覚えのあることがつぶさに書かれている。
これらの章はRails初心者にはかなり得るところが多いだろう。
経験者もざっと目を通してみるとふたつみっつためになることがあるかもしれない。

序盤以降のCapistranoやスケールアウト、パフォーマンスに関する章は初心者でないRails開発者にも興味深いはず。
特にパフォーマンスチューニングの具体的な方法やよくあるボトルネック、キャッシュは実は難しいといったところの内容は面白かった。

Mongrelの章はもうMongrel使ってないからスルーでいいやと思いきや、番犬(Moint)の設定やエラーの通知など面白いトピックがあった。
油断がならない。
Windows環境で運用する手順なんかもまとまっている。
この章は今のところほんとにパラパラとしかめくってないけれど、そのうち読んでみるつもり。

こいつは必読書だと思う。

Railsデプロイ
Railsデプロイ
posted with amazlet at 09.03.29
Ezra Zygmuntowicz Bruce A. Tate Clinton Begin
オライリージャパン
売り上げランキング: 9887

# ところでデプロイって言葉はもう普通に使っても通じるのかな?

2009-03-09

ActiveRecordで任意のidを持つレコードを追加する

各レコードのidの決まっているマスタを作るときなどに知っていると便利な小技。
普通ActiveRecordのidは自動で採番され、レコード作成時に指定することも後で変更することもできない。

例えば最後のレコードのidが100の時に

maker = Maker.create(:id => 1000)

などとしても

maker.id
#=> 101

といった結果になる。

しかしfind_or_create_by_idを使うとidを指定してレコードを作成できる。

maker = Maker.find_or_create_by_id(1000)

とすると

maker.id
#=> 1000

イレギュラーな挙動かもしれないけれど、今のところはこのように動く。

ちなみに更新はやっぱりできないので後からidを変えたくなった場合はexecute_sqlなどで強引に変えるしかないっぽいです。

2009-02-02

Rails 2.3RC1がリリースされた

きてます。
一応2/1付なのかな。

現時点では以下のコマンドで入れることができる。

$ gem install rails --source http://gems.rubyonrails.org

問題がなければたぶんこのままリリースになるんでしょう。

よくまとまっているリリースノート。

主な変更点に関する記事。

変更点はたくさんあるのだけれど、個人的に面白そうだなーと思ったものだけいくつかピックアップしてみる。

動的なScopedメソッドの追加

find_by_xxxと同じノリでscoped_by_xxxとかscoped_by_xxx_and_yyyとか使えるようになった。

scoped_by_xxx(:hoge).scoped_by_yyy(:hage)とかつなげることも出来てなにやらスマート。

というかscopedってメソッドがあったんですな。

デフォルトのScopeが定義できるようになった

モデルのコード内でdefault_scopeを使ってデフォルトの:orderや:limitを定義できるようになった。
普通にfindしたときとかデフォルトの条件が常に適用される。

これは微妙に便利だ。

ただし:conditionsは使えない。(一応使えるけど明らかにサポートされてない動作になる。副作用バリバリ)

Application Templates

DSLで書かれたテンプレートを使ってアプリケーションの初期構築をカスタマイズする仕組みができた。
Railsアプリを作るときいつも必ずする操作の自動化などができる。(public/index.htmlを消す、バージョン管理システムにインポートする、などなど)

以下のようにrailsコマンドに-mオプションでテンプレートファイルを指定すると、その内容を反映してRailsアプリケーションが作られる。

$ rails myproject -m /home/akahige/template.rb

rakeとかscript/generateを使ったりconfig/environment.rbやroutes.rbの内容を変更するなどの仕組みが提供されている。
その他シェルコマンドを叩けるのでいろいろできる。

参考:has_many :bugs, :through => :rails: Rails templates

Metal

MVCを使わずにレスポンスを返すMetalという仕組みを使えるようになった。
高速なレスポンスを返すことが出来る。

JSONやXMLなどテンプレート不要なデータを返したりするときに使える。

参考:ヽ( ・∀・)ノくまくまー(2008-12-16) [Rails] Metal

app/controllers以下の application.rb が application_controller.rb にリネームされた

リネームしないとRails 2.3ではエラーになる。
rake rails:updateで自動的にリネームされる。

うっかりバージョン管理システムへの反映忘れたりとか注意。

これは別に面白いとか思わなかったけどひっかかる人たくさんいそうだなぁと思って。

その他のトピック

個人的にピンと来ないけど一押しっぽいもの。

Nested Attributes, Nested Object Form

1対多の関連で多側のオブジェクトを一度にたくさんつくったり一度に操作するのが簡単になった。

HTTP Digest認証のサポート

ダイジェスト認証の仕組みがビルトインされた。

renderがシンプルに使えるようになった

renderの:actionとか:templateとかの指定がなくても引数を見て良きに計らってくれるようになった。
リリースノートのサンプルがわかりやすい。

Object.try

tryを使うとレシーバがnilでもNoMethodErrorが出ないでnilが返る。
nilかどうか分からないオブジェクトに対して、いちいちnilかどうかチェックして・・・みたいな手間が減らせると言うことなのかな。

2008-08-15

allow_nilの代わりにallow_blankを使えばいいよ

Railsのフォームのバリデーションで文字列を特定のフォーマットで入力して欲しいんだけど未入力はあり。

そんな時に使うのが:allow_nilというオプションなのだけど、このオプションをつけても空文字列は許してくれない(フォームに項目があると未入力でも空文字列が送られてくる)のでそのままでは使えずbefore_validationと組み合わせて使っていた。

validates_format_of :url, :with => URI.regexp(['http', 'https']), :allow_nil => true

def before_validation
  self.url = nil if self.url.blank?
end

でも2.0系からは:allow_blankが追加されて簡単になっていたようだ。

validates_format_of :url, :with => URI.regexp(['http', 'https']), :allow_blank => true

なんか勉強し直さないといけない気がしてきた・・・

2008-07-11

Railsの逆引きリファレンス本

EeePC100円につられて町田のヨドバシに出向いたもののドットがスムーズに打てないキーボードに失望し、書籍コーナーに目標チェンジ。
ちょうどRails 2.0で使える逆引きのリファレンス本っぽいのが欲しいと思っていたのでそこら辺を見てきた。

Railsレシピブック 183の技

あらかじめ目星をつけておいたのがRailsレシピブック。

本棚から取り出してパラパラとめくってみるとなかなかいいかんじ。
単なる型通りの説明だけでなく、ハマりやすいケースに関する注意やしっかりしたサンプルコードなどかゆいところに手が届く内容になっていると感じた。

この本はよく練り上げられている。お買い上げ。

Railsレシピブック 183の技
高橋 征義 諸橋 恭介
ソフトバンククリエイティブ
売り上げランキング: 9759
おすすめ度の平均: 5.0

5 買って損はありません

Ruby on Rails 逆引きクイックリファレンス

Railsレシピブックの近くにあったのでこちらも手に取ってみた。

内容は悪くないがRailsレシピブックに比べるとやや素っ気ないかんじ。これは比べる相手が悪いな。
ただRailsレシピブックで扱ってないトピックに関する項目があったりするので余裕があれば両方買うのもいいかもしれない。

自分はあんまり家に本を増やしたくない人なのでこっちは買わなかった。

Ruby on Rails 逆引きクイックリファレンス Rails 2.0対応
大場 寧子 大場 光一郎 久保 優子
毎日コミュニケーションズ
売り上げランキング: 7737
おすすめ度の平均: 4.0

4 Railsコードを読んでいくときに参考になる。

しかしまあ

Ruby関連のコーナーを見るとずいぶんとRails本が増えてる。
Railsを扱った本が一冊出るたびに話題になってた頃とは隔世の感を覚えた。

2008-06-11

Rails 2.1の変更点をまとめた素敵なPDFが公開されている

英語でありますが。

126ページに及ぶ力作。
ブラジルのRailsコミュニティがポルトガル語でまとめたものを英語に翻訳したものとのこと。
Free BookとあるようにBookなクオリティだけどFree。すばらしい。

先日このブログでもRails 2.1の主な変更点を整理してみたけれども、他にもたくさんよさげな変更が成されていることがわかる。
ややこしそうなところは飛ばして読んだが、へぇー、と思ったものが20以上あり、その中でもすぐに使ってみたいと思ったものが5個以上あった。

というわけで一読オススメです。
ざっと流し読みしつつ興味を惹いたところだけ目を通すだけでも面白いと思えるものがひとつふたつ見つかると思う。

2008/06/23追記

なにやら日本語訳が公開されてます。ぐっじょび。

Ruby on Rails 2.1 の変更点 PDF 日本語訳 « yoidore.org Blog

2008-06-10

マイグレーション関連のRakeタスクの再確認

Rails 2.1になってマイグレーション周りで追加されたタスクや覚えておくべきタスクについての確認。

追加された
  • db:migrate:up
  • db:migrate:down
覚えておいた方がよくなった
  • db:rollback
  • db:migrate:redo
  • db:abort_if_pending_migrations

追加された – 各マイグレーションのupとdownを任意に実行できるように

2.1からRakeタスクに「db:migrate:up」と「db:migrate:down」が追加された。
これにより各マイグレーションのupとdownが好きなタイミングで任意に実行できるようになった。

それぞれupとdownを実行したいVERSIONを指定して実行する。14桁。めんどくさくてもちゃんと指定する。指定しなくてはならない。

 $ rake db:migrate:up VERSION=200806010112
 $ rake db:migrate:down VERSION=200806010112

このdb:migrate:upやdb:migrate:downの実行はマイグレーションのバージョン管理テーブルに記録されない。
例えば新しいマイグレーションファイルを作ってその内容をdb:migrate:upで実行した後にdb:migrateするとフツーにまた実行される(そして多分エラーになる)
つまりdb:migrateやdb:rollbackの変わりになるものではないので注意。

なので恒久的な変更をdb:migrate:upやdb:migrate:downで行うのはやめておいた方がよい。
それだけ念頭に置いておけば便利に使える場面はありそうだ。

ひとつのマイグレーションにつきひとつの変更、あるいは関連するひとまとまりの変更のみを扱うようにしておけばupやdownしやすくていいかもしれない。
個人的にひとつのマイグレーションであれもこれもいろいろとやってしまう傾向があるのでこれを機に改めようかしらん。

覚えておいた方がよくなった – バージョン番号14桁時代に向けて

バージョン番号が長たらしくなったので、2.0から追加されていたタスクの中にもいくつか覚えておきたいタスクが増えた。
db:rollbackとかdb:abort_if_pending_migrationsとか。

rake db:migrateだけで生きていけた時代は終わりを迎えつつあるようだ。

db:rollback

DBの状態を以前のバージョンに戻すタスク。
rake db:migrateで以前のバージョンを指定したときと同じように動作する。

デフォルトでは一つ前のバージョンに戻る。
STEPでバージョンをいくつ戻すか指定できる。

 $ rake db:rollback
 $ rake db:rollback STEP=3
 $ rake db:rollback RAILS_ENV=production
 ※ rake db:migrate:rollbackではないので注意

このタスクを知らないとrake db:migrateのVERSION指定で戻すしかないが、バージョン番号が14桁になった今それはかなり鬱な作業となった。
なので覚えておきたい。

db:migrate:redo

マイグレーションのやり直しを行うタスク。
オプションなしで実行するとバージョンをひとつ戻して再適用する。

動作としてはdb:rollbackした後db:migrateするというもの。
実際単純にdb:rollbackとdb:migrateを続けて呼び出してるだけなので、途中のバージョンでredoした場合も最後のバージョンまでマイグレーションが進んでしまうので注意。
STEPやVERSIONなどそれぞれ指定可能。

このタスクが活躍するのは主に開発中かな?

 $ rake db:migrate:redo
 $ rake db:migrate:redo STEP=3
 $ rake db:migrate:redo STEP=3 VERSION=200806060112

これはそんなに重要じゃないと思うけど一応知ってたらちょっと楽。

db:abort_if_pending_migrations

適用されていないマイグレーションを確認するタスク。
実行した時にすべてのマイグレーションが適用済みであれば何も出力せずに終わるが、適用されていないものがあると次のように出力する。

 $ rake db:abort_if_pending_migrations
 (in /tmp/akahige/test_2_1)
 You have 1 pending migrations:
   20080606132820 Hogehoge
 Run "rake db:migrate" to update your database then try again.

マイグレーションの状況を確認したいときにはバージョン番号を見て判断するよりこちらでやるのがいいだろう。
仕組み的にバージョン番号だけでは適用されていないマイグレーションを判断できなくなったというのもあるが、それでなくてもタイムスタンプのバージョン番号とか正直見間違いしそうだし。

例えばdb:versionで確認しても

 $ rake db:version
 [22:30 root@daifuku] # rake db:version                                                                                                              [/tmp/akahige/test_2_1]
 (in /tmp/akahige/test_2_1)
 Current version: 20080605145748

やっぱりバージョン番号が長すぎてパッと見わかりにくい。
タイムスタンプのシリアル番号は便利だけど見やすいものではないですな。

2008-06-03

Rails 2.1の主な変更点を整理

以下のページを参考にRails2.1の変更点の概要を雑感を交えつつ整理してみた。

Riding Rails: Rails 2.1: Time zones, dirty, caching, gem dependencies, caching, etc

  • タイムゾーンサポート – Time zones
  • ActiveRecordによる変更のトラッキング – Dirty tracking
  • 依存するgemを簡単に準備 – Gem Dependencies
  • 検索条件ごとのファインダメソッドを簡単に作成 – Named scope
  • グループ開発におけるマイグレーションの機能強化 – UTC-based migrations
  • キャッシュ機構の強化 – Better caching
  • その他

タイムゾーンサポート – Time zones

ユーザーのタイムゾーンにあわせた時刻表示が簡単になる仕組み。

これは本体に入れるほどの機能なの?とか思ってしまうのは島国根性というやつでしょうか。
・・・そういやアメリカとか普通にマルチタイムゾーンなのか。

動きは以下のようなかんじ。

Time.zone = 'Tokyo'
Time.zone.now #=> Sun, 01 Jun 2008 22:26:32 JST +09:00
Time.zone = 'UTC'
Time.zone.now #=> Sun, 01 Jun 2008 13:26:45 UTC +00:00

この時Time.nowの挙動はどうなるんだろうかと見てみると

Time.now #=> Sun Jun 01 22:26:48 +0900 2008

これは変わらないので、使わない人はスルーしてもよさげ。
と言いつつTime.zone.nowを使うプラグインなんかが出てきたらそうも言ってられないかもしれないが。

デフォルトのタイムゾーンはconfig/environment.rbで設定する。設定しないとnil。
2.1でアプリを作るとUTCが設定されているので、一応Tokyoとかにしておいた方がよいだろう。OsakaとかSapporoでもよい。

参考

ActiveRecordによる変更のトラッキング – Dirty tracking

ActiveRecordオブジェクトが変更されているか、どの値が変更されているか、変更前の値は何か、などの確認ができるようになった。
また、saveしたときには変更されている値のみがSQLに含まれるようになり無駄が少なくなった。(Partial Updates)

ActiveRecordのオブジェクトで以下のようなメソッドが使えるようになっている。

changed? オブジェクトが変更されているかどうか確認する。変更されていたらtrueが返る。
changed 変更されている属性名を配列で返す。
changes 変更されている属性の名前と値をハッシュで返す。値は変更前と変更後の値の配列。
<attr>_changed? 属性が変更されているかどうか確認する。変更されていたらtrueが返る。
<attr>_was 属性の変更前の値を返す。変更されていない場合は現在の値(つまり同じ値)が返る。
<attr>_change 属性の変更前と変更後の値を配列で返す。変更されていない場合はnilが返る。
<attr>_will_change! 属性を変更することを明示する。代入以外で値を変更するときに使う。

上記のメソッドを使う場面がなくてもActiveRecoredが変更された値を管理していて、変更された値のみをSQLに含めるPartial Updatesが有効になっている。
そのため特に意識しなくてもRails 2.1にするだけで自然と更新の負荷が低減するという恩恵がある。

なおPartial Updatesはデフォルトで有効になっており以前のEdge Railsで必要だった以下の設定は不要になっている。

ActiveRecord::Base.partial_updates = true
参考

依存するgemを簡単に準備 – Gem Dependencies

アプリケーションに必要なgemを明示することで他の環境での動作を簡単にする機能。

他の環境でのgemのインストールをサポートするほか、vender以下にもれなくgemを取り込むことができる。
これはいい機能だ。

まず利用したいgemをconfig/environment.rbに書く。
ここでは割愛するけれどもバージョンを固定したりといったこともできる。

Rails::Initializer.run do |config|
  ...
  config.gem 'rmagick'
  config.gem 'gruff'
  config.gem 'hpricot'
  ...
end

そして依存するgemをシステムにインストールするには

$ sudo rake gems:install

これでconfig/environment.rbに書いておいたgemがすべてインストールされる。素敵だ。

gemをvender以下に取り込むには以下のようにする。

$ rake gems:unpack
参考

検索条件ごとのファインダメソッドを簡単に作成 – Named scope

これは以前紹介したhas_finderの機能を本体にマージしたもの。
モデルのfindメソッドに与えるconditionsやorderなどの条件ごとにそれぞれをメソッドとして定義して便利に使うことができる。

Railsで開発してるとついコントローラに同じ引数のfindメソッドの呼び出しとか増えがちだけど、そういうものを自然とモデルの方にまとめたくなるよい仕組みだ。

簡単なサンプルコード。

モデルで定義して

class Item < ActiveRecord::Base
  named_scope :stocked, :conditions => ["stock > 0"]
  named_scope :has_image, :conditions => {:has_image => true}
end

以下のように使う。

items = Item.stocked.has_image

Named scope同士を連結して使うことができるというのがとてもいい。
さらにNamed scopeの後にfindや集計関数をくっつけることもできたり。

参考

グループ開発におけるマイグレーションの機能強化 – UTC-based migrations

マイグレーションは主に複数人開発に関する機能強化が図られている。

まずマイグレーションのバージョン番号がUTCのタイムスタンプベースのYYYYMMDDHHMMSSになった。
これにより複数人でRails開発すると必ず発生するバージョン番号のバッティングの問題が解消された。

またバージョン番号だけでなくバージョンの管理の仕方も若干変わって、管理テーブルの名前がschema_infoからschema_migrationsに変わった。
schema_infoの頃は最終のバージョン番号だけを記録していたが、schema_migrationsは適用したすべてのマイグレーションのバージョン番号を記録するようになった。

これはコミットのタイミングの違いなどで適用されないマイグレーションが出てくるのを防ぐための仕組み。
タイムスタンプのバージョン番号だけに頼ると、例えば1日前に作られてその日にコミットされたマイグレーションがある場合に、5日前に作られて今日コミットされたマイグレーションが適用されなくなってしまう。
schema_migrationsの管理方法にしたことで適用されていないマイグレーションを確実に見つけて実行できるようになったというわけだ。

ちなみに既存の2.0以前のアプリを2.1にした後でマイグレーションを行うとschema_infoからschema_migraionsへの変換は自動的に行われる。

デメリットとしてはバージョン番号が長たらしくなってエディタで開くときめんどうとかそんなかんじ。
正直ソロ開発者にとってはデメリットだけであんまりうれしくないですな。

ほか、rakeのマイグレーション関連のタスクが増えてて、各マイグレーションのupとdownを任意に実行できるようになってたりする。

参考

キャッシュ機構の強化 – Better caching

フラグメントキャッシュなどのキャッシュは今まではファイルに保存するしかなかったけれども、これをメモリやmemcacheやdrbなど他のキャッシュストアにも保存できるようになった。

定義はconfig/environment.rbなどで以下のように

ActionController::Base.cache_store = :memory_store
ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
ActionController::Base.cache_store = :mem_cache_store, "localhost"

何も指定しないときのデフォルトは:memory_storeになる。

このほかにも自作のキャッシュストアを作成して使うこともできる。

参考

その他

DBコンソールやhas_one :throughといったフィーチャーが有効になっているようだ。

2008-06-01

PHPとPassengerのこととか

KoshigoeBLOG: Passengerのドキュメント読んだ

コメント欄にレスを書こうとしたけどTypeKeyに行く手を阻まれたのでここで。

比較的新しいソフトウェアなので、実績面で不安がありますが、(中略)
対応プラットホームも割と多く検証している気がしますが、そうでもないんでしょうかね。

最近のコミットログを見るに64bit LinuxとMac OS Xあたりの対応でいろいろとやっている模様。
実際に自分の手元でも64bitのCentOS 5.1で動かなかったりしたので、実績や検証はまだまだ足りないレベルかと。

Passengerを使う場合、mod_phpとか有効にすると邪魔になったりするのだろうか。Railsアプリが動くプロセスにmod_php分が上乗せされるのが気持ち悪いけど、気持ち悪いだけで、システムへの悪影響は全くないんだろうか。

通常のhttpdプロセスとは別にRailsアプリ専用のプロセスが立ち上がるのでmod_phpとPassengerが干渉しあうことはないと言ってよさそう。
そもそもPassengerはそうならないように注意深く設計されているとのこと。
ここらへんはPassengerアーキテクチャ概要に書いてありますな。

リクエストが来たらmod_passengerがRailsアプリに関係のあるリクエストがどうかを判断する。
関係のないリクエストはhttpdによってそのまま処理される。この時httpdにmod_phpが乗っていればPHPが動く。
関係のあるリクエストはRailsアプリケーションのインスタンスによって処理される。

といったかんじみたい。

ところでふと思いついて試しにRailsのViewの中にPHPスクリプトを書いて動かしてみたが、PHPは動かなかった。
一方でRAILS_ROOT/public以下のRailsアプリケーションが関与しないファイルは通常のhttpdプロセスによって処理されるので、ここにPHPスクリプトを置いておくと普通にPHPとして動作する。
ページキャッシュを使うとViewの中のPHPスクリプトもキャッシュ後に限って動くなぁ、とか思ったけどこのネタはあんまり役に立たなそうだ。

ところで

Passengerと呼ぶかmod_railsと呼ぶかどっちがいいのか。

前者が正式名称っぽいが、後者の方がわかりやすいよなー。
Rails以外のアプリも動くようにするかどうか検討中とかあるからPassengerと呼んでおいた方がいいのかな。

とりあえずPassengerで統一するか。

てか

ドキュメントの訳とかスゲー。→ KoshigoeBLOG: Passengerのドキュメント読んだ

2008-05-28

mod_rails安定稼働中

先週くらいからアウトドア用品価格比較をmod_railsで動かしているけど、問題なく動作している。
mongrelをクラスターで動かすよりいいかんじ。

メモリ消費量を心配してたけどそれほど大変でもなかった。


  USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
  root     16483  0.0  0.6 123616  6572 ?        Ssl  14:34   0:00 /usr/sbin/httpd
  root     16485  0.0  0.8  14320  8952 ?        S    14:34   0:00 Passenger spawn server
  apache   16490  0.0  1.6  62644 17140 ?        S    14:34   0:26 /usr/sbin/httpd
  apache   16493  0.1  1.6  62600 17188 ?        S    14:34   0:30 /usr/sbin/httpd
  apache   16950  0.0  1.6 103580 17220 ?        S    14:38   0:23 /usr/sbin/httpd
  apache   16491  0.1  1.6  62668 17288 ?        S    14:34   0:30 /usr/sbin/httpd
  apache   16488  0.0  1.6  52420 17300 ?        S    14:34   0:23 /usr/sbin/httpd
  apache   16492  0.1  1.6  62612 17304 ?        S    14:34   0:30 /usr/sbin/httpd
  apache   16954  0.1  1.6 113880 17308 ?        S    14:38   0:32 /usr/sbin/httpd
  apache   17609  0.1  1.6 124116 17432 ?        S    14:45   0:29 /usr/sbin/httpd
  apache   14929  1.0  2.0  27408 21340 ?        S    22:27   0:00 Passenger ApplicationSpawner: /var/www/outdooritem
  root     16602  0.0  2.0  27148 21520 ?        S    14:34   0:01 Passenger FrameworkSpawner: 2.0.2
  apache   13317  2.0  3.6  43168 37308 ?        S    22:02   0:27 Rails: /var/www/outdooritem

稼働してしばらく置いておくと「/usr/sbi/httpd」たちのVSZがものによっては100MB以上とか肥大化しているのでちょっとびびるけど、RSSは安定して20MB以下だしhttpdのプロセスも定期的に生まれ変わるようになっているので問題ないかなと。
Railsと直接関係しない(public以下の静的ファイルの面倒は見てくれるが)httpdプロセスの状況は、VSZが増えて行くところ以外はmod_rails導入前と大して変わらない。
前のエントリではVSZが増えたのをメモリ消費量増えたと表現してた。けどちゃんと見たら実メモリ使用量は大して変わってなかった。もちろんパフォーマンス的にも問題はない。

「Rails: /var/www/outdooritem」は普段はひとつだけど、連続したアクセスが増えてくると現状で数個くらいにまで増える。(デフォルトの状態で最大20個までのようだ)
このプロセスはひとつにつきRSSが35MB~40MB程度なので、プロセスひとつ当たりのメモリ消費はmongrelと比べて少ないくらい。
「Passenger*」たちを加えると1プロセス動作時の実メモリ使用量は若干増えるけど、プロセスが増えるほどmod_railsの方が有利になる。

またmongrelは時間のかかるリクエストを処理している状態で次のリクエストが来ると、前のリクエストが終わるまで次のリクエストを待たせるけど、mod_railsは適宜プロセスを増やして柔軟に対応してくれる。
このあたりはmongrelでクラスター組むよりすぐれている。

Apacheのmod_proxy_balancerと複数のmongrelで組んだクラスターでは、クラスター内のbusyなプロセスにもリクエストを割り振るので時々クライアントを待たせる状態が発生していた。
なのでmongrelを5個くらい立ち上げてごまかしてたんだけど、その状況に比べるとmod_railsを使っている現在の方が断然健全な状態になった。

参考までに実メモリ消費量でざっくり比較すると以下のようなかんじになっていて、メモリ1GBで他にもいろいろとやらせていることのあるマシンではけっこう助かっている。

  • mongrel 50MB/process * 5 = 250MB
  • Passenger*の合計 50MB + Rails:* 40MB/process * 3 = 170MB

プロセス数が1個の状態で比較するとmongrelの方が40MBくらいお得だけど、mongrelを複数立ち上げてクラスターを組むようならmod_railsの方が楽だし負荷にも柔軟だなぁ。
クラスター内のmongrelに処理を割り振るためにリバースプロキシとか使う面倒もないし。

ちなみに現時点で1日当たりのアクセスは6000~9000リクエスト程度。
Googlebotがスゲーお得意様です。

近況

MarkeZine:◎少年ジャンプの歴代人気キャラが駅貼りポスターで競演、缶コーヒー「ルーツ」キャンペーン

今日たまたまJR町田駅で見かけたが、悟空の言ってることがあまりにローカルネタすぎて吹いたw
あなどれねぇぜ町田。

2008-05-18

Passengerことmod_railsを使ってみた

httpd.confでDocumentRootのディレクトリ名を間違えてパーミッションではまった以外は簡単に導入できた。
Apacheモジュールのインストーラ周りはかなりしっかりしていて、足りないライブラリがあると明示してくれる上、それをインストールするためのyumのコマンドまで教えてくれたりする。
親切だ。
インストールでつまずく人はほとんどいないだろうな。

しかし自作のアプリを動かしてみると以下の問題が出た。

いずれもちょっと特殊ケースになるのかもしれないが、商用レベルで動かしているアプリケーションを移行する場合はしっかりテストはした方がよいと思われる。
こういう時のテストはSeleniumがよさげだなぁ。

あとメモリの消費量はけっこうでかい。
PHP5込みのApache2で1プロセス当たり30MB程度だったのが50MBにふくれあがった。
mongrelは1プロセス当たり5MBから6MB程度なのでだいぶ違う。 50MBから60MB。桁間違ってた。
これはメモリの貧弱なマシンにはけっこうきついなー。と、間違い分で認識を改めつつもやはりApacheのプロセスがいくつも立ち上がってしまうときついかなぁ。抑制するオプションはあるっぽいけど。

とは言え、Apacheで簡単にRails運用できる意義はやっぱり大きい。
fast-cgiの設定もいらなくなるし、public以下の.htaccessとかいじる必要もなくなるし(.htaccessは明示的に読ませない限り読まれない。読まなくても勝手にmod_railsがよきに計らってくれる)
バーチャルホストを定義するだけでいいというのは本当にお手軽。

GithubのPassengerのプロジェクトを見ると毎日怒濤のコミットがなされているのでbetaが取れる日も遠くはなさそうだ。

参考

Overview — Phusion Passenger (a.k.a. mod_rails)

追記

メモリ関連はそんなに心配するほどでもなかった。

mod_rails安定稼働中 – ひげろぐ

2008-05-12

Rails2.0でauto_completeを使う

Railsにはもともとテキストフィールドにオートコンプリート機能を簡単に付けることのできる機構が用意されていたが、Rails2.0でこれを使おうとするといくつかつまずく点がある。

プラグインになっている

まずひとつめはページネーションなどと同じくRails2.0になって機能がプラグインに分離されたと言う点。
なのでプラグインをインストールする必要がある。

$ ruby script/plugin install auto_complete

CSRFプロテクションにブロックされる

次にRails2.0から付け加えられたCSRFプロテクションのためにこのまま使ってもエラーが出てしまうので、auto_completeプラグインのソースをいじってやる必要があるようだ。

それには以下のページを参考になる。

Rails 2.0.2で、auto_completeを使うと、 – Invalid Token

これで従来と同じように使えるようになる。

auto_completeの使い方自体は以下のページが詳しい。

検索可能なプルダウンリストを作る。 – ザリガニが見ていた…。

ほかぐぐってもたくさん出てくるのでauto_completeに関してこれ以上の説明は割愛する。

copyright brass.to | powered by WordPress ME