2010-02-21

ActiveRecordのパフォーマンスチューニングをしてみた

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
RailsのActiveRecordのパフォーマンスチューニングをざっくりとした感じで試しました。

アプリケーションの内容は、csvファイルを受け取りそこのデータをデータベースに読み込ませるというもので、データのチェックとして4つぐらいマスターテーブルにデータが存在するかどうかのチェックを行うというものです。

実行時間は、
log/development.log
に記録されるものを利用します。

まず最初の状態。
Completed in 328217ms (DB: 116445)

マスターファイルにデータが存在するかどうかを
find_by_xxx
で行っていたのですが、これは遅いよみたいなことがあったので、これをすべて
find
に書き換えた結果。
Completed in 308355ms (DB: 117323)

さらにマスター検索部分をcache_fuを利用してmemcachedにキャッシュさせた時の結果。
Completed in 262213ms (DB: 39203)

マスター検索ぐらいの比較的類似データの検索がかかるようなものは、DBのキャッシュにひっかかり、memcachedを利用してもそんなに変わらないと思っていたのですが、結構かわるもんですね。

ちなみに
cache_fu
を利用するために
gem install system_timer --include-dependencies
gem install memcache-client --include-dependencies
./script/plugin install git://github.com/defunkt/cache_fu.git
をやって、
マスター用のモデルに以下のようなコードを書きました。

class Zip < ActiveRecord::Base
acts_as_cached :ttl => 10.minutes

def self.get_zip_cache(code)
self.get_cache("zip_#{code}") do
find(:first,:conditions => ["zipcode = ?",code])
end
end
end

こんなコードを書いた後に
Zip.find(:first,:conditions => ["zipcode = ?",code])
していた部分を
Zip.get_zip_cache(code)
と変更しています。

2010-02-19

CentOS5.4環境でexcelファイルをrubyで読み込む

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
rubyではSpreadsheetというのでExcelファイルを読み込めます。
インストールは、
sudo gem install spreadsheet
でOKです。

なのですが、CentOS5.4の環境で試したところ
WINDOWS-932がUTF-8に変換できないよ!
といわれてファイルを読み込むことができませんでした。
ちなみにrubyのバージョンは1.8.7です。

たしかに
iconv -l
と利用できる文字コードを見ると
WINDOWS-932
がありませんでした。

なので、
sudo vi /usr/local/lib/ruby/gems/1.8/gems/spreadsheet-0.6.4.1/lib/spreadsheet/excel/internals.rb
で、以下のようにWINDOWS-932を指定している部分をCP932に変更したら読めるようになりました。

# 932 => "WINDOWS-932", #(Japanese Shift-JIS)
932 => "CP932", #(Japanese Shift-JIS)

ちなみにrubyのインストールprefixを/usr/localにしている場合です。
別に
SHIFT-JIS
とかでもよいのかもしれませんが、CP932にしてみました。

2010-02-15

ActiveScaffoldとjQueryのthickboxを組み合わせる

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ActiveScaffoldとjQueryでモーダルダイアログを表示するthickboxを組み合わせてみました。

郵便番号をzipsテーブルに格納してあり、郵便番号を検索するための画面をthickboxでモーダルダイアログとして表示します。
そこで検索とかして最終的に親のwindowの方の郵便番号欄に値を入れる感じです。

郵便番号欄のフォームのためのviewは以下のような感じです。
jqueryやthickboxのjavascriptやstylesheetは取得してpublic配下に適切においてあるとします。
・・・略
  <%= javascript_include_tag 'jquery' %>
  <%= javascript_include_tag 'thickbox' %>
  <%= stylesheet_link_tag 'thickbox' %>
・・・略
 郵便番号:
 <%= text_field_tag("zip","", {:maxlength => "7"}) %>
 <%= link_to "検索",{:controller=>"zips",:action=>"index",:TB_iframe=>"true",:height=>"400",:width=>"600"},{:class=>"thickbox",:title=>"郵便番号検索"} %>
・・・略
ActiveScaffoldを使っているとTB_iframeを指定してiframeで開かないとうまく開きませんでした。

これから呼び出されるZipsControllerは以下のような感じです。
一応、モーダル用のlayoutを別途用意しているものとしています。
こちらのレイアウト上でjqueryは読み込んでいます。
モーダル上で郵便番号をクリックすると親windowの郵便番号欄に渡されるようにします。
class ZipsController < ApplicationController
  layout "modal"

  active_scaffold :zips do |config|
    config.label = "郵便番号検索"
    config.actions = [:list, :search]
    config.columns = [:code,:address]
    config.columns[:code].label = "郵便番号"
    config.columns[:address].label = "住所"
    config.list.sorting = [{:code => :ASC}]
    config.search.columns << [:code,:address]
  end
end

module ZipsHelper
  def code_column(record)
    link_to_function record.code,"$('#zip',window.parent.document).val('#{record.code}');
    self.parent.tb_remove();"
  end
end
ZipsHelperをこのコントローラーを書いたファイルと同じファイル内に書いています。
ZipsHelperを普通にhelperとして用意してしまうとcodeを利用する部分すべて置き換わってしまいます。
iframeで表示しているので親windowの値を利用するためにparentをちょいちょい書いてあります。

最近は便利なものが本当にいろいろありますね。

2010-02-07

ActiveScaffoldでデータを保存する際にsessionに格納されている情報を利用する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Railsではマスタメンテナンス系のためにScaffoldがありますが、そんなScaffoldを素敵にしてくれるActiveScaffoldがお気に入りなのです。

ActiveScaffoldのインストール方法や利用方法は検索すればすぐわかると思いますので割愛します。

そんなActiveScaffoldで特定のカラムにsessionに格納されているデータを利用して格納する場合です。
ある情報を誰が作成更新したかなどをsessionに格納しているlogin情報などを利用して更新する場合などを想定しています。

仮に以下のようなtestsテーブルがあるとします。
id
data
data2
recorder
create_at
updated_at
このrecorder部分にデータを作成または更新した人の情報を格納するものとします。
更新した人の情報はsession[:login]に格納されているものとします。

このtestsテーブルのメンテナンス画面をActiveScaffoldを利用する場合です。
モデルは定義済みとしてcontrollerは、以下のようにすればOKです。
class TestController < ApplicationController
  active_scaffold :tests do |config|
    config.label = "テストメンテナンス画面"

    config.columns = [:data,
                      :data2]
    config.columns[:data].label    = "登録必須データ"
    config.columns[:data].required = true
    config.columns[:data2].label         = "登録必須でないデータ"
    config.columns[:recorder].label  = "更新者"
    config.columns[:created_at].label    = "作成日時"
    config.columns[:updated_at].label    = "更新日時"
    config.columns[:data].search_sql = 'data'

    config.list.columns << :recorder
    config.list.sorting = [{:data => :ASC}]

    config.show.columns << [:recorder,
                            :created_at,
                            :updated_at]

    config.search.columns << :data
  end

  protected
  def before_create_save(record)
    record[:recorder] = session[:login]
  end

  def before_update_save(record)
    record[:recorder] = session[:login]
  end
end

ポイントはcontroller内に定義した
before_create_save(record):新規登録用
before_update_save(record):更新処理用
を利用することです。

最初は、何も考えずにModelの
before_saveでsession情報を取ろうとしてしまったのですが、
session情報は、Modelでは取れないわけなのでした。

2010-02-05

Windows環境で作成したShift-JISのタブ区切りファイルをUTF-8でlinux上のrubyで扱う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ファイル読み込みを行う際にcsvやtsvファイルを作ってもらって読み込むことが多々あると思います。
そんなファイルはたいていShift-JISになっていて、さらに最初の行がカラムの説明行になっていることもよくあると思います。

例えば、以下のようなファイルです。
test.tsv

日付 お名前 性別
2010/2/5 お試しするぞう 男
2010/2/5 試してみるわ 女


これをlinux上でrubyでUTF-8として読み込む方法です。

以下をなんとかします
・Shift-JISをUTF-8に変換する
・タブ区切りファイルを読み込む
・最初の行をなんとかする

FasterCSVを利用します。
インストールは
gem install fastercsv
でOK。

csvファイルでヘッダーがない場合は、以下のような感じで利用します。

require 'fastercsv'
FasterCSV.foreach('test.csv') { |row| p row}

csvを以下のような配列を得ることができます。

["2010/2/5", "お試しするぞう", "男"]
["2010/2/5", "試してみるわ", "女"]


で、今回の場合は以下のような感じにします。

require 'fastercsv'
FasterCSV.parse(NKF.nkf('-w -Lu',open('test.tsv').read),
:col_sep=>"\t",
:headers => true) {|row|
data = row.fields
p data
}

これで同じように
["2010/2/5", "お試しするぞう", "男"]
["2010/2/5", "試してみるわ", "女"]
となります。

・Shift-JISをUTF-8に変換する
 このために
 NKF.nkf('-w -Lu',open('test.tsv').read)
 でファイルの内容をUTF-8の文字列として取得します。
 ファイルを直接読むならば
  FasterCSV.foreach
 でよいのですが、文字列を扱うので
  FasterCSV.parse
 とします。

・タブ区切りファイルを読み込む
 :col_sep=>"\t"
 を指定すればOKです。

・最初の行をなんとかする
 :headers => true
 を指定することで先頭行をheaderとすることができます。
 ただこれだけだとcsvの値が配列ではなく

#<FasterCSV::Row "日付":"2010/2/5" "お名前":"お試しするぞう" "性別":"男">
#<FasterCSV::Row "日付":"2010/2/5" "お名前":"試してみるわ" "性別":"女">

 のようにFasterCSV::Rowオブジェクトになってしまいます。
 これだと各カラム内容を取得するためには
 row['日付']
 のようにする必要があります。
 ヘッダー情報が絶対変わらないのならばいいのですが、そうとも限らないとこれはこまるので
 data = row.fields
 で配列を取得しています。

前処理で文字変換と一行目除去をしておけばここまでしなくてもよいのですけどね。