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.

Chinaimbiss Orchidee ist nicht mehr

Da habe ich mal 2 Monate lang nicht bei meinem Lieferanten für asiatische Küche bestellt und dann macht er einfach den Laden zu. Wo soll ich denn jetzt immer knusprige Ente oder Hähnchen mit diversen scharfen Saucen essen? Wo soll ich bestellen, wenn ich mal wieder zu faul bin, selbst zu kochen? Ich weiß ja, daß ich einer der besten Kunden war. Aber nur weil ich mal 2 Monate lang einfach keine Zeit hatte, kann er doch nicht einfach den Laden schließen! ;-(

Danke, Tuan, für die leckeren Gerichte. Solltest du irgendwann wieder einen Laden eröffnen, verspreche ich dir, der erste Kunde zu sein :)

Zeig mir deine .irbrc

Die IRB ist ein sehr nützliches Tool, um mal interaktiv mit Ruby zu spielen. Die Standardeinstellungen der IRB sind jedoch etwas spartantisch. Meine .irbrc macht das Leben ein wenig komfortabler.

require 'irb/completion'
require 'rubygems'
require 'map_by_method'
require 'what_methods'
require 'pp'
require 'irb/ext/save-history'
IRB.conf[:AUTO_INDENT] = true
IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:SAVE_HISTORY] = 200
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-history"

I want my MTV

Ich erwähnte es ja schon einmal, daß ich das alte MTV vermisse. Damals™ verdiente MTV noch seinen Namen: Es gab Musik und es lief im TV! Doch das beste war, daß es nur ein MTV für ganz Europa gab. Das Programm war international und man mußte Englisch beherrschen, um es zu verstehen. In MTV’s Most Wanted riefen Menschen aus ganz Europa an. Da konnte man so richtig den europäischen Gedanken noch spüren.

Irgendwann kam das nationale MTV. MTV Europe wurde balkanisiert. Vorbei war es mit dem Blick über die Landesgrenzen. Nun gibt es nur noch deutsche Produktionen und die US-Importe. Was ist mit England? Frankreich? Spanien? Diese Länder kommen im deutschen MTV selten bis gar nicht vor.

Irgendwann wurde ich auch zu alt für MTV. Der Generation X versprach man aber ein eigenes Musikfernsehen. Erst MTV2 dann Viva Plus. MTV2 habe ich im Kabel nie zu Gesicht bekommen und was aus Viva Plus geworden ist, dürfte den meisten bekannt sein.

I want my MTV!

FastJacks Gesetz

Ich weiß nicht, ob mir nicht schon jemand zuvorgekommen ist, eine schnelle Google-Suche ergab zumindest nichts. Hiermit postuliere ich in Anlehnung an Godwins Gesetz mein eigenes Gesetz: Wer im laufe einer politischen Diskussion Terrorismus oder Kinderpornographie als Argumentationshilfe hinzuzieht, hat sich automatisch selbst disqualifiziert.

Es gibt eine beängstigende Tendenz vieler Politiker, gerade diese Totschlagargumente heranzuziehen. Es wäre schön, könnte man auch der normalen Bevölkerung Godwins und mein Gesetz mal näherbringen könnte.

Zitat des Tages

Science is like sex, sometimes something useful comes out but that is not the reason we are doing it!

Auf in neue Galaxien

Das Team von Stargate Atlantis ist auf zu neuen Ufern. Da es scheinbar keinen Ausweg gab, um dem Angriff der Replikatoren etwas entgegenzusetzen, ist das Atlantis-Team mit der gesamten Stadt in den Weltraum aufgebrochen. Ich bin mal auf die kommende Staffel gespannt. Das wird sicher interessant.