16.4 Fragment-Caching 

Grundlagen 

Beim Page- und Action-Caching wird der gesamte HTML-Code einer Action im Cache zwischengespeichert. Wie sieht es jedoch aus, wenn auf einer Seite userspezifische Informationen angezeigt werden sollen?
In einem Shop-System wird in einem Bereich z. B. der Warenkorbinhalt des aktuellen Benutzers angezeigt. Dieser Bereich ist für jeden Benutzer individuell und kann daher nicht im Cache zwischengespeichert werden. Der Bereich der Website, der die Produktdetails anzeigt, hingegen schon.
Auch für diesen Anwendungsfall bietet Rails mit dem Fragment-Caching eine Lösung. Bei dieser Art des Caching wird auf jeden Fall die entsprechende Action ausgeführt. Im passenden Template zu der Action kann mit einem Befehl ein Bereich »markiert« werden, der im Cache gespeichert werden soll.
Fragment-Caching bietet die flexibelste Art des Caching, da hier genau gesteuert werden kann, welche Teile zwischengespeichert werden sollen.
Beispiel 

Wir setzen das Beispiel aus dem vorherigen Kapitel fort. Um das Fragment-Caching zu zeigen, möchten wir die aktuelle Uhrzeit auf der Produkt-Liste (Action index des Products-Controllers) ausgeben. Da sich bei jedem Aufruf der Seite die Uhrzeit ändern soll, können wir diese Ausgabe nicht cachen. Die Ausgabe der Produkte hingegen schon.
Controller
Im Controller deaktivieren wir zunächst das Action-Caching für die Action index und setzen eine Instanzvariable für die aktuelle Uhrzeit.
Listing app/controllers/products_controller.rb
class ProductsController < ApplicationController before_filter :authenticate caches_action :show cache_sweeper :product_sweeper, :only => [:update, :create, :destroy] # GET /products # GET /products.xml def index @current_time = Time.now @products = Product.find(:all) respond_to do |format| format.html # index.html.erb format.xml { render :xml => @products } end end ...
cache do ... end
Im Template index.html.erb geben wir die Uhrzeit aus und umschließen den gesamten HTML-Code, der für die Ausgabe der Tabelle zuständig ist, mit dem Block cache do ... end.
Listing app/views/products/index.html.erb
<h1>Listing products</h1> <%= @current_time.strftime("%H:%M:%S") %> <% cache do %> <table> <tr> <th>Name</th> <th>Price</th> </tr> ... </table> <% end %>
Sie können nun das Beispiel testen, indem Sie im Browser die URL http://localhost:3000/products aufrufen. Bevor wir das Beispiel ausführen, sollten wir zunächst den Cache mit dem Rake-Befehl
rake tmp:cache:clear
löschen.
Cache-Datei
Der Bereich, der von dem Befehl cache umschlossen ist, wird nur dann ausgeführt, wenn noch keine Cache-Datei vorliegt. Ansonsten wird die Datei products.cache aus dem Verzeichnis tmp/cache/localhost.3000 ausgegeben.
Wenn wir uns die Cache-Datei anschauen, erkennen wir, dass im Gegensatz zum Action-Caching nur der HTML-Code enthalten ist, den wir mit dem Befehl cache »markiert« haben.
Listing tmp/cache/localhost.3000/products.cache
<table> <tr> <th>Name</th> <th>Price</th> </tr> <tr> <td>Laserschwert LX light</td> <td>860.0</td> ...
Mehrere Caching-Bereiche |
Es ist auch möglich, mehrere Caching-Bereiche bzw. Fragmente in einem View zu verwenden, indem den Cache-Methoden jeweils ein Name übergeben wird:
cache(name) do ... end |
index
Wir haben nun den Teil des Templates im Cache gespeichert, der für die Ausgabe aller Produkt-Datensätze zuständig ist. Das Problem ist jedoch, dass in der Action index trotzdem bei jedem Aufruf unnötigerweise ein Datenbankzugriff mit dem Befehl @products = Product.find(:all) erfolgt.
Eine Lösung ist es, den Datenbankbefehl aus dem Controller herauszunehmen und innerhalb des cache -Blocks zu setzen. Dies ist zwar möglich, verletzt aber das Model-View-Controller-Prinzip, bei dem die einzelnen Bereiche voneinander getrennt sein sollen.
Eine bessere Lösung ist, im Controller mit der Methode read_fragment() zu prüfen, ob die Caching-Datei vorliegt.
Listing app/controllers/products_controller.rb
class ProductsController < ApplicationController ... def index @current_time = Time.now if !read_fragment(hash_for_products_url) @products = Product.find(:all) end respond_to do |format| format.html # index.html.erb format.xml { render :xml => @products } end end
Löschen von Cache-Dateien 

Die Cache-Dateien des Fragment-Caching liegen an der gleichen Stelle wie beim Action-Caching. Genau genommen ist das Action-Caching ein Spezialfall des Fragment-Cachings.
Rake-Task
Zum Löschen der Cache-Dateien können daher auch dieselben Rake-Tasks verwendet werden (siehe Abschnitt 16.3).
expire_fragment
Rails stellt zum Löschen von Fragment-Cache-Dateien den Befehl expire_fragment() zur Verfügung. Wie bei den anderen expire -Befehlen gibt man im Parameter an, welche Cache-Datei gelöscht werden soll. Im Unterschied zu den anderen ist expire_fragment flexibler, da drei Parametertypen verwendet werden können:
Sie hätten auch die Hilfsmethode hash_for_products_url verwenden können, die durch das Routing in der Datei routes.rb zur Verfügung steht:
- expire_fragment(string)
Der String gibt den Namen der Cache-Datei an, die gelöscht werden soll. Zum Beispiel löscht expire_fragment("products") die Datei products.cache im Cache-Verzeichnis.
- expire_fragment(hash)
Ein Beispiel für die Übergabe eines Hash-Wertes ist:Sie hätten auch die Hilfsmethode hash_for_products_url verwenden können, die durch das Routing in der Datei routes.rb zur Verfügung steht:expire_fragment(:controller => 'products', :action => 'index')
expire_fragment(hash_for_products_url)
- expire_fragment(RegExp)
Als Parameter kann auch ein Regulärer Ausdruck verwendet werden, um z.B. mehr als nur eine Datei zu löschen. Mit demfolgenden Befehl werden alle Cache-Dateien gelöscht, die eine Zahl enthalten:Es ist zu beachten, dass Sie das Dach-Zeichen und das Dollar-Zeichen nicht zum Markieren vom Anfang und Ende einer Zeichenkette verwenden können, da der Reguläre Ausdruck mit der Zeichenkette der Form ./cache/pfad/dateiname.cache verglichen wird.expire_fragment(//[0-9]+//)
Um den Fragment-Cache in unserem Beispiel zu löschen, verwenden wir die Hash-Variante des expire_fragment -Befehls. Der Sweeper sieht dann wie folgt aus:
Listing /app/models/product_sweeper.rb
class ProductSweeper < ActionController::Caching::Sweeper observe Product def after_save(product) expire_products(product) end def after_destroy(product) expire_products(product) end private def expire_products(product) expire_action(product_url(product)) expire_fragment(hash_for_products_url) end end
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.
- expire_fragment(RegExp)