Mit ‘Ruby’ getaggte Artikel

RoR-Migrations und PostgreSQL-Datentypen

Montag, 01. März 2010

Migrationsskripte sind in Ruby on Rails das Konzept, um die Entwicklung von Datenbanken im Verlaufe der Produktentwicklung zu verwalten und zu steuern. Da es sich bei ihnen um Ruby-Skripte handelt, steht einem auch der volle Funktionsumfang von Ruby on Rails zur Verfügung.
Ein Hauptprinzip der Migrationsskripte ist deren Datenbankneutralität. Sie soll es ermöglichen, mit möglichst geringem Aufwand die Datenbank wechseln zu können. Dies geht auf Kosten der Unterstützung spezieller Datenbank-Features.
Arbeitet man nun mit einer PostgreSQL-Datenbank und möchte man seine Spalten auch mit PostgreSQL-spezifischen Datentypen versehen, so kann man das wie folgt bewerkstelligen:

In der Methode “native_database_types” der Klasse “ActiveRecord::ConnectionAdapters::PostgreSQLAdapter” werden die Mappings für die SQL-Datentypen definiert, die von den Migrationsskripten im Zusammenhang mit einer PostgreSQL-Datenbank benutzt werden. Genau diese Methode kann man nun überschreiben, um die Mappings anzupassen.

class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
def native_database_types
{
:primary_key => "bigserial primary key",
:string      => { :name => "character varying", :limit => 255 },
:text        => { :name => "text" },
:integer     => { :name => "integer" },
:float       => { :name => "float" },
:datetime    => { :name => "timestamp" },
:timestamp   => { :name => "timestamp" },
:time        => { :name => "time" },
:date        => { :name => "date" },
:binary      => { :name => "bytea" },
:boolean     => { :name => "boolean" },
:bigint      => { :name => "int8" }
}
end
end

Nun kann man diese Definition beispielsweise in psql_ext.rb im lib-Verzeichnis speichern und sie per require ‘psql_ext’ in die Migrationsskripte einbinden.


Christian Borkowski


Mein erstes eigenes Rails-PlugIn

Montag, 29. Juni 2009

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


Ruby On Rails – Vorsicht bei der Spaltenbenennung!

Montag, 19. Mai 2008

Getreu den Grundprinzipien von Ruby On Rails „Don’t repeat yourself“ und „Convention over configuration“, versucht das Webframework dem Entwickler die Datenbankanbindung zu erleichtern. Hält man sich an die Konventionen, so genügt es zum Beispiel, die Spalten einer Tabelle nur in der Datenbank festzulegen. Zur Laufzeit stehen einem automatisch Zugriffsmethoden für die Spalten zur Verfügung. Diese werden einfach nach den Spaltennamen benannt. Es werden hierfür also keine Konfigurations- bzw. Mappingdateien mehr benötigt.

Problem

Allerdings führt dieser Mechanismus zu Einschränkungen bei der Auswahl der Spaltennamen. Tabellenspalten sollten nicht den selben Namen tragen wie Methoden der Klasse AciveRecord::Base oder der Klasse Object. Werden diese Einschränkungen nicht beachtet, resultiert dies unter Umständen in schwierig aufzufindenden Fehlern. Nennt man beispielsweise die Spalte einer Tabelle “class”, so wäre der konventionelle Zugriffsweg auf diesen Spaltenwert über eine Instanz t des Models dieser Tabelle: t.class

Da class aber eine Methode der Klasse Object (Basisklasse von ActiveRecord::Base) ist, erhält man mit t.class nicht den erwarteten Spaltenwert, sondern die Klasse der Instanzvariablen t. Benennt man eine Tabellenspalte nach einer Methode von ActiveRecord::Base, so wird also die Spaltenzugriffsmethode nicht zur Verfügung gestellt, da es ja bereits eine gleichnamige Methode gibt. Verwendet man nun diese entsprechend seiner Tabellenspalte benannte Methode, führt dies zu einem unerwartetem Programmverhalten.

Lösung

Es ist empfehlenswert, die erwähnten Einschränkungen bei der Spaltenbenennung zu beachten und damit die Probleme zu vermeiden. Möchte man dennoch eine seiner Spalten genau wie eine Methode von ActiveRecord::Base benennen, so kann man sich einfach einen eigenen getter schreiben:

    def get_class
 
    self.attributes["class"]
 
    end

Die Bezeichner, die man bei der Spaltenbenennung vermeiden sollte, können der Ruby- bzw. der Rails-API entnommen werden. (Members der Klassen Object und ActiveRecord::Base)


Christian Borkowski