Passenger für Rails-3-Entwicklung

Wie man den Phusion Passenger für die Entwicklung benutzt wurde schon an einigen Stellen beschrieben. In Verbindung mit Rails 3 gibt es eine kleine Falle. Folgt man diesen Anleitungen, wird die eigene Rails-3-Anwendung immer im Production-Mode laufen. Grund dafür ist die config.ru-Datei im Hauptverzeichnis der Anwendung. Damit erkennt Passenger die Anwendung nicht als Rails- sondern als Rack-Anwendung. Die Lösung des Problems ist einfach. Man benennt die Datei um, damit Passenger es nicht mehr für eine Rack-Anwendung hält. Möchte man aus irgendeinem Grund, diese Datei nicht nicht anfassen, muß man Passenger mitteilen, daß es die Rack-Anwendung im Development-Mode starten soll. Dazu muß man in der Apache-Konfiguration folgende Zeile einfügen:

RackEnv development

Damit sollte die Anwendung nach dem Restart auch tatsächlich im Entwicklungsmodus laufen.

NetBeans 6

Wer es an anderer Stelle noch nicht mitbekommen hat, NetBeans 6 ist erschienen. Die Rails-Unterstützung ist fantastisch und hat bei mir TextMate als Editor für Rails abgelöst. (Allerdings auch nur für Rails)

NetBeans 6 als Rails-IDE

Als ich mir das letzte Mal NetBeans angeschaut habe, war das während meiner äußerst kurzen Java-Phase. Es war irgendwann im letzten Jahrhundert, noch bevor Sun NetBeans erwarb. NetBeans war eine reine Java-Entwicklungsumgebung — für und in Java. Bis vor kurzem sah ich auch keinen Grund, mich mit NetBeans wieder zu befassen. Doch vor ein paar Tagen sah ich dann diesen Artikel in meinem Feed-Reader.

Da sich Sun vor kurzem die JRuby-Entwickler ins Haus geholt hat, wurde Ruby-Support in das kommende NetBeans 6 eingebaut. Derzeit ist NetBeans 6 noch eine Entwicklerversion und es gibt noch hier und da ein paar scharfe Ecken und Kanten, doch die Software ist durchaus benutzbar.

Ich habe einen Versuch gestartet und eine kleine Rails-Anwendung komplett mit der NetBeans-IDE entwickelt. NetBeans macht es möglich, komplette Anwendungen zu erstellen, ohne auf die Konsole zu müssen. Alle Generatoren lassen sich aus der IDE aufrufen. Auch der Mongrel/WEBrick läßt sich hier starten und stoppen. Subversion/CVS-Support muß ich wohl nicht extra erwähnen, da ich das heutzutage als selbverständlich ansehe. Sogar Autotest läßt sich bequem aus einem Menü starten. Der eingebaute SQL-Client (eigentlich fast ein kleiner Datenbankmanager) ermöglicht es sich direkt mit Datenbanken zu verbinden. Die Entwickler haben sogar einen Gem-Manager eingebaut. Damit lassen sich bequem mit einer GUI Ruby-Gems installieren, deinstallieren und updaten. (Dazu unten noch mehr)

Negativ aufgefallen ist mir, daß die IDE sich auf meinen PowerPC PowerBook ab und zu eine kleine Auszeit nimmt. Dies geschieht meistens, wenn NetBeans 6 versucht mir seine Hilfe anzubieten und dazu alle möglichen Methoden auflistet, die ich an das Objekt senden könnte. Dies ist mir auf dem Mac mini mit Core Duo in der Firma nicht so aufgefallen. Man sollte also schon eine schnelle CPU haben. Ab und zu poppen auch Fenster mit irgendwelchen Java-Exceptions auf. Da es sich noch um eine Entwicklerversion von NetBeans handelt, ist das verzeihlich. Trotz dieser Exceptions läuft jedoch die Software weiter und stürzt nicht ab.

Hier noch ein paar Tips: NetBeans 6 bringt sein eigenes JRuby mit ein paar Gems mit. Ich hatte natürlich schon ein voll lauffähiges Ruby 1.8.6 mit einem Haufen an Gems auf dem Rechner installiert. Glücklicherweise haben die Entwickler daran gedacht. In den Einstellungen findet sich unter Ruby -> Platform die Möglichkeit, einen eigenen Ruby-Interpreter anzugeben. Wählt man hier sein bisheriges Ruby aus, findet NetBeans auch die dazugehörigen Gems. NetBeans erzeugt für diese Gems einen Index, der für die Codevervollständigung benutzt wird.

Der eingebaute Gem-Manager ist zwar angenehm, funktioniert aber bei einer normalen Ruby-Installation nicht, weil normale User in der Regel nicht über Schreibrechte in /usr(/local)/lib/ruby/gems verfügen. Erst wenn man sich die nötigen Rechte verschafft, funktioniert der Gem-Manager.

Autotest, Growl und RSpec

John Nunemaker beschreibt in Autotest Growl Pass/Fail Notifications wie man mit Growl sich die Ergebnisse von Autotest darstellen lassen kann. Hierzu muß man nur eine ~/.autotest-Datei anlegen, die die Benachrichtigunren anzeigt. Anscheinend haben jedoch letzte Änderungen in RSpec diese Beispielskripte unwirksam gemacht. Schuld ist das Ausgabeformat des Testlaufs. Doch mit wenigen Änderungen kann man das wieder hinbiegen. Nun klappen die Growl-Benachrichtigungen bei mir. Hier ist die ~/.autotest-Datei, die ich benutze. (Ich habe auch die Bilder an einen anderen Ort abgelegt)

require 'autotest/redgreen'
require 'autotest/timestamp'

module Autotest::Growl

  def self.growl title, msg, img, pri=0, sticky=""
    system "growlnotify -n autotest --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}"
  end

  Autotest.add_hook :ran_command do |at|
    image_root = "~/Library/Autotest/Images"
    results = [at.results].flatten.join("
")
    output = results.slice(/(\d+)\sexamples*,\s(\d+)\sfailures*/)
    if output
      if $~[2].to_i > 0
        growl "FAIL", "#{output}", "#{image_root}/fail.png", 1
      else
        growl "Pass", "#{output}", "#{image_root}/pass.png"
      end
    end
  end
end

Latexspiele auf den Schienen

Wer jetzt einen Artikel über außergewöhnliche Sexualpraktiken erwartet hat, der wird enttäuscht. Vielmehr geht es darum, aus einer Rails-Anwendung ein ansehnliches PDF herauszubekommen. Es gibt einen Haufen Wege, um PDFs in einer Rails-Anwendung zu erzeugen, aber irgendwie fand ich alle unbefriedigend. Die meisten sind einfach zu Low-Level. Ich will nicht festlegen müssen, an welchen Koordinaten ein Text erscheinen soll.

Wieso nicht etwas nehmen, was sich seit Jahrzehnten bewährt hat und das ich auch kann? LaTeX erzeugt seit 1984 angenehm lesbare Dokumente. Anscheinend bin ich nicht der einzige, der diese Idee hatte. Mit Rtex existiert bereits ein Plugin für Rails, was genau das erledigt. Damit kann man einfach LaTeX-Dokumente als Views verwenden. Da das Dokument vorher durch Erb geschleust wird, kann man natürlich auf die gewohnten <%= %>-Marker zurückgreifen.

SQL ohne Schleifen

Manche Aufgaben schreien geradezu danach, mit Schleifen gelöst zu werden. In SQL ist dies selten nötig. In meinem Beispiel wollten wir für jeden Monat des letzten Jahres die Häufigkeit eines bestimmten Ereignisses errechnen um es graphisch darzustellen. Mein Kollege, der an dieser Stelle besser anonym bleiben möchte, schlug schon vor, die Abfrage 12× mit unterschiedlichen Bedingungen abzuschießen.

Mir fiel ein, daß PostgreSQL über Funktionen verfügt, mit denen man aus einem Datum Jahr/Monat/Tag (und einiges Mehr) extrahieren kann. Die Funktion heißt sinnvollerweise extract. Damit kann man das Problem mit einer einzigen Abfrage erschlagen. Dies sieht dann so aus:

SELECT EXTRACT(month FROM created_on) AS month, COUNT(*) AS occurences
FROM my_event_log
WHERE EXTRACT(year FROM created_on)=2006
GROUP BY EXTRACT(month FROM created_on)
ORDER BY EXTRACT(month FROM created_on)

Dies läßt sich auch prima in Rails verwenden.

events = MyEventLog.count(:conditions => [ "EXTRACT(year FROM created_on)=?", 2006],
                          :group => "EXTRACT(month FROM created_on)",
                          :order => "EXTRACT(month FROM created_on)")

Es sollte an dieser Stelle jedoch nicht unerwähnt bleiben, daß durch die Verwendung von Funktionen wie extract in Verbindung mit ActiveRecord, der datenbankagnostische Aspekt von ActiveRecord verloren geht, da nicht alle Datenbanken die gleichen Funktionen unterstützen. Aber vermutlich ist datenbankagnostische Programmierung eh eine utopische Vision.

Es ist alles eine Ansichtssache

Wir stellen in der Firma derzeit Teile unserer alten Software auf eine hippe und coole Rails-Anwendung um. Solange unsere Anwendung nicht fertig ist, muß die alte und die neue Software parallel laufen und auf die gleichen Daten zugreifen können. Da wird die alte Software nicht ändern können und wollen, muß sich Rails entsprechend anpassen. Wir sind ja agil, oder? Ich hätte natürlich die Tabellen gerne möglichst Rails-konform, doch die alten Daten strotzen nur so vor Altlasten: unverständliche oder irreführende mixed-case Namen. Kombiniert man das mit PostgreSQL, hat man ein Problem, da der PostgreSQL-Adapter in Rails die Namen nicht quotet, was PostgreSQL dazu veranlaßt, alle Namen in Kleinbuchstaben zu verwandeln.

Meine Lösung dieses Problems verwendet einige relativ selten genutzte Features von PostgreSQL (andere DBMSse werden vermutlich entsprechende Äquivalente dazu haben). Schemas, Views und Rules. Anfangs wollte ich mit einer sauberen leeren Datenbank anfangen und dann mit Rails Migrations meine Tabellen anlegen. Dies hätte jedoch den Zugriff auf die vorhandenen Tabellen jedoch erheblich erschwert. Ich konnte jedoch auch nicht einfach meine Tabellen in die vorhandene Datenbank reinwerfen, da Rails dann über diese alten Tabellen stolperte und nach entsprechenden Modellen suchte. Die Lösung waren Schemas. Standardmäßig legt PostgreSQL alle Tabellen in einem Schema namens public. Ich legte für meine Anwendung einfach ein eigenes Schema an und wies den Rails DB-User an, standardmäßig in diesem Schema zu suchen.

CREATE ROLE myrailsuser WITH LOGIN PASSWORD 'myrailspass';
CREATE SCHEMA myschema AUTHORIZATION myrailsuser;
ALTER ROLE myrailsuser SET search_path TO myschema,public;

Um sicherzugehen, daß auch Rails immer schön brav in diesem Schema arbeitet und nicht über die durchgezogene weiße Linie fährt, habe ich in der environment.rb folgendes Angegeben:

config.active_record.table_name_prefix = "myschema."

Jetzt konnte ich wie gewohnt mit Migrations in Rails arbeiten und mich vorerst nicht weiter um die alten Tabellen kümmern.

Irgendwann kam es jedoch, wie es kommen mußte, ich mußte auf Daten aus einer der alten Tabellen zugreifen. In meinem Fall waren es die Benutzer, die ich benötigte, um ein Login zu bauen. Die für mich relevanten Felder in der alten Tabelle sehen so aus: (ich habe knapp ein Dutzend uninteressanter Felder hier weggelassen)

                 Table "public.TBLMitarbeiterSTM"
     Column     |            Type             |     Modifiers
----------------+-----------------------------+--------------------
 Personalnummer | integer                     | not null default 0
 Vorname        | character varying(50)       |
 Name           | character varying(50)       |
 E-Mail         | character varying(50)       |
 Passwort       | character varying(10)       |
 Loginname      | character varying(20)       |
 Level          | integer                     |
 inaktiv        | boolean                     |

So wird Rails damit nichts anfangen können. Die Lösung ist ein View:

CREATE OR REPLACE VIEW myschema.users AS
  SELECT "TBLMitarbeiterSTM"."Personalnummer" AS id,
    "TBLMitarbeiterSTM"."Vorname" AS first_name,
    "TBLMitarbeiterSTM"."Name" AS last_name,
    "TBLMitarbeiterSTM"."E-Mail" AS email,
    "TBLMitarbeiterSTM"."Loginname" AS login,
    "TBLMitarbeiterSTM"."Passwort" AS password,
    "TBLMitarbeiterSTM"."Level" AS level
  FROM public."TBLMitarbeiterSTM"
  WHERE NOT "TBLMitarbeiterSTM".inaktiv OR "TBLMitarbeiterSTM".inaktiv IS NULL;
GRANT SELECT ON myschema.users TO myrailsuser;

Damit ist Rails glücklich und akzeptiert den View als Grundlage für ein Modell. Das Modell muß natürlich von Hand angelegt werden und nicht über das generate-Skript:

class User < ActiveRecord::Base
end

Das war’s auch schon. Das Modell kann nun auf die Daten zugreifen, als wäre es eine ganz normale Tabelle.

Gerade als ich mir das wohlverdiente Guinness zum vorgezogenen Feierabend holen wollte, kam mir die Idee, daß es doch ganz toll wäre, könnte das Modell auch die Daten ändern und neue Daten hinzufügen. Dies ist mir auch fast gelungen.

Ein View ist eine Einbahnstraße. Ich kann daraus nur lesen. Glücklicherweise kann man hier mit Rules ein wenig tricksen. Eine Rule kann eine Operation auf eine Tabelle oder View abfangen und stattdessen etwas ganz anderes tun. Ich habe meine Rules angewiesen, Schreiboperationen auf den View abzufangen und stattdessen auf die richtige Tabelle zu schreiben.

DROP RULE IF EXISTS users_upd ON myschema.users;
CREATE RULE users_upd AS ON UPDATE TO myschema.users
  DO INSTEAD
  UPDATE public."TBLMitarbeiterSTM"
    SET "Vorname" = NEW.first_name,
        "Name" = NEW.last_name,
        "E-Mail" = NEW.email,
        "Loginname" = NEW.login,
        "Passwort" = NEW.password,
        "Level" = NEW.level
  WHERE "Personalnummer" = OLD.id;

DROP RULE IF EXISTS users_ins ON myschema.users;
CREATE RULE users_ins AS ON INSERT TO myschema.users
  DO INSTEAD
  INSERT INTO public."TBLMitarbeiterSTM" (
    "Personalnummer", "Vorname", "Name",
    "E-Mail", "Loginname","Passwort", "Level"
  ) VALUES (
    (SELECT MAX("Personalnummer")+1
    FROM public."TBLMitarbeiterSTM"),
    NEW.first_name, NEW.last_name,
    NEW.email, NEW.login, NEW.password, NEW.level
  );

DROP RULE IF EXISTS users_del ON myschema.users;
CREATE RULE users_del AS ON DELETE TO myschema.users
  DO INSTEAD
  UPDATE public."TBLMitarbeiterSTM"
    SET inaktiv = true
  WHERE "Personalnummer" = OLD.id;
GRANT INSERT, UPDATE, DELETE ON myschema.users TO myrailsuser;

Zwei Besonderheiten wäre hier zu erwähnen. Beim Delete lösche ich nicht wirklich den Benutzer. Ich markiere ihn einfach nur als inaktiv und da der View solche Benutzer ausschließt, sieht es so aus, als wäre der Benutzer gelöscht worden. Und da die ursprüngliche Tabelle über keine Sequenz verfügt, um die IDs automatisch zu verteilen, habe ich mir mir einer Art Sequenz für Arme beholfen.

Ein paar Tests von der psql-Kommandozeile zeigten Erfolg. Die Regeln fangen die Zugriffe ab und leiten diese auf die ursprüngliche Tabelle um. Es stellte sich jedoch heraus, daß Rails trotz dieser Regeln kein Insert durchführen kann, da es die Existenz einer Sequenz voraussetzt, die ich in diesem Fall gar nicht habe. Da die ursprüngliche Tabelle auch über keine Sequenz verfügte. Leider ist mir dazu bislang keine Lösung eingefallen. Aber ich hoffe hier auf die Hilfe des Lazyweb.

Rails flash-helper

Flashes sind in Rails ein schöner Weg, um den Benutzer über Ereignisse zu benachrichtigen. Zufällig bin ich dabei im Web über diesen Helper gestolpert. Nette Sache, funktioniert allerdings nicht ganz wie erwartet, wenn man noch zusätzlich das Human Attribute Override Plugin benutzt. Die Attributnamen wurden dabei vom Flash-Helper nicht korrekt angezeigt. Glücklicherweise lies sich dieses Problem leicht lösen. Der geänderte Helper sieht nun also so aus:

def display_standard_flashes(message = 'Folgende Probleme verhinderten das Speichern:')
  if flash[:notice]
    flash_to_display, level = flash[:notice], 'notice'
  elsif flash[:warning]
    flash_to_display, level = flash[:warning], 'warning'
  elsif flash[:error]
    level = 'error'
    if flash[:error].instance_of? ActiveRecord::Errors
      flash_to_display = message
      flash_to_display << activerecord_error_list(flash[:error])
    else
      flash_to_display = flash[:error]
    end
  else
    return
  end
  content_tag 'div', flash_to_display, :class => "flash #{level}"
end

def activerecord_error_list(errors)
  error_list = '<ul class="error_list">'
  errors.each_full do |m|
    error_list << "<li>#{m}</li>"
  end
  error_list<< '</ul>'
end

Nun werden auch die mit attr_human_name definierten Attributnamen in den Meldungen dargestellt.

Verschwurbelter Code

Sortierleiste

In meinem Rails-Programm möchte ich gerne Listenelemente (acts_as_list) horizontal sortieren können. Das ganze sollte über eine kleine Symbolleiste (s. Bild) über jedem Listenelement geschehen. Anfangs schrieb ich einfach den HTML/Rails-Code, der die 5 Buttons enthielt, direkt in den View hinein. Doch dann packte mich der Ehrgeiz und habe versucht, es zu verbessern. Am Ende hatte ich folgenden verschwurbelten Helpercode:

  def list_sort_buttons(options = {})
    "<table class=\"sort_buttons\"><tr>#{sort_buttons(options)}</tr></table>"
  end

  protected
  def sort_buttons(options = {})
    buttons = [ { :action => :to_top, :exclude_on => :first? },
                { :action => :up, :exclude_on => :first? },
                { :action => :delete, :exclude_on => :nil? },   # dummy
                { :action => :down, :exclude_on => :last? },
                { :action => :to_bottom, :exclude_on => :last? } ]
    cols = buttons.collect do |button|
      ["<td>", (sort_button(:controller => options[:controller],
                           :action => button[:action],
                           :id => options[:id]) unless options[:id].send(
                                                        button[:exclude_on])),
      "</td>"].join
    end.join
  end

  def sort_button(options = { :size => '16x16'})
    link_to image_tag(options[:action].to_s, :size => options[:size]),
            :controller => options[:controller], :action => options[:action],
            :id => options[:id]
  end

Endlich habe ich das Gefühl, die Anfängerpfade verlassen zu haben. Und das nur um DRY-willen.

An solchem Code sehe ich auch, daß es höchste Zeit fürs Wochenende wird

Partials und lokale Variablen

Meine erste Begegnung mit Partials in Ruby on Rails sah etwa so aus:

  render :partial => 'item', :collection => @items

Und in Partial selbst sah das meistens dann so aus:

<tr>
  <td><%= h(item.name) %></td>
  <td><%= item.price %></td>
</tr>

Ich ließ mich durch dieses Beispiel zu der Annahme verleiten, daß wenn die Collection oben aus @items besteht, die lokale Variable im Partial dann der Singular davon (in diesem Fall also item) sein müßte. Umso erstaunter war ich, daß folgendes nicht funktionierte.

  render :partial => "milestone/show", :collection => @project.milestones

Meiner Logik nach wäre nun im Partial die lokale Variable milestone. Da war Rails jedoch ganz anderer Meinung. Erst nach einem genaueren Studium von ActionView::Partials klärte sich die Fehleinschätzung auf. Der Name der lokalen Variable leitet sich aus dem Namen des Partials ab. Somit wäre der Name der Variable im zweiten Beispiel show. Das Partial für das zweite Beispiel sieht nun in etwa so aus:

<h3><%= h(show.name) %></h3>