Umgang und Mapping einer Legacy Datenbank mit Ruby (ohne Rails) mit abweichenenden Namenskonventionen mit Active Support

on under asbach
3 minute read

Out Of Date Warning

This article was published on 13/01/2010, this means the content may be out of date or no longer relevant.
You should verify that the technical information in this article is still up to date before relying upon it for your own purposes.

Einleitung/Motivation

Zur Zeit moechte ich verschiedene XML-Dialekte aus einem vorhandenen Datenbankschema gewinnen, und brauchte dazu ein ordentliches Objektrelationales Mapping, wie man es aus Rails ja kennt. (Ausprobieren! keine Zeile SQL mehr notwendig :D).
Allerdings ohne Rails sondern in einem einfachen Rubyscript.
Was es dort alles gibt, will ich hier mal kurz exemplarisch vorfuehren.

Voraussetzung und Datenbankverbindung

Um ein Legacy relationales Datenmodell mit Ruby schoen zu mappen, ging ich letztens wie folgt vor:

Zu erst Rails (active_record ist aber auch ausreichend) installieren, falls noch nicht gemacht (fuer active_record) und composite primary keys, das uns wie der Name schon sagt, zusammengesetzte Primärschlüssel, welches unser Legacy-Schema mitunter mit sich bringt, bereitstellt.
- bash
sudo gem install rails composite_primary_keys
-

Nun können wir uns im ersten Schritt mit unserer Datenbank verbinden:


ruby

require “rubygems”
require “active_record”
require “composite_primary_keys”

options = {:adapter => ‘mysql’,
:database => ‘databasename’,
:username => ‘username’,
:password => ‘******’,
:host => ‘localhost’ ,
:encoding => ’utf8’}
ActiveRecord::Base.establish_connection(options)
-

Definition der Modelle

Nun kommt dort drunter die Konstruktion der Modell-Klassen, die die Tabellen abbilden.

Angenommen wir haben eine Tags, Posts, Posts_Tags und Users Tabelle (in freier Ruby Namenskonvention) mit den folgenden Beziehungen:
Post : Tag = n : m (Jobs_Tags)
Post : User = n : 1

Also ein Post hat eine Anzahl Tags (über “jobs_tags”) und genau einen User.

Da das aber zu leicht wäre, gibt es folgende Handicaps :):

  • “Posts” Tabelle heißt “entries_import”, Primaerschluessel (PK) postid, Fremdschlüssel “uid” heißt “nutzer”
  • “Tags” Tabelle heißt “clouds_import”, PK tagid
  • “Posts_Tags” Tabelle heisst “clouds_import_lnk”, PK (postid,tagid) zusammengesetzter Primaerschluessel!
  • “Users”-Tabelle heißt “users”, mit PK uid

(leicht abgeändertes RealWorld Beispiel!)

Tja sieht schon recht messy aus. Aber alles machbar:

Der Code


Ruby

class Post < ActiveRecord::Base
set_table_name “entries_import” # posts Tabelle
set_primary_key “postid”
has_many :tags, :through => :taggings
has_many :taggings, :primary_key => “postid”, :foreign_key => “postid”
belongs_to :user, :foreign_key => “nutzer”

  1. Extra Points! Unser ‘Post’ hat ein “visible” Attribut, welches
  2. geradezu nach einem named scope schreit! :)
    named_scope :visible, :conditions => {:visible => 1}, :order => “pubDate desc”

#Extra Points! Tags direkt als (Komma)getrennte Liste zurueckgeben lassen, und

  1. noch den Tag “Blog” auf jeden Fall ans Ende der Tags haengen
    def tag_list(sep = “,”)
    (tags.map(&:name) << “Blog”).uniq.join(sep)
    end
    end

class Tag < ActiveRecord::Base
set_table_name “clouds_import”
set_primary_key “tagid”
has_many :jobs, :through => :taggings
has_many :taggings, :primary_key => “tagid”, :foreign_key => “tagid”
end

class Tagging < ActiveRecord::Base
set_table_name “clouds_import_lnk”
set_primary_keys :jobid, :tagid # Zusammengesetzter Primaerschluessel
belongs_to :job, :primary_key => “postid”, :foreign_key => “postid”
belongs_to :tag, :primary_key => “tagid”, :foreign_key => “tagid”
end

class User < ActiveRecord::Base
set_primary_key “uid”
set_table_name ‘users’
has_many :jobs, :foreign_key => “nutzer”, :primary_key => “uid”
end
-

Fertig!
Nun können wir in althergebrachter Rails Manier extrem bequem auf unsere Modelle wie folgt zugreifen:


Ruby

posts = Post.all
posts.first.tags # gibt uns die tags zurück
post = Post.find(:first, :order => “…”)
post.user.name

  1. oder auch named scopes (siehe oben)
    Post.visible.first.tag_list
    -
    usw.
    Wirklich eine Wohltat ;)

Natürlich kann man in seinen Modellen noch Methoden definieren, die uns den Zugriff noch erleichtern und unser Modell um Funktionalität erweitert und damit unsere Controller und Views nicht zumuellt.

Bonus: XML Builder

Wie man jetzt damit einen XML-Dialekt (z.B. auch RSS) baut, ist recht einfach, dank des XML Builder Gems (Das bei Rails eigentlich auch schon dabei sein sollte).
Ich will hier nur ganz kurz teasern, ansonsten.. Google ist dein Freund:


ruby

require “rubygems”
require “builder”

xml = Builder::XmlMarkup.new( :target => $stdout , :indent => 1 )
xml.instruct!
xml.rss do

  1. oder: xml.tag!(“rss”) do sinnvoll falls unser Tag nicht nur aus Kleinbuchstaben besteht
    xml.channel do
    for item in Post.visible

    … item.title …
end end

end
-