Bei komplexen Views, die auch mal 500 ms und mehr zum Rendern
brauchen, ist die Zeitersparnis natürlich bedeutend. Als
Webseitenbetreiber spart man wieder wertvolle Serverresourcen und kann
mehr Besucher mit der gleichen Hardware bedienen. Der Besucher der
Webseite profitiert durch eine schnellere Auslieferung der Seite.
Page
Caching im Development-Modus aktivieren
Als Erstes müssen wir in der Datei
config/environments/development.rb
den Eintrag
config.action_controller.perform_caching
auf
true
setzen:
config.action_controller.perform_caching = true
Sonst können wir das Page Caching im Development-Modus nicht
ausprobieren. Im Production-Modus ist Page Caching per Default
aktiviert.
Company Index- und Show-View cachen
Page Caching wird im Controller aktiviert. Wenn wir für Company
die Index- und Show-Views cachen wollen, dann müssen wir im Controller
app/controllers/companies_controller.rb
am Kopf den
caches_page :index, :show
-Befehl eingeben:
class CompaniesController < ApplicationController
caches_page :index, :show
# GET /companies
# GET /companies.json
def index
@companies = Company.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @companies }
end
end
[...]
Vor dem Starten der Applikation sieht das
public
-Verzeichnis so aus:
public/
|-- 404.html
|-- 422.html
|-- 500.html
|-- favicon.ico
|-- index.html
`-- robots.txt
Nach dem Starten der Applikation mit
rails
server und dem Abruf der URLs
http://0.0.0.0:3000/companies
und
http://0.0.0.0:3000/companies/1
mit einem Webbrowser sieht es so aus:
public/
|-- 404.html
|-- 422.html
|-- 500.html
|-- companies
| `-- 1.html
|-- companies.html
|-- favicon.ico
|-- index.html
`-- robots.txt
Die Dateien public/companies.html
und
public/companies/1.html
wurden vom Page Caching
erstellt. Ab sofort wird der Webserver beim Aufruf dieser Seiten nur
noch die gecachten Versionen liefern.
Wenn man Page Cache einsetzt, dann sollte man auch direkt
gezippte gz-Dateien cachen. Das geht mit der Option :gzip =>
true
oder anstatt true
einen bestimmten
Kompressionsparameter als Symbol (z. B.
:best_compression
).
Der Controller
app/controllers/companies_controller.rb
würde
dann am Anfang so aussehen:
class CompaniesController < ApplicationController
caches_page :index, :show, :gzip => :best_compression
# GET /companies
# GET /companies.json
def index
@companies = Company.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @companies }
end
end
[...]
Damit werden automatisch eine komprimierte und eine
unkomprimierte Variante eines jeden Page Caches abgespeichert:
public/
|-- 404.html
|-- 422.html
|-- 500.html
|-- companies
| |-- 1.html
| `-- 1.html.gz
|-- companies.html
|-- companies.html.gz
|-- favicon.ico
|-- index.html
`-- robots.txt
Bei der Verwendung des in
Kapitel 15, Webserver im
Produktionsbetrieb erklärten Nginx-Servers geht
das am leichtesten mit der folgenden Anpassung der
try_files
-Anweisung in der
Nginx-Konfiguration-Datei:
try_files $uri/index.html $uri $uri.html @unicorn;
Nginx schaut damit nach, ob eine Datei mit der Endung
.html
von der aktuell aufgerufenen URI
existiert.
Page Caches
automatisch löschen
Sobald sich die im View verwendeten Daten verändern, müssen
natürlich die gespeicherten Cache-Dateien gelöscht werden. Sonst wäre
der Cache nicht mehr aktuell.
Laut offizieller Rails-Doku ist die Lösung für dieses Problem die
Klasse
ActionController::Caching::Sweeper
. Dieser
auf
http://guides.rubyonrails.org/caching_with_rails.html#sweepers
beschriebene Weg hat aber einen großen Nachteil: Er beschränkt sich nur
auf Aktionen, die innerhalb des Controllers geschehen. Wenn also eine
Action per URL vom Webbrowser getriggert wird, dann wird auch der
entsprechende Cache verändert oder gelöscht. Wenn aber z. B. in der
Console ein Objekt gelöscht wird, bekommt der Sweeper davon gar nichts
mit. Deshalb stelle ich Ihnen hier einen Ansatz vor, der keinen Sweeper
benutzt, sondern direkt im Model mit ActiveRecord Callbacks
arbeitet.
Wichtig
Wir müssen darauf achten, keine Seiten mit einer Flash-Nachricht
zu cachen. Weiterhin macht es auch keinen Sinn, bei diesen gecacheten
Seiten einen CSRF-Meta-Tag einzubauen. Beides wird im folgenden Code
beachtet.
Fangen wir mit den Controllern an. Bitte ändern Sie den Anfang
von
app/controllers/companies_controller.rb
wie
folgt ab:
class CompaniesController < ApplicationController
caches_page :index, :show, :gzip => :best_compression,
:if => Proc.new { flash.count == 0 }
before_filter(only: [:index, :show]) { @page_caching_is_active = true if flash.count == 0 }
# GET /companies
# GET /companies.json
def index
@companies = Company.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @companies }
end
end
[...]
Bitte fügen Sie die
cache_page
-Anweisung auch in den Controller
app/controllers/employees_controller.rb
ein:
class EmployeesController < ApplicationController
caches_page :index, :show, :gzip => :best_compression,
:if => Proc.new { flash.count == 0 }
before_filter(only: [:index, :show]) { @page_caching_is_active = true if flash.count == 0 }
# GET /employees
# GET /employees.json
def index
@employees = Employee.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @employees }
end
end
[...]
Jetzt müssen wir in den Models noch eintragen, dass die
entsprechenden Caches automatisch gelöscht werden, sobald sich ein
Objekt erstellt, verändert oder gelöscht wird.
app/models/company.rb
class Company < ActiveRecord::Base
attr_accessible :name
validates :name,
:presence => true,
:uniqueness => true
has_many :employees, :dependent => :destroy
after_create :expire_cache
after_update :expire_cache
before_destroy :expire_cache
def to_s
name
end
def expire_cache
ActionController::Base.expire_page(Rails.application.routes.url_helpers.company_path(self))
ActionController::Base.expire_page(Rails.application.routes.url_helpers.companies_path)
end
end
app/models/employee.rb
class Employee < ActiveRecord::Base
attr_accessible :company_id, :first_name, :last_name, :phone_number
belongs_to :company, :touch => true
validates :first_name,
:presence => true
validates :last_name,
:presence => true
validates :company,
:presence => true
after_create :expire_cache
after_update :expire_cache
before_destroy :expire_cache
def to_s
"#{first_name} #{last_name}"
end
def expire_cache
ActionController::Base.expire_page(Rails.application.routes.url_helpers.employee_path(self))
ActionController::Base.expire_page(Rails.application.routes.url_helpers.employees_path)
self.company.expire_cache
end
end
In der
app/views/layouts/application.html.erb
müssen wir
noch berücksichtigen, ob ein CSRF-Meta-Tag eingebaut wird. Dies ist
bei gecacheten Seiten nicht der Fall.
<!DOCTYPE html>
<html>
<head>
<title>PhoneBook</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tag unless @page_caching_is_active %>
</head>
<body>
<%= yield %>
</body>
</html>