Letztens hat mich ein Kollege gefragt: “Was ist das Problem, wenn man 2 Rails-Entwickler und 2 Java-Entwickler in ein Projekt steckt? Man hat hinterher 4 Rails-Entwickler…”. Ok, Rails macht Spaß. Aber es ist nicht immer soooo ganz einfach auch mal hinter die Kulissen zu schauen. So habe ich auch ein wenig gebraucht, bis ich zum ersten mal ein einfaches eigenes Plug-In schreiben konnte, das automatisch die anderen Klassen in meinem Projekt bei Systemstart erweitert.
Mein Privat-Projekt: Ein System zum Verwalten von Segel-Regatten und den zugehörigen Anmeldungen.(www.regattamanager.org)
Das Problem: Zu jedem Schiff, User und Anmeldung gehört ein Segel-”Club”, der referenziert werden soll. Allerings gibt es Clubs wie Sand am Meer und somit gibt es niemals eine Tabelle mit allen möglichen Clubs, sondern die Teilnehmer sollen selber auch eigene Clubs “on-the-fly” anlegen können.
Die Lösung: Meine Entitäten Ship, User und Registration sollen zwar intern eine Referenz auf die club_id besitzen, nach aussen aber diese Referenz wie zwei eigene Felder club_name und club_shortcut “maskieren” (und damit die automatische Erstellung von nicht vorhandenen Clubs übernehmen).
[Anmerkung: Dass ich überhaupt in der Situation bin, das Problem wie oben zu haben und zu lösen ist wohl "broken-by-design", aber so ist das in Freizeitprojekten...
]
Die Strategie: In Java hätte ich einfach die Vererbungshierarchie der Entitätsklassen erweitert. Das geht aber in Rails nicht, da jede Kindklasse von ActiveRecord auch persistiert werden muss (Konvention). Somit bleibt mir nur eins: ein Plugin, in dem ich meine Funktionen in alle betreffenden Klassen “einmixe”. Ich nenne das Plugin “ClubMasquerade”.
Und so funktioniert das mit den PlugIns:
1. Ich schreibe mein eigenes “module” in vendor/plugins/club_masquerade/lib/club_masquerade.rb
require 'activerecord'
module ClubMasquerade #:nodoc:
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_as_club_masquerade
before_save :set_club_from_fields
validates_length_of :club_name, :within => 3..50, :allow_blank =>true, :allow_nil => true
validates_length_of :club_shortcut, :within => 2..10, :allow_nil => true, :allow_blank => true
include ClubMasquerade::InstanceMethods
end
end
# Hier sind die zu erweiternden Objekt- / Instanzmethoden drin
module InstanceMethods
# ...
end
end |
Beachte: mein Modul “ClubMasquerade” hat 2 Unter-Module “ClubMasquerade::ClassMethods” und “ClubMasquerade::InstanceMethods”
2. Und nun kommt das Kunststück: durch diese magische Zeile wird in meinem Modul die Funktion “self.included” (siehe 1.) ausgeführt. Die darin beschrieben Funktion ruft per “Reflection” die Methode “include” für ActiveRecord::Base auf und fügt damit den Funktionsumfang meines Moduls ClubMasquerade dynamisch der Klasse hinzu.
Im Projekt-Ordner vendor/plugins/club_masquerade schreibe ich dazu in die Datei init.rb
ActiveRecord::Base.send(:include, ClubMasquerade) |
3. Kurzer Zwischenstand: Wenn Rails beim Hochfahren des Systems nun die init.rb findet und ausführt (Automatismus), dann wird der Funktionsumfang von ActiveRecord erweitert um meine eigenen Funktionen. Das bedeutet, dass auch in allen Model-Klassen (die sich ja von ActiveRecord ableiten) die Funktion “acts_as_club_masquerade” zur Verfügung steht.
In jedem Model wird nun eingetragen:
class Ship < ActiveRecord::Base
acts_as_club_masquerade
# ...
end |
Durch den Aufruf dieser Klassenmethode beim Interpretieren der Klasse “Ship” wird der zugehörige Codeblock aus meinem Modul ausgeführt (siehe oben). Und was macht der Code-Block “acts_as_club_masquerade” ? Er fügt wiederum Instanzmethoden zum Funktionsumfang meines Objektes hinzu, nämlich genau die Funktionen, die ich im Objekt erweitern wollte. Es lebe Reflection. Hoch Introspection. Vive la revolution!
Das Ergebnis:
Alle Model-Klassen, die ich mit meinem “acts_as_club_masquerade” versehen habe (und nur die) besitzen einen um meine Wünsche erweiterten Funktionsumfang, den ich entsprechend nutzen kann.
So, ich hoffe, das hilft
!
Andreas Jene