Archive for the ‘Allgemein’ Category

Barcamp Berlin 3 / Facebook und Rails

Montag, September 1st, 2008

Ende Oktober bin ich auf meinem ersten Barcamp! Habe es geschafft mich dort heute Mittag in allen Veranstaltungen einzutragen, obwohl der Server total überlastet war.

Als Barcamp-Neuling ist man ja angehalten, etwas dazu beizutragen. Ich habe mir überlegt einen Vortrag über Facebook-Anwendungen mit Ruby on Rails zu halten. Ich glaube das Thema ist gerade in Deutschland recht interessant, wo Facebook ja noch nicht so lange expandiert. Neben einem sehr empfehlenswerten Buch, welches ich dazu gelesen habe, (momentan nur als PDF erhältlich) ist seit einigen Tagen auch meine erste Facebook-Anwendung online.

Da bis zum Barcamp-Termin noch ein bisschen Zeit verstreicht, kann ich vielleicht auch berichten, was für Marketing-Erfolge man sich in Deutschland davon versprechen kann. Facebook liegt zwar bei den Nutzerzahlen stark hinter StudiVZ, wer-kennt-wen und anderen Netzwerken, nur was ist die Alternative? Keine der anderen Plattformen* biete eine API für eigene Anwendungen an.

____

* Anfang des Jahres hat StudiVZ eine eigene API angekündigt. Bisher gab es zu dem Thema aber noch keine Neuigkeiten.

SQL-Abfragen optimieren mit :select und :include

Sonntag, August 3rd, 2008

Um bei Rails mit der Datenbank zu kommunizieren, muss man sich dank ActiveRecord-Pattern nicht mehr um SQL-Befehle kümmern. Diese Kapselung ist sehr praktisch und verhindert auch Fehler, die sich bei komplizierten Abfragen schnell einschleichen.

Dieser Vorteil wird allerdings teuer erkauft, da ActiveRecord nicht weiss, wie die Daten von der Anwendung verwendet werden sollen. So benötigt man beispielsweise nicht immer alle Felder (oder Spalten) einer Tabelle- oder aber man weiss schon im Vorfeld, dass man auch Datensätze aus verbundenen Tabellen benötigt (belongs_to).

Nur benötigte Felder laden mit :select

Wird ein Datensatz über find() geladen, so führt ActiveRecord ein “SELECT * FROM …” aus. Das ist schlecht, da es eine Menge speicher verschwenden kann.

Beispiel: Eine Blog-Anwendung soll die 100 am meisten gelesenen Blog-Beiträge in einer Liste ausgeben, wobei nur die Überschriften angezeigt werden. Die Blog-Einträge sind in einer Tabelle gespeichert, welche Spalten für Id, Datum, Anzahl der Leser, Titel und Inhalt des Blogbeitrags hat. Zum erstellen dieser Liste benötigt man nur den Titel und die Id (um den Link zu erstellen).

Die Abfrage könnte daher lauten:

@most_viewed = BlogPost.find(:all, :order => 'views DESC', :limit => 100, :select => 'id, title')

Angenommen, jeder Beitrag (Inhalts-Feld) ist im Schnitt 10 KB groß dann fallen wärend dieser Abfrage mindestens 1 MB weniger Daten aus Datenbank an (die sowieso nicht verwendet werden). Das hört sich vielleicht zunächst nicht nach einem Problem an, allerdings geht es hier nicht nur um den Arbeitsspeicher des Webservers. Läuft der Datenbank-Server nämlich auf einem anderen Rechner als der Webserver, zum Beispiel bei einem größeren Projekt mit mehreren Webservern, dann müssen für die optimierte Abfrage mindestens 1 MB weniger an Daten übers Netzwerk übertragen werden. Bedenkt man jetzt noch, dass eine solche auch von vielen Besuchern gleichzeitig ausgelöst werden kann, summieren sich schnell mehrere MB unnützer Daten deren Übertragung für die Performance nicht folgenlos bleiben kann.

Mögliche Komplikationen: Bei einem Datensatz der mit :select-Anweisung geladen wurde sind die nicht gewählten Felder nil. Beim Aufruf von save werden diese Felder daher auch in der Datenbank mit NULL überschrieben! Mit reload können die fehlenden Felder nachgeladen werden. (Selbstverständlich bringt es nichts mit :select und reload zu arbeiten, wenn save auf JEDEN Datensatz angewendet werden soll).

belongs_to-Models laden mit :include

Angenommen beim BlogPost-Beispiel gibt es noch ein zugehöriges “Author”-Model, welches über belongs_to/has_many mit BlogPost verbunden ist und wir den Benutzernamen mit anzeigen wollen, dann würden wir bei der Ausgabe <blogpost-instanz-variable>.author.nickname aufrufen.

Da ActiveRecord nicht weiss, dass das Author-Model verwendet werden soll, wird es mit einer zweiten SQL-Abfrage in dem Moment geladen, wo auf das Feld nickname zugegriffen wird. Das ist insb. in Schleifen ungünstig, da n+1 SQL-Abfragen dazu notwendig sind (Eine Abfrage um die Liste der BlogPosts zu erstellen, dann n Abfragen um für jeden BlogPost den Author zu laden).

Per SQL kann mit der JOIN-Anweidungs der zugehörige Datensatz direkt (in einer Abfrage) geladen werden. ActiveRecord erzeugt Joins mit der :include-Option:

@posts = BlogPost.find(:all, :order => 'created_at', :limit => 100, :include => 'Author')

Mögliche Komplikationen: Im Beispiel wird nach der Spalte created_at sortiert, welche möglicherweise in beiden Models/Tabellen vorhanden ist. Man erhält dann eine ActiveRecord::StatementInvalid-Exception und folgende Fehlermeldung:

Mysql::Error: Column 'created_at' in order clause is ambiguous: SELECT ...

created_at gibt es in beiden Tabellen, daher ist es unklar nach welchem der Felder sortiert werden soll. Da der Inhalt von :order direkt an die SQL-Abfrage angehängt wird, kann man dort die Tabelle direkt mit angeben, z.B. so:

@posts = BlogPost.find(:all, :order => 'blog_posts.created_at', :limit => 100, :include => 'Author')

Autotest-Notification unter GNOME über libnotify

Samstag, Juli 26th, 2008

Ich benutze das Autotest-Script aus der ZenTest Testing-Suite für Rails.
Autotest ist dazu gedacht den Entwickler zum Einsatz von TDD (Test-Driven Development) zu motivieren. Bei TDD wird das gewünschte Programmverhalten (also das Ziel der Arbeit) zuerst in Form von Tests spezifiert. Anschließend wird so lange programmiert, bis alle Tests “bestanden” werden.

Test-Driven Development (TDD)

Dieses Vorgehen zwingt den Entwickler dazu, sich erst mal Gedanken darüber zu machen, was überhaupt erreicht werden soll- was wiederum zu saubererem Code führen soll.

Wie dem auch sei, Tests sind ungeheuer praktisch weil Sie bei größeren Projekten auch Sicherheit geben, dass eine kleine Änderung nicht “schreckliche” Auswirkungen an anderen Stellen hervorruft, die man zuerst gar nicht bemerkt.
Tests für bereits bestehenden Code zu schreiben ist langweilig, da man ja schon weiss, dass der Code funktioniert- deshalb kann ich jedem nur den Umgekehrt weg via TDD empfehlen.

Autotest

Rails macht das testen bereits sehr einfach, da für alle über script/generate erzeugten Dateien entsprechende Test-Gerüste erstellt werden. Dieses Tests können auch über rake test aufgerufen werden.

Die Idee hinter autotest ist, das manuelle ausführen von Tests zu sparen, und immer im Hintergrund zu testen so bald der Quellcode geändert wurde. Man öffnet autotest daher in einem Konsolenfenster, wo es in einer Endlosschleife läuft und alle Dateien im Rails-Verzeichnisbaum überwacht.

Werden diese Dateien verändert, führt autotest alle Tests aus, die von der Änderung betroffen sein könnten. Nun möchte man nicht ständig ein Auge auf das Konsolenfenster werfen um zu sehen, wieviele Tests erfolgreichen waren und wieviele nicht. Die Ausgabe auf der Konsole ist außerdem nicht einfach zu lesen und wirr formatiert.

Autotest ohne Desktop-Integration

Notifications auf dem Desktop

Hier setzt der Trick mit libnofity an. Autotest unterstützt Hooks, die je nach Resulat des Testlaufs ausgeführt werden, d.h. man kann ein Programm aufrufen, wenn alle Tests erfolgreich waren oder ähnliches. Zeigt dieses Programm eine “Sprechblase” auf dem Desktop an, bekommt man wärend des Programmierens ständig Rückmeldung darüber, wieviel noch zu tun ist, oder ob alle Tests bestanden wurden.

Davon hörte ich zuerst in einem Screencast, wo solche Notifications unter OS X ausgerufen wurden. Später stieß ich zufällig auf einen interessanten Blog-Beitrag, wie solche Meldungen unter KDE erzeugt werden können. Ich setze GNOME als Arbeitsumgebung ein und habe mir ein ähnliches Skript gebastelt. Unter GNOME kann man über den Befehl “notifiy-send” eine Sprechblase auf dem Desktop erscheinen lassen.

GNOME und libnotify

Zunächst muss natürlich das entsprechende Paket installiert werden (hier für Debian-basierte Distributionen):

apt-get install libnotify-bin

Den Erfolg kann man direkt überprüfen durch die Eingabe von  notify-send “hallo” oder ähnlichem auf der Konsole.

anschließend muss im Home-Verzeichnis des Benutzers eine Datei Namens .autotest mit folgendem Inhalt angelegt werden:

#!/usr/bin/ruby
module GNOMENotify
def self.gnotify(msg, icon=’information’)
system “notify-send –urgency low –expire-time=3000 –icon=dialog-#{icon} \”#{msg}\”"
end

Autotest.add_hook :red do |at|
gnotify “#{at.files_to_test.size} tests failed”, “warning”
end

Autotest.add_hook :all_good do |at|
gnotify “All specs passed.”
end

end

So, alles fertig! Jetzt kann es los gehen: Auf der Konsole muss im Verzeichnis des Rails-Projekts autotest (einfach autotest eingeben) ausgeführt werden, und schon werden erstmal alle Tests aufgerufen. Anschließend erhält man schon ein Feedback auf dem Desktop.

Hat man nach TDD-Manier schon ein paar Tests geschrieben, aber ist mit der Implementierungsarbeit noch nicht fertig, sieht es wahrscheinlich so aus:

Autotest mit Desktop-Integration 2

Am Ende eines produktiven Arbeitstages hoffentlich so:

Autotest mit Desktop-Integration 2