カテゴリ:プログラミング

[Rails 2.3.2] acts_as_state_machine から AASMへ変更

こんちには、おがしょーです。
今回は自分の備忘録のためのエントリです。


ユーザー認証などでRestfulAuthenticationを使う場合、ステートマシンとして2種類のモジュールが使える。
1. acts_as_state_machine
2. AASM (rubyist-aasm)


※今回の流れ。
ググって出てくる参考サイトではほとんどacts_as_state_machineを使用していたので、迷い無くこれをチョイス。

プラグインはGitHubからのインストールでまとめたいので探してみる、が見つからない。

実は長い間アップデートされてない。

後継でAASMなるものを発見。

rubyist-aasmをインストールした後、RestfulAuthenticationをAASMに切り替える。

find_in_stateがエラーになる。

そういえばacts_as_state_machineに複数状態を指定できるパッチ当ててたっけ。。


引用元: kaeruspoon

module ScottBarron
module Acts
module StateMachine
module ClassMethods
protected
def with_state_scope(target_states)
target_states = [target_states] unless target_states.is_a?(Array)
raise InvalidState unless target_states.all? {|s| states.include?(s)}
cond = []
cond_param = []
target_states.each do |st|
cond << "#{table_name}.#{state_column} = ?"
cond_param < {:conditions => [cond.join(" OR "), cond_param].flatten} do
yield if block_given?
end
end
end
end
end
end

AASM用に変更してみる・・・が、動かない。AASMがgemだからか??

メソッド名を変えてリトライ。
[ RAILS/config/initializers/patch_aasm.rb ]
module AASM
module Persistence
module ActiveRecordPersistence
module ClassMethods
def find_in_states(number, state, *args)
with_states_scope state do
find(number, *args)
end
end
def count_in_states(state, *args)
with_states_scope state do
count(*args)
end
end
def calculate_in_states(state, *args)
with_states_scope state do
calculate(*args)
end
end
protected
def with_states_scope(target_states)
target_states = [target_states] unless target_states.is_a?(Array)
raise InvalidState unless target_states.all? {|s| aasm_states.include?(s)}
cond = []
cond_param = []
target_states.each do |st|
cond << "?"
cond_param < {:conditions => ["#{table_name}.#{aasm_column} IN (#{cond.join(', ')})", cond_param].flatten} do
yield if block_given?
end
end
end
end
end
end

できた (n’∀’)η


まだgemとかよくわかってないのGitHubでForkして修正して・・・とかやりたかったけど無理でした。
先は長いなぁ〜。


おまけ。
ステートマシンについてはこちらのページが詳しいです。
境界を越える: Rails での拡張

[Rails 2.3.2] モデルのカラムにサブクエリの値が使いたいのですよ

こんにちは、最近めっきり(なにが?)なおがしょ〜です。
ここ数ヶ月はずっとRubyOnRailsを勉強してますが、ようやく数%くらいわかってきた気がします。


さて、本題。ウェブアプリケーションで親子関係のあるデータを一覧表示しようとする場合で、子供側のデータを COUNT(*) してみたりしたくなる時があると思います(あるよね?ね??)。深く考えず、はじめにRailsでやってみたのはこんな感じでした↓


# モデル:item.rb
class Item < ActiveRecord::Base
  # col: id
  # col: name
  has_many :sub_item
end
# モデル:sub_item.rb
class SubItem < ActiveRecord::Base
  # col: id
  # col: item_id
  # col: name
  belongs_to :item
end
# アクション:items_controller.rb
def index
   @items = Item.find :all
end
# ビュー:items/index.html.erb
<% @items.each do |item| %>
<%=h item.sub_items.count %><!—  小モデルの合計を表示する —>
<% end %>


上記コードで問題なく実行できましたが、発行されるSQL文は
  親モデルのSELECT文+(小モデルのSELECT文×行数)
となり、非常に無駄な気がします。
サブクエリを使えばSQL文は1つで済みますよね。


プラグインとか他のやり方とか探したんですが、findbysql()をする以外の方法が見つからなかったので、簡単にサブクエリを使ったカラムがモデルに追加できるプラグインを作ってみました。http://github.com/ogasyo/subquerycolumn


以下のコマンドでさくっとインストールして、  $ script/plungin install git://github.com/ogasyo/subquerycolumn.git親モデルとコントローラーのfind()部分を以下の用に修正するだけ。


# モデル:item.rb
class Item < ActiveRecord::Base
  # col: id
  # col: name
  hasmany :sub_item
  sub_query_column :count_sub_items, :type => :integer, :query => “SELECT COUNT(id) FROM sub_items where sub_items.item_id = items.id”
end
# アクション:items_controller.rb
def index
   @items = Item.with_sub_query.find :all
end


ビュー側での値の取得はこんな感じです↓
<%=h item.count_sub_items %><!—  NEW! 小モデルの合計を表示する —>

以上で超簡単にできます。


サクッと簡単に作ったプラグインなのでテストはありませんし、バグもあると思います。
利用する場合はきっちりデバッグを行うようにオヌヌメします。
できればGitHubでForkして改良してくれれば最高です(他力本願
#あと、他の方法でこんなのあるよ、とかもうすでに同じのあるだろ、とかはこっそり教えてくれるとありがたいです。。

CakePHP パフォーマンスチューニング

こんにちは、おがしょ〜です。

現在進行中のプロジェクトでCakePHPなるフレームワークを使っているんですが、これが機能満載で思いのほか重い。使用しているバージョンは 1.2RC3 です。「CakePHP パフォーマンス」とかでググってみると1.1に比べてだいぶ遅いというベンチマーク結果などが出てきました。やっぱり。。


とはいえ開発も終盤でフレームワークの変更はしたくないですし、メインの開発陣とは別にチューニング箇所について調査することにしました。まずシステム構成ですが、

  • Linux(なんだっけ?)
  • Apache2
  • mysql5
  • mod_php5

まんまLAMPですね。
あとはアプリ側キャッシュにmemcachedを使ってたり、画像配信にSquidを前に立てたりします。


で、PHP&CakePHP側の主要な構成ですが、
  • コードキャッシュ&ローカルキャッシュにはAPCをチョイス。
  • CakeのCacheエンジンはAPCとMemcacheを用途に応じて切り替え。
  • Memcachedは複数サーバから同じデータを参照する必要のあるものにのみ使用する。それ以外のどうでもいいキャッシュはAPCに格納する。パフォーマンスはたぶんローカルmemcachedと同じようなものかと。
・・・な感じでしょうか。


まず、Apacheについてくる ab(ApacheBench) コマンドで実際のページのパフォーマンスを計測していきます。

>  $ ab http://example.jp/
コマンド終了時に表示された内容の「Requests per second: x.xx [#/sec]」の部分でだいたいの性能をみます。秒間n回リクエストを捌きました的な意味合いだと思います。


どんな数字が出れば速いのか?の基準については私もよくわかりません;;ただ体感的に考えて、1#/sec 程度だとブラウザの表示に最低1秒以上かかってることになるのであまりよろしくないですよね。この辺りの判断は人によります、たぶん・・・。


で、abで計測した結果をみて思ったことは、「なんか全体的に同じように遅い」でした。商品DBなんかの検索機能のあるページとかが飛び抜けて遅いだけなら複雑なSQLクエリとかが原因と考えられたりしますが、どうもそうではない様子。もっと細かく調べてみたところ、CakePHPのコア部分でいくつか微妙な箇所がありました。
  1. モデルをたくさん使っているコントローラが遅い
  2. セッションのストア先にmemcachedを指定すると遅い
  3. ImageMagick(convert)の変換処理が遅い


3はCakePHP関係ないですね;結論からいうと上記3つが主なボトルネックでして、解決することでかなりのパフォーマンスアップが図れました!が、長くなりそうなので解決編は次回以降のエントリに分けます・・・。