Skip to content

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:

  1. def items
  2.   @db[:employees].map do |employee|
  3.     new_item(
  4.       ,
  5.       employee,
  6.       "/employees/#{employee[:id]}"
  7.     )
  8.   end
  9. 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):

  1. def items
  2.   @db[:employees].map do |employee|
  3.     new_item(
  4.       "= render ‘profile’",
  5.       {:updated_at => employee[:changedate], :title => employee[:name],
  6.        :description => "Profile of #{employee[:name]}"}.merge(employee),
  7.       "/employees/#{employee[:id]}"
  8.     )
  9.   end
  10. 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:

  1. data_sources:
  2.   -
  3.     # The type is the identifier of the data source. By default, this will be
  4.     # `filesystem_unified`.
  5.     type: filesystem_unified
  6.     []
  7.   -
  8.     # Add mysql database (lib/data_sources/user_db)
  9.     type:         userdb
  10.     items_root:   /

Definiert wird die Datenquelle dann in lib/data_sources/user_db.rb:

  1. # taken from http://nanoc.ws/docs/guides/using-external-sources/
  2.  
  3. require ‘sequel’
  4.  
  5. class UserDataSource < ::Nanoc::DataSource
  6.   identifier :userdb
  7.  
  8.   def up
  9.     @db = Sequel.mysql2(:host=>‘HOSTNAME’, :user=>‘USER’, :password=>‘PASS’, :database=>‘DATABASE’)
  10.   end
  11.  
  12.   def items
  13.     @db[:users].map do |user|
  14.       fullname = [user[:vorname],user[:name]].join ’ ‘
  15.       # create profile page
  16.       Nanoc::Item.new(
  17.         "= render ‘profile’",
  18.         {:author => fullname, :created_at => ‘2002-11-14’,
  19.           :updated_at => user[:changedate], :title => "Profil von #{fullname}",
  20.           :short => "#{fullname}", :template => ‘page-container’,
  21.           :description => "Profil des Autors #{fullname} in der Newsgroup de.etc.notfallrettung"}.merge(user),
  22.         "/user/#{user[:id]}/"
  23.       )
  24.     end
  25.   end
  26.  
  27. 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!

Trackbacks

Keine Trackbacks

Kommentare

Ansicht der Kommentare: Linear | Verschachtelt

Noch keine Kommentare

Kommentar schreiben

HTML-Tags werden in ihre Entities umgewandelt.
Markdown-Formatierung erlaubt
Standard-Text Smilies wie :-) und ;-) werden zu Bildern konvertiert.
BBCode-Formatierung erlaubt
Gravatar, Favatar, Pavatar, Twitter, Identica, Identicon/Ycon Autoren-Bilder werden unterstützt.
Formular-Optionen
tweetbackcheck