Archive for the ‘Rails’ Category

Ganz frisch: mod_rails verspricht einfacheres Server-Setup mit Apache

Montag, April 14th, 2008

Passenger aka mod_rails

Letzten Donnerstag wurde das lang erwartete Apache-Modul für Rails-Anwendungen mit dem Namen Passenger erstmals veröffentlicht. Die Installation von Rails-Anwendungen unter Apache wird damit stark vereinfacht: mod_rails einbinden und das public-Verzeichnis einer Rails-Anwendung als DocumentRoot für Apache angeben, schon wird die Anwendung dort gestartet.

Umständliches Aufsetzen eines Mongrel-Clusters mit Proxy-Balancer ist nicht mehr nötig. Gleichzeitig kann Passenger die Zahl der Server-Instanzen je nach Auslastung dynamisch anpassen. Nett ist auch die automatische Zuweisung der Benutzerrechte für Shared-Hosting-Umgebungen: Der Server-Prozess läuft unter dem Besitzer der Datei config/environment.rb der entsprechenden Rails-Anwendung.

Performance

Dieser Vergleich zwischen Passenger und Mongrel zeigt, dass sich Passenger zumindest auf gleichem Niveau von Mongrel befindet (bei der typo-Anwendung ist es sogar erheblich schneller). In Sachen Speicherverbrauch gibt es aber auch eine Kehrseite, wie die Erfahrungen von HostingRails.com zeigen. Kurz gesagt verdreifacht sich der Speicherverbrauch im Vergleich zu einer Mongrel-Instanz.

Allerdings haben die Macher von Passenger noch die Ruby Enterprise Edition (eine modifizierte Ruby-VM mit verbesserter Garbage Collection) in Vorbereitung, die zufälligerweise den Speicherverbrauch um ein Drittel reduzieren soll, was also auf +/-0 rauslaufen würde :-).

Ähnlich wie beim Betrieb über FCGI beendet Passenger auch den Server-Prozess wenn innerhalb eines bestimmten Zeitraums keine HTTP-Anfragen mehr kommen (Voreinstellung sind 120s). Das spart einerseits eine Menge Speicher wenn auf dem Server viele kleine Anwendungen laufen, die wenig Traffic generieren. Andererseits benötigt der erste Seitenaufruf nach diesem Zeitintervall aber einige Sekunden da dann erst das Rails-Framework “gebootet” werden muss.

Ausblick

Bedeutet Passenger das Ende für spezielle Rails-Hosting-Angebote wie RunRails.de? Meiner Einschätzung nach wird Passenger auf jedenfall die Verbreitung von Rails-Hosting auch bei anderen Shared-Hostern fördern, da der Einsatz so einfach ist. So bald viele Kunden diese Möglichkeit auch Ausnutzen, könnte allerdings der immense Speicherverbrauch des Railsframeworks Probleme bereiten.

Trotzdem - RunRails.de wird nicht mit äußerst günstigen Shared-Hosting angeboten die Rails nur als Beigabe anbieten konkurrieren können. Daher wird es sich Rails-spezifischen Zusatzleistungen von der Masse abheben müssen. Neben den optionalen Subversion-Repositories habe ich noch ein paar weitere Features in Vorbereitung, die den Alltag beim Rails-Deployment erheblich vereinfachen werden (dazu später mehr).

RunRails.de läuft seit gestern bereits mit Passenger. Allerdings sind die oben beschriebenen “Boot”-Zeiten der Anwendung störend, und den Speicherverbrauch werde ich auch nochmal im Auge behalten müssen. Für Mongrel hat also noch nicht das letzte Stündlein geschlagen!

Automatisch richtige Pfad-Trennzeichen benutzen (Linux/Windows)

Donnerstag, April 10th, 2008

Häufiges Szenario ist die Entwicklung unter Windows (mit InstantRails) und das Deployment unter Linux.
Greift man innerhalb der Rails-Anwendung auf das Dateisystem zu, hat man hierbei ein Problem mit den Pfadangaben. Unter Windows trennt ein Backslash die einzelnen Verzeichnisse, unter Linux* jedoch ein normaler Schrägstrich (Slash).

Man könnte eine Trennzeichen-Variable definieren und diese dann in jedem nur erdenklichen Verzeichnis-String einsetzen. Den Wert dieser Variablen könnte man abhängig von der aktuellen Umgebung (Development vs. Production) machen, um ihn nicht vor jedem Deploy ändern zu müssen. Das macht die Pfadangaben-Strings innerhalb der Anwendung nicht nur unübersichtlich und fehleranfällig sondern ist auch von der Denkweise falsch: Das Trennzeichen hängt ja nicht von der Rails-Umgebung sondern vom Betriebssystem ab- falls es mehrere Railsentwickler mit unterschiedlichen Betriebssystemen gibt, funktioniert das ganze schon wieder nicht.

Die Lösung bringt File.join(*parts)

File.join fügt einzelne Strings als Pfadangaben mit dem jeweils Betriebssystem-spezifischen Trennzeichen zusammen:

File.join('foo', 'bar')  # => "foo/bar" (Linux/Mac OS)

File.join('foo', 'bar')  # => "foo\bar" (Windows)

Es wird keine Operation auf dem Dateisystem ausgeführt; der Pfad muss also nicht existieren. Möchte man das Trennzeichen isolieren, genügt ein File.join(”",”") (falls es eine kürzere Schreibweise gibt, lasst es mich wissen).

*und natürlich bei allen anderen *NIX-”Geschmacksrichtungen”, wie BSD oder Darwin (Mac OS)

Plugin: “Micro-CMS” mit comatose

Donnerstag, April 3rd, 2008

Jede Webanwendung benötigt eigentlich ein Content-Management-System (CMS). Es gibt immer Unterseiten wie das Impressum, die AGBs oder Hilfetexte, deren Inhalt man ab und zu aktualisieren muss. Diese Texte statisch im entsprechenden View einzubinden ist schlechter Stil und erschwert die Wartung.

comatose ist ein Plugin, welches ein kleines CMS implementiert. Man kann Texte in der Datenbank ablegen und darauf mit render zugreifen, als ob man ein Partial rendern würde. Das Plugin erzeugt auch gleich das Backend zum verändern der Texte und liefert hilfreiche Rake-Tasks zum synchronisieren des Contents zwischen Development und Production-Umgebung mit.

Installation

Die Installation läuft nicht besonders anders ab als bei anderen Plugins auch. Der aktuelle Code des Comatose-Projekts ist von Rubyforge zu Google-Code umgezogen. Man installiert es aus den aktuellen Quellen mit

ruby script/plugin install http://comatose-plugin.googlecode.com/svn/trunk/comatose

Der Installationsprozess schließt mit dem Hinweis, was als nächstes zu tun ist:

ruby script/generate comatose_migration

um eine Migration zu erzeugen, die die Tabellen für das CMS initialisiert. Diese muss anschließend mit

rake db:migrate

ausgeführt werden.
Fast geschafft! Jetzt noch in der routes.rb folgende Zeilen hinzufügen (comatose_root muss in die letzte Zeile der routes.rb):

map.comatose_admin
map.comatose_root ''

Falls eine URL aufgerufen wird, die von keiner anderen Route erkannt wurde (z.B. www.beispiel.de/impressum) greift die comatose_root-Route und sucht die Comatose-Seite mit dem Namen “Impressum” heraus und zeigt sie an.

Achtung! Damit die Einträge in der routes.rb so funktionieren, muss unbedingt (auch in Development-Umgebung) der Server neu gestartet werden. Für Rails 2.0+ muss außerdem (vor Serverneustart) die Plugins acts_as_list und acts_as_tree installiert werden (ruby script/plugin install acts_as_list), andernfalls schlägt man sich mit Meldungen wie

undefined method `acts_as_tree' for #<Class:0x376faf4>

herum.

Sinnvolle Konfiguration

In der environment.rb sollte unbedingt folgender Konfigurations-Block eingefügt werden:

Comatose.configure do |config|
config.helpers << ApplicationHelper  # Falls das restful_authentication-Plugin benutzt wird
# wird der Autor einer CMS-Seite anhand des Usernamens gespeichert.
config.admin_includes << :authenticated_system
config.admin_get_author do
current_user.login
end  # Application-Helper im comatose-Controller verfügbar machen
config.helpers << ApplicationHelper

# Falls restful_authentication-Plugin benutzt wird,

# kann so der Zugriff auf das CMS auf angemeldete Benutzer beschränkt werden  config.includes << :authenticated_system
config.authorization        = Proc.new { true }
config.admin_authorization  = :login_required

# Fragment-Caching erstmal ausschalten.

config.disable_caching = true

end

Das wichtigste ist natürlich, den Zugriff auf den Admin-Bereich einzuschränken, da ansonsten jeder Besucher der Website den Inhalt verändern kann! :login_required ist eine Methode des restful_authentication-Plugins und sollte je nach Seite mit einer eigenen Methode ergänzt werden, die nur Admin-User erlaubt.

Zweiter wichtiger Punkt ist das abschalten des Fragment-Cachings. In der Production-Umgebung ist es sonst aktiviert, funktioniert aber nicht richtig- beim Fragment-Caching speichert Rails die Ausgabe von Partials, wozu aber erstmal ein geeigneter Speicherplatz organisiert werden muss (/tmp-Ordner, etc.). Zum ausprobieren des Plugins ist es daher sinnvoll diese Funktion abzuschalten. (Tuning-Maßnahmen wie Caching erschweren das Debugging und sollten erst eingebaut werden, wenn die Performance wirklich am Limit ist).

Neustart des Servers nicht vergessen- und schon kann es losgehen.

Benutzung

Unter der URL /comatose_admin ist jetzt die Admin-Oberfläche des CMS verfügbar. Die Benutzung sollte eigentlich selbsterklärend sein. Artikel können geschachtelt in einer Baumstruktur angelegt werden. Der Zugriff auf die einzelnen Artikel erfolgt über den Titel bzw. den Pfad der Titel. Für die Ausgabe stehen verschiedene Parser zur Verfügung, so dass man der Inhalt nicht unbedingt HTML sein muss. RedCloth ermöglicht z.B. das formatieren von Fettdruck, Überschriften usw. ohne HTML-Tags.

Um eine Seite aus dem Comatose-CMS anzuzeigen, kann man innerhalb der Template einfach

<%= render :comatose=> 'pfad/name' %>

aufrufen, wobei Pfad die Namen aller übergeordneten Seiten und Seite der Name der anzuzeigenden Seite ist (getrennt durch “/”).

Wärend die Anzeige über render für Textbausteine innerhalb einer Seite gedacht ist, können Comatose-Seiten auch einzeln angezeigt werden. Durch die Angabe von

map.comatose_root ''

in der letzten Zeile von route.rb haben wir ja schon eine Default-Route auf Comatose gesetzt. D.h. wenn die URL von keiner der anderen Routen verarbeitet werden kann, wird sie als Comatose-Pfadangabe behandelt. Die Idee scheint logisch, in der Praxis habe ich das aber nicht zum laufen bekommen. Zunächst mal wird bei diesen Seiten nicht das Default-Layout benutzt, was man aber durch die zusätzliche Angabe von :layout => ‘application’ ändern kann. Das zweite Problem ist, dass die View-Helper nicht geladen werden. Ich verwende beispielsweise einige Helper um Menüs (Tabs, etc.) darzustellen, was bei einer Comatose-Seite nicht funktioniert.

Aus Kommentaren des Comatose-Wiki habe ich den Trick, die Helperklassen per confg.include zuzuweisen (siehe Konfigurationsabschnitt oben), was aber bei mir aber nicht funktioniert hat.

Letztendlich habe ich einfach eine spezielle “Page”-Action in einem Controller erzeugt, dessen Template nur

<%= render :comatose=> params[:path].join('/') %>

enthält. Der letzte Eintrag in der Routes.rb lautet dann

map.cms '*path', :controller => 'home', :action => 'page'

Die spezielle Anweisung *path enthält die URI-Bestandteile als Array (daher muss weiter oben per join(’/') wieder der ursprüngliche Pfad rekonstruiert werden).

Weitere Funktionen

Im Content der Seiten kann man auch auf Ruby-Variablen zugreifen. Das ist sehr praktisch um den Content anzupassen, z.B. durch aktuelle Statistiken, das Datum oder den Benutzernamen. Ähnlich wie bei einem Partial muss dabei die Variable dem Render-Aufruf übergeben werden, z.B. so:

<%= render :comatose=>'welcome', :locals=>{ :username=>'USERNAME' } %>

In der Content-Seite mit der Überschrift ‘welcome’ kann dann so auf die Variable zugegriffen werden:

Hallo {{ username }}!

Komplexere Anweisungen sind mit sogenannten “Drops” möglich: Einem Drop kann man Codeblöcke zuweisen, die dann innerhalb der Comatose-Seite aufgerufen werden können. Die Templatesprache von Comatose ermöglicht dabei auch for-in-Konstruktionen etc.
Weiteres dazu gibt aus auf den Wiki-Seiten des Comatose-Projekts

Bewertung

****. (4/5 Sternen)

Positiv: Nützliches Plugin, großer Funktionsumfang, elegante Einbindung in Rails (render :comatose, rake-Tasks)

Negativ: Für viele Anwendungen überdimensioniert, Benutzung von zahlreichen Plugins, die zusätzlich Speicher benötigen. Standard-Konfiguration läuft nicht fehlerfrei in Production-Umgebung (wenn Fragment-Caching nicht konfiguriert wurde).

Fazit

Es handelt sich um ein nützliches und sehr leistungsfähiges Plugin. Meine anfängliche Begeisterung hat sich aber gelegt, da für viele Seiten dieser immense Funktionsumfang nicht benötigt wird. Diese Komplexität führt zu einigen Fallstricken bei der Installation und Konfiguration, die das direkte Loslegen mit dem Plugin erschweren.

Viele Seiten kommen sicher ohne Versionsmanagement und Baumstrukturen der Seiten aus, so dass man sich die Verwendung von acts_as_tree und acts_as_versioned und somit wertvollen RAM sparen könnte. Auch werden mehrere Markup-Parser geladen (RedCloth, Liquid), die nicht jeder verwenden möchte.

Man sollte daher prüfen, ob alle Funktionen tatsächlich benötigt werden. Bei einigen meiner Projekte werden ich jedenfalls bei meiner eigenen, schlankeren CMS-Lösung bleiben.