|
|
|
|
Railsには Page, Action, Fragment という3段階のキャッシュ機能が備わっている。tDiaryを使っていても思うが、修正よりも参照の方が圧倒的に多いコンテンツで毎回cgiで同じコンテンツを吐くのは無駄だなぁ。でもキャッシュを自分で用意するのは面倒だし、フレームワークかWebサーバがやってくれたらなぁ。Railsはそんなかゆい所にも手が届く、Web開発界の万能戦艦ノーチラス号なんです。
caching するには、config/environment.rb の最後あたりに以下を記述しておく。
# キャッシュ利用の宣言 (これを忘れるとキャッシュされなくて1時間悩む) ActionController::Base.perform_caching = true # キャッシュファイル保存場所を変更する場合 (デフォルトは RAILS_ROOT+'/public') #ActionController::Base.page_cache_directory = '/var/www/rails_cache'
アクション(cgi)の出力をHTMLで保存し、次回からはActionPack(cgi)の実行を介さずに Web サーバが直接出力する。完全に静的コンテンツ扱いとなるため、通常のコンテンツ生成より100倍くらい速くなりえるが、ステートレスであり、全てのユーザに同じコンテンツを提供するときにしか使えない。具体的には、blog や wiki などには最適だが、個人別のページなどでは恩恵がない。'caches'系クラスメソッドでキャッシュを設定する。
class BerryzController
caches_page :show
def show
@obj = Berryz::find(@params["id"])
end
end
caches_page :show によって、'show'アクション(cgi)の出力結果のHTMLが":page_cache_directory/:controller/:action/:id.html" に保存され、次回以降の参照には直接そのHTMLファイルが返される。キャッシュを無効にする方法は以下の3通り。
# 上記2の例
class BerryzController
def update
Berryz.update(@params["berryz"]["id"], @params["berryz"]) # 通常のデータ更新作業
expire_page :action => "show", :id => @params["berryz"]["id"] # ここでキャッシュ削除
redirect_to :action => "show", :id => @params["berryz"]["id"] # 表示することで新しくキャッシュを作る生活の知恵
end
end
Pageと同じでキャッシュ対象はアクションの出力全部であるが、Fragment(後述)として保存される点と参照時に ActionPack を経由する点が Page とは異なる。つまり、キャッシュを渡す前に ActionPack へ制御がうつるため、そこで認証や制限を行う事が可能になる。
class BerryzController
before_filter :authenticate, :except => :show # filter は全部にかかるので show は除外する
caches_page :show
caches_action :edit
def authenticate
unless @request.env["REMOTE_HOST"] == "127.0.0.1"
redirect_to "/404.html"
end
end
end
キャッシュの削除には expire_action メソッドを利用する。
テンプレートの一部分をキャッシュする。例えば、会員毎のページで会員名を表示するのでPage,Actionは使えないが、共通の重い処理(メニュー項目の作成)だけをキャッシュするという用途がある。PageはHTMLファイルで保存されるが、ActionとFragmentは格納手段(場所)を選択することができる。現在のところ、以下の4種類がある。デフォルトは MemoryStore(メモリ格納)であり、変更する場合は config/environment.rb の最後あたりで定義しておく。
# [MemoryStore] メモリに格納する
# WEBrickやFCGIに最適。CGIではリクエストの最後に破棄されてしまうので無意味。
ActionController::Base.fragment_cache_store =
ActionController::Caching::Fragments::MemoryStore.new
# [FileStore] cache_path にファイルとして保存される。
# どんな環境でも役立つ。
ActionController::Base.fragment_cache_store =
ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
# [DRbStore] 別メモリで実行中の共有DRbに保存される。
# 全プロセスで同じキャッシュを共有しつつも、実行は別DRbプロセスで行いたい場合に役立つ。
ActionController::Base.fragment_cache_store =
ActionController::Caching::Fragments::DRbStore.new("druby://localhost:9192")
# [MemCachedStore] Danga's MemCached
# Danga's MemCached を使って DRbStore と同じ動きをする。
ActionController::Base.fragment_cache_store =
ActionController::Caching::Fragments::FileStore.new("localhost")
# (↑FileStoreになってるけど、そういうもの?> actionpack/lib/action_controller/caching.rb)
Fragmentのキャッシュは view 内では cache メソッドとして利用可能。(CacheHelper#cache)
# berryz_mypage.rhtml <b>ようこそ<%= @session["username"] %>さん</b> <% cache do %> 登録データ一覧: <%= render :partial=>"berryz", collection=>Berryz.find_all %> # (注: ← この render の書式は0.13以降) <% end %> # 'collections' => 'collection' に修正(2005/07/18)
controller 内では以下のメソッドが利用可能。(ActionController::Caching::Fragments module)
実際、キャッシュを削除しようとすると、方法1は動的に更新のあるデータを扱うサイトでは使えず、方法2はモデル(データ)が更新される場所(action)ごとに expire_page なりをそれぞれ呼び出していく必要があり、これは結構な作業の上に見落としが入りやすい。そこで方法3の Sweeper の登場です。(但し、Rails-0.13 以降)。これはモデルの状態を見張り、変更があったら after_save イベントに連動してキャッシュを削除するというObserverとFilterのあいのこのようなキャッシュ掃除人です。
# app/models/berryz_sweeper.rb
class BerryzSweeper < ActionController::Caching::Sweeper
observe Berryz
def after_save(record)
expire_page(:controller => "berryz", :action => %w( show ), :id => record.id)
end
end
Berryzモデルを見張り、DBへの保存後に Page caching で作成された show アクション用のHTMLキャッシュを削除する BerryzSweeper を定義している。これだけで使えそうなものだが、実際に上記の Sweeper を使うコントローラ内で利用宣言をしないといけない。
# app/controllers/berryz_controller.rb class BerryzController < ApplicationController caches_page :show cache_sweeper :berryz_sweeper, :only => [ :destroy ] end
cache_sweeper は引数の Sweeper に対して around_filter を準備してくれる。これで BerryzController の destroy アクションの場合にだけさきほどの after_save が実行されることになる。うーん、このコントローラへの登録は冗長じゃないかなぁ?どのコントローラのどのアクションで実行されたときでも、save の後は常にやってくれる方が安全かつ自然のような気がするけど、速度の問題?ARのcallbackのように定義したら常にやって欲しいな。というか、そもそもARの callback でやってしまったらどうだろう。いや、それはモデルがコントローラを知ることになるからMVCに反するからダメか。ARレベルで完結している連続した更新作業を考えても、HTMLキャッシュと連動されると面倒な気もする。結論、概ねよし。
例えば、Page caching で "/public/show/*" が大量にあって、関連するモデルのどんな更新でもキャッシュ全部を Sweeper に消してもらいたいとき、どうやるんだろう?
ActionController::Caching::Pages#expire_page(options)
→ ...
→ File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
なのでまとめて消す方法は準備されてないし。Dir[page_cache_directory + page_cache_path(path)]のファイルを全部調べて1つずつoptions 作って expire_page 呼ぶのもなんか違うし。やっぱり、
ActionController::Caching::Pages#expire_page_dir(options)
とか欲しいな。仕方ないからとりあえずは上の方法で泥臭くやろうと思ったら、RAILS_ROOT が空でパスが取れない。WEBrick でやってるからなのか?
| JRuby | Rails | Berryz | ℃-ute | エッグ | jQuery |
| 前 | 2005年 7月 |
次 | ||||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 | ||||||
いま気づいたのですが、<br># キャッシュ利用の宣言 (これを忘れるとキャッシュされなくて1時間悩む)<br>ActionController::Base.perform_caching = true<br><br>これ、config/environments/development.rbとconfig/environments/production.rbでそれぞれ定義されており、development.rbではfalseとなっているのでキャッシュされません。なのでdevelopment環境でキャッシュ使いたい場合はemviroments/development.rbを書き換えるのが正しいと思います。