16.2 Page-Caching 

Page-Caching ist die einfachste Art des Caching in Rails. Es basiert auf dem Prinzip, dass die gesamte Seite in einer Datei zwischengespeichert wird.
Grundlagen 

Wenn ein Seite zum ersten Mal aufgerufen wird, wird sie zunächst von Rails verarbeitet. Dies erfordert folgende Schritte:
- Routing
Die URL wird verarbeitet, und nach den definierten Routingregeln werden daraus der entsprechende Controller und die passende Action aufgerufen.
- Ausführung der Action im Controller
Im Controller wird die entsprechende Action ausgeführt, um z. B. Parameter auszulesen, ActiveRecord-Befehle auszuführen und die Ergebnisse in einer Instanzvariablen einem Template zur Verfügung zu stellen. Die gegebenenfalls erforderlichen Datenbankzugriffe sind in den meisten Fällen der zeitaufwendigste Teil der Verarbeitung.
- Verarbeitung des Templates
Im Template schließlich wird der HTML-Code generiert.
- Speichern des HTML-Codes als Datei
Wenn das Caching aktiviert ist, wird der HTML-Code nicht nur direkt an den Client (Webbrowser) weitergeleitet, sondern auch als Datei im Verzeichnis public gespeichert.
Beim nächsten Aufruf wird nicht mehr der Railsprozess durchlaufen, sondern das statische HTML-Dokument aus dem Verzeichnis public direkt an den Client ausgeliefert. Ihre Website ist in diesem Fall genauso performant wie eine Website, die auf rein statischen Webseiten basiert.
public
Der Rails-Server schaut bei jedem Zugriff im Verzeichnis public nach, ob das entsprechende Dokument vorhanden ist. Wenn es vorhanden ist, so wird das HTML-Dokument direkt an den Client geschickt. Ansonsten wird der Railsverarbeitungsprozess gestartet. Angenommen, der Webserver erhält die Anfrage für die URL http://http://www.url.de/products/234 , dann überprüft er, ob die Datei public/products/234.html vorhanden ist. Es ist zu beachten, dass products ein Verzeichnis ist. Ist die Datei vorhanden, wird sie direkt an den Client ausgeliefert.
Im folgenden Diagramm wird der Ablauf des Caching verdeutlicht.
Abbildung Grundprinzip des Page-Caching
Caching im Controller aktivieren 

Das Page-Caching wird pro Action (Methode) im Controller festgelegt. Es kommen jedoch nur die Actions für das Page-Caching in Frage, die auch nur für die Anzeige von Daten zuständig sind. Dies sind in der Regel nur die Index-Action für die Anzeige aller Datensätze und die Show-Action für die Anzeige eines Datensatzes im Detail. Formulare sollten deshalb nicht im Cache gespeichert werden.
caches_page
Das Page-Caching wird im Controller mit dem Befehl caches_page :action1, :action2, ... für die entsprechenden Actions aktiviert. Die Aktivierung des Page-Caching für die Actions :index und :show im Employees-Controller unseres Beispiels aus Kapitel 3 können Sie wie folgt umsetzen:
class EmployeesController < ApplicationController
caches_page :index, :show
...
end
Einstellungen 

In den Environment-Konfigurationsdateien wird mit der Einstellung
config.action_controller.perform_caching = true/false
das Caching aktiviert bzw. deaktiviert. Diese Einstellung betrifft sowohl das Page-Caching als auch das Action- und Fragment-Caching.
Standardmäßig hat Rails drei Konfigurationsdateien:
- Konfigurationsdatei für die Entwicklungsumgebung:
In der Datei config/environments/development.rb werden Einstellungen für die Entwicklungsumgebung vorgegeben. Wenn Sie die Applikation lokal mit ruby script/server starten, werden die Einstellungen aus der Development-Umgebung geladen. Hier ist das Caching standardmäßig deaktiviert:... config.action_controller.perform_caching = false ...
- Konfigurationsdatei für die Testumgebung:
Die Testumgebung wird in der Datei config/environments/test.rb konfiguriert. Auch hier ist das Caching standardmäßig deaktiviert. Da die Testumgebung für die Ausführung der Testklassen verwendet wird, sollte das Caching hier nicht aktiviert werden.
- Konfigurationsdatei für die Produktionsumgebung:
Die Produktionsumgebung wird in der Regel auf dem Webserver verwendet. Deshalb ist das Caching in der Konfigurationsdatei config/environments/production.rb aktiviert:config.action_controller.perform_caching = true
Beispiel 

Um Ihnen das Caching anhand eines Beispiels zu zeigen, erstellen wir eine kleine Applikation zur Verwaltung von Produkten und aktivieren im Products-Controller das Caching.
- Rails-Projekt erstellen
rails caching_demo cd caching_demo
- Ressource »products« erstellen
ruby script/generate scaffold product name:string \ price:float
- Migration zum Erstellen der Tabellen ausführen
rake db:migrate
- Im Controller Caching für Index- und Show-Action aktivieren
class ProductsController < ApplicationController caches_page :index, :show ... end
Starten Sie anschließend den lokalen Server mit ruby script/server, rufen Sie die Seite http://localhost:3000/products auf, und geben Sie ein paar Beispielprodukte ein.
Abbildung http://localhost:3000/products
Wir aktivieren zu Testzwecken das Caching in der Konfigurationsdatei für die Entwicklungsumgebung.
Listing config/environments/development.rb
config.action_controller.perform_caching =
true
Nachdem wir Änderungen an der Konfigurationsdatei vorgenommen haben, müssen wir den lokalen Server erneut starten (
+
und dann erneut ruby script/server).
Wenn wir die Seiten http://localhost:3000/products und http://localhost:3000/products/1 aufrufen, werden folgende Dateien durch das Caching aktiviert:
Abbildung mono{public}-Verzeichnis mit den Cache-Dateien
In dem Konsolen-Fenster, in dem der lokale Server gestartet wurde, bzw. im Log-File kann man beobachten, dass es beim erneuten Laden der Seiten zu keinerlei Aktivität kommt.
Löschen von Cache-Dateien 

Problem
Wenn wir einen weiteren Datensatz über den Link New product hinzufügen, fällt auf, dass das neue Produkt nicht in der Liste der Produkte http://localhost:3000 angezeigt wird. Das gleiche Problem tritt auch auf, wenn wir einen Datensatz verändern. Die Änderung wird wegen des Caching weder auf der Index- noch auf der Show-Seite angezeigt. Es gibt verschiedene Möglichkeiten, den Cache zu löschen. Das Löschen von Cache-Dateien wird auch als Cache Expire bezeichnet.
Manuelles Löschen von Cache-Dateien
Löschen
Der Cache kann z. B. leicht gelöscht werden, indem Sie einfach die entsprechende Datei im Verzeichnis public (z. B. public/flights.html) löschen. Dann werden die Daten wieder frisch aus der Datenbank gelesen und die Cache-Dateien neu erstellt.
Ein manuelles Löschen ist jedoch nicht praktikabel. Wir hätten gerne, dass bei jeder Änderung eines Datensatzes oder beim Hinzufügen eines neuen Datensatzes automatisch die betroffenen Cache-Dateien gelöscht werden. Wenn z. B. die Produkt-Informationen zum Produkt mit der ID 1 geändert werden, müssen die Cache-Dateien gelöscht werden:
- public/products.html
- public/products/1.html
Löschen von Cache-Dateien mit dem »expire_page«-Befehl
Der Befehl expire_page(pfad_zur_datei) löscht die unter der URL angegebene Datei (vorausgesetzt, die Datei ist auf dem Server vorhanden).
Beispiel für die Verwendung von expire_page:
- expire_page(/products)
Löscht die Datei public/products.html.
- expire_page(/products/2)
Löscht die Datei public/products/2.html.
Anstatt den Pfad anzugeben, ist es praktischer, Methoden zu verwenden.
Der scaffold-Generator hat in der Datei config/routes.rb den Befehl map.resources :products hinzugefügt. Dadurch wird eine Reihe von Methoden definiert, mit deren Hilfe die Pfade angegeben werden können. Der Aufruf des expire_page -Befehls kann dann wie folgt erfolgen:
- expire_page(products_path)
Löscht die Datei public/products.html.
- expire_page(product_path(@product))
Löscht die Datei public/products/2.html, vorausgesetzt, @product ist ein Produkt-Datensatz mit der ID 2.
Einsatz im Controller
Wenn ein Datensatz bearbeitet, hinzugefügt oder gelöscht wird, müssen die entsprechenden Cache-Dateien gelöscht werden. Wir zeigen den Einsatz des expire_page -Befehls am Beispiel des Products-Controller. Es werden jedoch nur die Methoden gelistet, für die der Befehl hinzugefügt wurde:
class ProductsController < ApplicationController caches_page :index, :show def new @product = Product.new expire_page(products_path) respond_to do |format| format.html # new.html.erb format.xml { render :xml => @product } end end def create @product = Product.new(params[:product]) respond_to do |format| if @product.save expire_page(products_path) flash[:notice] = 'Product was successfully created.' ... end end end def update @product = Product.find(params[:id]) respond_to do |format| if @product.update_attributes(params[:product]) expire_page(products_path) expire_page(product_path(@product)) flash[:notice] = 'Product was successfully updated.' ... end end end def destroy @product = Product.find(params[:id]) @product.destroy expire_page(products_path) expire_page(product_path(@product)) respond_to do |format| format.html { redirect_to(products_url) } format.xml { head :ok } end end end
caches_page :index
Um das Page-Caching für den Products-Controller zu aktivieren, haben wir den Befehl caches_page :index, :show verwendet, der angibt, welche Actions im Cache gespeichert werden sollen. Unschön ist jedoch, dass wir den Befehl expire_page verstreut in den einzelnen Actions eingefügt haben. Besser wäre, wenn die einzelnen Actions überhaupt keinen Code für das Caching beinhalten würden, da das Caching kein Bestandteil des Algorithmus der einzelnen Actions sein soll.
Dies kann durch den Einsatz von Sweepern verbessert werden.
Einsatz von Sweepern
Sweeper (zu Deutsch Straßenkehrer) sind spezielle Klassen, die für das Löschen von Cache-Dateien zuständig sind. Ein Sweeper kann ein oder mehrere Models beobachten, ob ein Datensatz gespeichert oder gelöscht wurde.
Wir definieren die nachfolgende Klasse in der Datei product_sweeper.rb, die wir im Verzeichnis app/models anlegen:
Listing app/models/product_sweeper.rb
class ProductSweeper < ActionController::Caching::Sweeper observe Product def after_save(product) expire_page(product_path(product)) expire_page(products_path) end def after_destroy(product) expire_page(product_path(product)) expire_page(products_path) end end
In dieser Klasse wird nach jedem Speichern (after_save) und dem Löschen (after_destroy) eines Datensatzes im Model Product die Detailseite und die Listenansichts-Seite gelöscht.
Der Befehl observe legt fest, welche Models auf Änderungen beobachtet werden sollen. Es kann auch mehr als ein Model beobachtet werden, wie z. B. observe Product, Category.
Im folgenden Schritt refaktorisieren wir die Klasse, indem wir das DRY-Prinzip anwenden:
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_page(product_path(product)) expire_page(products_path) end end
expire_page
Im Products-Controller benötigen wir nicht mehr den Befehl expire_page, sondern fügen lediglich den Befehl cache_sweeper am Anfang in Form einer Deklaration hinzu:
Listing app/controllers/products_controller.rb
class ProductsController < ApplicationController
caches_page :index, :show
cache_sweeper :product_sweeper,
:only => [:update, :create, :destroy]
...
end
Der Cache-Sweeper product_sweeper wird nur für die Actions update, create und destroy aufgerufen, weil nur in diesen Actions Datensätze verändert werden.
Löschen von Cache-Dateien mit einem Rake-Task
Mit dem folgenden Rake-Task können alle Cache-Dateien leicht gelöscht werden. Dies ist z. B. praktisch, wenn Sie neue Daten direkt in die Datenbank importieren oder wenn Sie ganz sichergehen wollen, dass keine veralteten Daten angezeigt werden. Wir speichern dazu den folgenden Rake-Task in der Datei lib/tasks/cache.rake ab:
Listing lib/tasks/cache.rake
desc "delete all products cache files" task "cache:products:clear" do product_index = File.join(RAILS_ROOT,'public','products.html') product_dir = File.join(RAILS_ROOT,'public','products') rm_rf([product_index,product_dir]) end
rake -T
Der Rake-Task erscheint dann in der Liste aller Rake-Tasks (rake -T).
rake -T rake cache:products:clear # delete all products ... rake db:abort_if_pending_migrations # Raises an error if ... rake db:charset # Retrieves the charse... rake db:collation # Retrieves the collat... ...
Der Rake-Task kann mit rake cache:products:clear aufgerufen werden.
Cachen der Root-Page 

In jeder Applikation wird normalerweise eine Action eines Controllers als Root-Page bzw. Start-Seite der gesamten Website festgelegt.
In unserem Beispiel soll dies die Action index des Products-Controllers sein. Dazu gehen wir wie folgt vor:
- Den Products-Controller als Startseite festlegen
Dazu wird in config/routes.rb der Products-Controller als Root-Route eingetragen:
Die Angabe :acion => 'index' ist optional.map.root :controller => 'products', :action => 'index'
- Löschen der Datei public/index.html
Die Datei public/index.html enthält die »Willkommen«-Seite von Rails.
index-Action
Die index -Action des Products-Controllers kann nun über die folgenden URLs aufgerufen werden:
- http://localhost:3000
In diesem Fall wird die Cache-Datei public/index.html generiert.
- http://localhost:3000/products
In diesem Fall wird die Cache-Datei public/products.html generiert.
Für das Löschen des Caches für die index -Action des Products-Controller müssen daher zwei Dateien gelöscht werden.
Eine Lösung besteht darin, im Sweeper die Datei public/index.html mit dem Löschbefehl FileUtils.rm_f zu entfernen. Im Gegensatz zu FileUtils.rm wird bei FileUtils.rm_f kein Fehler angezeigt, wenn die entsprechende Datei nicht vorhanden ist.
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_page(product_path(product)) expire_page(products_path) expire_index end def expire_index # index.html löschen index = File.join(RAILS_ROOT,'public','index.html') FileUtils.rm_f(index) 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.