Externe Datenquellen für nanoc
Bereits vor gut zwei Jahren hatte ich berichtet, wie man nanoc mit einer (MySQL-)Datenbank als Datenquelle betreiben kann, und auf die entsprechende Dokumentation verwiesen. Schon damals hatte ich angemerkt, dass sich in dem dort gezeigten Weg möglicherweise ein Zwischenschritt einsparen lässt. Dies habe ich dann mittlerweile auch erfolgreich getestet.
Das Beispiel aus der nanoc-Dokumentation
Das in der nanoc-Dokumentation gezeigte Beispiel generiert aus jedem Datensatz in der Datenbank ein Item (also im Prinzip ein Dokument), das keinen Inhalt hat, aber die einzelnen Datenbankfelder als Attribute enthält, und erzeugt diese “leeren” Seiten unter /external/hr/employees/
. Das ergibt sich aus dem Zusammenwirken des Prefixes items_root: /external/hr
in der nanoc.yaml
einerseits und der Funktion zum Erzeugen von Items in der Definition der Datenquelle andererseits:
- def items
- @db[:employees].map do |employee|
- new_item(
- ”,
- employee,
- “/employees/#{employee[:id]}”
- )
- end
- end
Für jeden Datensatz wird ein Item /employees/ID/
(also für den Datensatz mit dem Feld id: 1
ein Item /employees/1/
) erzeugt, dem der Pfad /external/hr
vorangestellt wird (also im Beispiel /external/hr/employees/1/
).
Alle diese Seiten dort sind leer, weshalb ihre Erzeugung im Beispiel auch unterdrückt wird. Von ihnen werden nur die Attribute (“Metadaten”) benötigt, um ein Verzeichnis mit allen Mitarbeitern zu erstellen.
Meine Anwendung für d-e-n.net
Diese Lösung hatte ich für die “Schreiberliste von de.etc.notfallrettung” übernommen.
Da ich dort aber nicht nur eine Liste der Nutzer, sondern für jeden auch eine Profilseite benötige, war ich zweischrittig vorgegangen: in einem ersten Schritt wurden - wie im Beispiel - die Daten eingelesen und leere Seiten (unter /_external/db/users/ID
) erzeugt und in einem zweiten Schritt aus den Attributen dieser leeren Seiten entsprechende Profilseiten erstellt, indem im preprocess
-Block der Rules
eine entsprechende Funktion aufgerufen wurde.
Schon damals fand ich das recht ineffizient: einmal “leere” Seiten nur mit Attributen erstellen, deren Ausgabe unterdrücken und dann für jedes dieser “virtuellen” Items noch einmal eine Seite erzeugen, die dann ausgegeben wird? Warum nicht den ersten Schritt entfallen lassen und direkt die Profilseiten erstellen?
Gesagt, (zwei Jahre später) getan: es genügt, new_item()
statt des “leeren” Inhalts den passenden Seiteninhalt zu übergeben (und ggf. die notwendigen Zusatzattribute hinzuzufügen):
- def items
- @db[:employees].map do |employee|
- new_item(
- “= render ‘profile’”,
- {:updated_at => employee[:changedate], :title => employee[:name],
- :description => “Profile of #{employee[:name]}”}.merge(employee),
- “/employees/#{employee[:id]}”
- )
- end
- end
Dann musste nur noch - neben einigen Anpassungsarbeiten im Code - in der nanoc.yaml
entsprechend items_root:
auf /
gesetzt werden, damit die Daten auch unter /user/ID
aufscheinen, und schon war der unnötige Zwischenschritt entfallen.
Der konkrete Code
Die Datenquelle wird dabei bei mir wie folgt in der nanoc.yaml
eingebunden:
- data_sources:
- -
- # The type is the identifier of the data source. By default, this will be
- # <code>filesystem_unified</code>.
- type: filesystem_unified
- […]
- -
- # Add mysql database (lib/data_sources/user_db)
- type: userdb
- items_root: /
Definiert wird die Datenquelle dann in lib/data_sources/user_db.rb
:
- # taken from http://nanoc.ws/docs/guides/using-external-sources/</p>
- <p>require ‘sequel’</p>
- <p>class UserDataSource < ::Nanoc::DataSource
- identifier :userdb</p>
- <p>def up
- @db = Sequel.mysql2(:host=>’HOSTNAME’, :user=>’USER’, :password=>’PASS’, :database=>’DATABASE’)
- end</p>
- <p>def items
- @db[:users].map do |user|
- fullname = [user[:vorname],user[:name]].join ’ ’
- # create profile page
- Nanoc::Item.new(
- “= render ‘profile’”,
- {:author => fullname, :created_at => ‘2002-11-14’,
- :updated_at => user[:changedate], :title => “Profil von #{fullname}”,
- :short => “#{fullname}”, :template => ‘page-container’,
- :description => “Profil des Autors #{fullname} in der Newsgroup de.etc.notfallrettung”}.merge(user),
- “/user/#{user[:id]}/”
- )
- end
- end</p>
- <p>end
Ich arbeite derzeit noch mit dem alten nanoc 3.x, weshalb dort statt new_item()
noch Nanoc::Item.new()
verwendet wird.
Mit der dargestellten Funktion in der Definition der Datenquelle wird für jeden Datensatz eine Seite (ein Item) erzeugt, die (das) als einzigen Inhalt = render 'profile'
enthält. Da in den Rules
angegeben ist, dass die Seiten unterhalb von /user/
einen HAML-Filter durchlaufen, wird mithin der Inhalt der Datei profile
im Verzeichnis /layouts/
eingefügt - das Template für alle Profilseiten. Mit dem ERB-Filter wäre entsprechend <%= render 'profile' %>
einzufügen. Die erzeugte Seite bzw. das erzeugte Item erhält überdies alle Felder aus dem Datensatz als Attribute übergeben, verbunden mit weiteren, zusätzlichen Attributen wie :author
oder :title
. Am Schluss bekommt die Seite / das Item einen Identifier unter Nutzung des Datensatzfeldes id
, für id: 1
also dementsprechend /user/1/
.
Fertig!
Kommentare
Ansicht der Kommentare: Linear | Verschachtelt