Zum Vergleich
Im folgenden gibt es einen kleinen Vergleich verschiedener Datenbank-Anbindung mit Ruby.
Die Tests erfolgen im folgenden Umfeld:
- Betriebssystem Windows
- SQLite3 wird verwendet.
- Ruby Version 1.8 (+ Vorschau auf 1.9).
- Es ist angedacht verschiedene Datenbanken in einem Programm zu verwenden.
Bei Fragen oder Hinweisen: Im Ruby-Forum gibt es einen Thread zu dieser Seite:
Nachfragen sind sicherlich angebracht, wenn man den Eindruck hat, es stimmt etwas nicht. Da es sich hier um Einstiegsexperimente handelt, kann es gut sein, das es einfachere Möglichkeiten gibt oder falsch verwendete Features gibt.
Dieses Dokument gibt es in zwei Fassungen:
Geprüfte DB-Mapper
Die jeweiligen Testfiles sind angehängt, in Klammern die Version des gems, die ich nutzte.
- activerecord ./test_active_record.txt (2.3.8)
- datamapper ./test_datamapper.txt
- sequel ./test_sequel.txt (3.13.0)
Weitere - hier nicht beschriebene - Möglichkeiten:
- dbi - sehr nahe am SQL. Ich hatte Probleme sqlite anzubinden.
- sqlite3 - Wie dbi, allerdings ohne Probleme für sqlite
- winole siehe Source: http://snippets.dzone.com/posts/show/3906
Für die folgende Einteilung danke ich Skade aus dem Ruby-Forum.
ActiveRecord
ActiveRecord ist ein ORM - ein Objekt-Relationaler-Mapper. Es setzt also Relationen aus der Datenbank automatisch in Objekte im Programm um - die im AR-Lingus nun mal Models heissen.
DataMapper
DataMapper zäumt das Pferd etwas anders auf, aber es gilt grundsätzlich das gleiche: DataMapper ist eine Objektpersistenzschicht, die auch SQL kann. DataMapper kann genauso problemlos in Couch oder Redis persistieren, wenn man das denn will, auch wenn gewisse Features (zum Beispiel Relationen) von diesen Stores nicht unterstützt werden. In DataMapper könnte man sogar sagen, dass es ohne DataMapper-Objekt garnicht geht, weil man sonst ja nix zum persistieren hat.
Sequel
Sequel dahingegen ist kein ORM, sondern eine Bibliothek zur Interaktion mit SQL-Datenbanken. Sequel::Model ist daher nur eine Erweiterung, der man durchaus auch anmerkt, dass sie nicht das Hauptaugenmerk der Sache ist. Unbequem ist sie zwar nicht, man muss sich aber viel selbst zusammmenbauen.
Testverfahren
Je Mapper gibt es ein Rakefile, das die identischen Testfälle als Task enthält. Die einzelnen Task führen den Test aus und geben (sofern sinnvoll) ihr Ergebnis aus.
Die Tests werden im folgenden mit Testcode, Ausgabe und Kommentar gelistet.
Als Testdaten wurden verwendet:
- Testdaten ./filmdaten.yaml
Und falls beim lesen jemand Lust auf die Filme von Buster Keaton bekommt:
Testfälle
- Datenbank anmelden
- SQLite
- ODBC
- Models anlegen
- Tabelle erzeugen (create table)
- Tabelleneinträge anlegen (insert)
- Tabelleneinträge lesen (select)
- Tabelleneinträge ändern (update)
- Views anlegen und verwenden
- 'Native' SQL verwenden
Weitere Testfälle
Bisher hier nicht aufgenommen:
- select mit order/sort
- kombiniertes select (Spalten/Zeilen/oder...)
- Joins (inner/outer)
- Migrations
An Datenbank anmelden
SQLite
active_record | datamapper | sequel |
---|---|---|
Testvariante :connect_sqlite | ||
ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :database => ":memory:" ) |
#Without filename -> DB in memory DB = DataMapper.setup(:default, 'sqlite::memory:') |
DB = Sequel.sqlite() |
|
|
ODBC
active_record | datamapper | sequel |
---|---|---|
Testvariante :connect_odbc | ||
#dsn, username and password must be known. ActiveRecord::Base.establish_connection( :adapter=>"sqlserver", :mode=>"odbc", :dsn => $dsn, :username => $username, :password=> $password ) |
#dsn, username and password must be known. # #??? # |
#dsn, username and password must be known. DB = Sequel.connect( :adapter=>'ado', :host=> $server, :database=>$dsn, :user=> $username, :password=>$password ) |
Bisher erfolglos. Siehe http://forum.ruby-portal.de/viewtopic.php?f=22&t=11545 |
Abmelden (disconnect)
active_record | datamapper | sequel |
---|---|---|
Testvariante :disconnect | ||
ActiveRecord::Base.connection.disconnect! |
nil |
DB.disconnect |
Scheint es nicht zu geben: |
Models
Models sind Objekte, die Tabelleneinträge entsprechen. Über Models sind DB-Aktionen vereinfacht möglich.
Datenbankaktionen ohne Models werden weiter unten vorgestellt.
Models definieren
active_record | datamapper | sequel |
---|---|---|
Testvariante :create_model | ||
class Film < ActiveRecord::Base #set_table_name :films end |
class Film include DataMapper::Resource property :id, Serial property :title, String property :year, Integer end |
class Film < Sequel::Model(:films) end |
|
|
|
Datenbank manipulieren
Tabelle erzeugen (create table)
active_record | datamapper | sequel |
---|---|---|
Testvariante :create_tables | ||
ActiveRecord::Schema.define do create_table :films do |t| #~ primary_key :fid t.column :title, :string t.column :year, :string, :size => 4 end end |
require 'dm-migrations' class Film include DataMapper::Resource property :id, Serial property :title, String property :year, Integer end DataMapper.auto_migrate! |
DB.create_table :films do #~ primary_key :fid column :title, :string column :year, :string, :size => 4 end |
|
|
Das Verzichten auf den primary_key mag nicht mit jeder DB funktionieren. |
Testvariante :create_tables_2 | ||
ActiveRecord::Schema.define do create_table( :films ) add_column(:films, :title, :string) add_column(:films, :year, :string, :size => 4) end |
||
|
Tabelleneinträge anlegen (insert)
active_record | datamapper | sequel |
---|---|---|
Testvariante :insert_values | ||
#Model Film wurde bereits definiert Film.create(:title => 'Three Ages', :year => 1923) |
Film.create(:title => 'Three Ages', :year => 1923) Film.new( :title => 'Steamboat Bill Jr.', :year => 1928 ).save |
Film.insert( :title => 'Three Ages', :year => 1923 ) |
Tabelleneinträge ändern (update)
active_record | datamapper | sequel |
---|---|---|
Testvariante :update | ||
#1 = interne id Film.update(1, :year => 'egal' ) Film.find(:all, :conditions => "year = 'egal'") |
#~ Film.update(:year => 0 ) Film.all(:year => 1923 ).update(:year => 0 ) |
#Model Film wurde bereits definiert Film.update(:year => 'egal') |
|
Views anlegen und verwenden
active_record | datamapper | sequel |
---|---|---|
Testvariante :create_view | ||
nil |
nil |
DB.create_view(:films1923, DB[:films]. where(:year => 1923). select(:title) ) DB[:films1923].each{|f| p f} |
{:title=>"Three Ages"} {:title=>"Our Hospitality"} |
||
|
|
Tabelleneinträge lesen (select)
Verarbeitungsmethoden
Mit Array als Ergebnis
active_record | datamapper | sequel |
---|---|---|
Testvariante :select2array | ||
#Model Film wurde bereits definiert res = Film.find(:all) puts res.inspect[0..42] + '...' |
res = Film.all() puts res.inspect[0,45] |
#Model Film wurde bereits definiert res = Film.all puts res.inspect[0,40] |
[#<Film id: 1, title: "The Saphead", year: ... |
[#<Film @id=1 @title="The Saphead" @year=1920 |
[#<Film @values={:year=>1920, :title=>"T |
Die Definition eines Models ist notwendig (?) |
Die Definition von Models ist möglich. |
Blockverarbeitung
active_record | datamapper | sequel |
---|---|---|
Testvariante :select2block | ||
#Model Film wurde bereits definiert Film.find(:all).each{|film| p film } |
Film.each{|film| film } Film.select{|film| p film } |
#Model Film wurde bereits definiert Film.each{|film| p film } |
#<Film id: 1, title: "The Saphead", year: "1920"> #<Film id: 2, title: "Three Ages", year: "1923"> #<Film id: 3, title: "Our Hospitality", year: "1923"> #<Film id: 4, title: "Sherlock Jr.", year: "1924"> #<Film id: 5, title: "The Navigator", year: "1924"> #<Film id: 6, title: "Seven Chances", year: "1925"> #<Film id: 7, title: "Go West", year: "1925"> #<Film id: 8, title: "Battling Butler", year: "1926"> #<Film id: 9, title: "The General", year: "1926"> #<Film id: 10, title: "College", year: "1927"> #<Film id: 11, title: "Steamboat Bill Jr.", year: "1928"> #<Film id: 12, title: "The Cameraman", year: "1928"> #<Film id: 13, title: "Spite Marriage", year: "1929"> |
#<Film @id=1 @title="The Saphead" @year=1920> #<Film @id=2 @title="Three Ages" @year=1923> #<Film @id=3 @title="Our Hospitality" @year=1923> #<Film @id=4 @title="Sherlock Jr." @year=1924> #<Film @id=5 @title="The Navigator" @year=1924> #<Film @id=6 @title="Seven Chances" @year=1925> #<Film @id=7 @title="Go West" @year=1925> #<Film @id=8 @title="Battling Butler" @year=1926> #<Film @id=9 @title="The General" @year=1926> #<Film @id=10 @title="College" @year=1927> #<Film @id=11 @title="Steamboat Bill Jr." @year=1928> #<Film @id=12 @title="The Cameraman" @year=1928> #<Film @id=13 @title="Spite Marriage" @year=1929> |
#<Film @values={:year=>1920, :title=>"The Saphead"}> #<Film @values={:year=>1923, :title=>"Three Ages"}> #<Film @values={:year=>1923, :title=>"Our Hospitality"}> #<Film @values={:year=>1924, :title=>"Sherlock Jr."}> #<Film @values={:year=>1924, :title=>"The Navigator"}> #<Film @values={:year=>1925, :title=>"Seven Chances"}> #<Film @values={:year=>1925, :title=>"Go West"}> #<Film @values={:year=>1926, :title=>"Battling Butler"}> #<Film @values={:year=>1926, :title=>"The General"}> #<Film @values={:year=>1927, :title=>"College"}> #<Film @values={:year=>1928, :title=>"Steamboat Bill Jr."}> #<Film @values={:year=>1928, :title=>"The Cameraman"}> #<Film @values={:year=>1929, :title=>"Spite Marriage"}> |
Ergebnisseinschränkungen
Select mit Filter (where)
active_record | datamapper | sequel |
---|---|---|
Testvariante :select_where | ||
Film.find( :all, :conditions => "year = '1923'" ).each{|f| p f} |
res = Film.all(:year => 1923 ) #equal |
#Model Film wurde bereits definiert Film.filter( :year => 1923 ).all Film.where( :year => 1923 ).each{|f| p f } |
#<Film id: 2, title: "Three Ages", year: "1923"> #<Film id: 3, title: "Our Hospitality", year: "1923"> |
#<Film @id=2 @title="Three Ages" @year=1923> #<Film @id=3 @title="Our Hospitality" @year=1923> |
#<Film @values={:year=>1923, :title=>"Three Ages"}> #<Film @values={:year=>1923, :title=>"Our Hospitality"}> |
Testvariante :select_where_gt | ||
Film.find( :all, :conditions => "year > '1927'" ).each{|f| p f} |
Film.all(:year.gt => 1927 ).each{|f| p f } |
#Model Film wurde bereits definiert res = Film.where{ :year > 1927 }.all res = Film.filter{ :year > 1927 }.each{|f| p f } |
#<Film id: 11, title: "Steamboat Bill Jr.", year: "1928"> #<Film id: 12, title: "The Cameraman", year: "1928"> #<Film id: 13, title: "Spite Marriage", year: "1929"> |
#<Film @id=11 @title="Steamboat Bill Jr." @year=1928> #<Film @id=12 @title="The Cameraman" @year=1928> #<Film @id=13 @title="Spite Marriage" @year=1929> |
#<Film @values={:year=>1928, :title=>"Steamboat Bill Jr."}> #<Film @values={:year=>1928, :title=>"The Cameraman"}> #<Film @values={:year=>1929, :title=>"Spite Marriage"}> |
In :conditions steht die SQL Where-Klausel. |
|
|
Testvariante :select_where_in | ||
Film.find(:all, :conditions => "year in ( '1920', '1923')" ).each{|f| p f} |
Film.all(:year => [ 1920, 1923 ] ).each{|film| p film } |
#Model Film wurde bereits definiert Film.where( :year => [ 1920, 1923 ] ).all Film.filter( :year => [ 1920, 1923 ] ).each{|f| p f } |
#<Film id: 1, title: "The Saphead", year: "1920"> #<Film id: 2, title: "Three Ages", year: "1923"> #<Film id: 3, title: "Our Hospitality", year: "1923"> |
#<Film @id=1 @title="The Saphead" @year=1920> #<Film @id=2 @title="Three Ages" @year=1923> #<Film @id=3 @title="Our Hospitality" @year=1923> |
#<Film @values={:year=>1920, :title=>"The Saphead"}> #<Film @values={:year=>1923, :title=>"Three Ages"}> #<Film @values={:year=>1923, :title=>"Our Hospitality"}> |
In :conditions steht die SQL Where-Klausel. |
||
Testvariante :select_where_range | ||
Film.find(:all, :conditions => "year between '1920' and '1923'" ).each{|f| p f} |
Film.all(:year => 1920..1923 ).each{|film| p film } |
#Model Film wurde bereits definiert Film.where( :year => 1920..1923 ).all Film.filter( :year => 1920..1923 ).each{ |f| p f } |
#<Film id: 1, title: "The Saphead", year: "1920"> #<Film id: 2, title: "Three Ages", year: "1923"> #<Film id: 3, title: "Our Hospitality", year: "1923"> |
#<Film @id=1 @title="The Saphead" @year=1920> #<Film @id=2 @title="Three Ages" @year=1923> #<Film @id=3 @title="Our Hospitality" @year=1923> |
#<Film @values={:year=>1920, :title=>"The Saphead"}> #<Film @values={:year=>1923, :title=>"Three Ages"}> #<Film @values={:year=>1923, :title=>"Our Hospitality"}> |
In :conditions steht die SQL Where-Klausel. |
Select mit Spaltenauswahl
active_record | datamapper | sequel |
---|---|---|
Testvariante :select_col | ||
#Model Film wurde bereits definiert res = Film.find(:all, :select => "title") |
#Nicht wirklich die Lösung class Film include DataMapper::Resource property :id, Serial property :title, String property :year, Integer, :lazy => true end res = Film.all |
#Model Film wurde bereits definiert Film.select(:title).each{|f| p f } |
#<Film title: "The Saphead"> #<Film title: "Three Ages"> #<Film title: "Our Hospitality"> #<Film title: "Sherlock Jr."> #<Film title: "The Navigator"> #<Film title: "Seven Chances"> #<Film title: "Go West"> #<Film title: "Battling Butler"> #<Film title: "The General"> #<Film title: "College"> #<Film title: "Steamboat Bill Jr."> #<Film title: "The Cameraman"> #<Film title: "Spite Marriage"> |
#<Film @id=1 @title="The Saphead" @year=<not loaded>> #<Film @id=2 @title="Three Ages" @year=<not loaded>> #<Film @id=3 @title="Our Hospitality" @year=<not loaded>> #<Film @id=4 @title="Sherlock Jr." @year=<not loaded>> #<Film @id=5 @title="The Navigator" @year=<not loaded>> #<Film @id=6 @title="Seven Chances" @year=<not loaded>> #<Film @id=7 @title="Go West" @year=<not loaded>> #<Film @id=8 @title="Battling Butler" @year=<not loaded>> #<Film @id=9 @title="The General" @year=<not loaded>> #<Film @id=10 @title="College" @year=<not loaded>> #<Film @id=11 @title="Steamboat Bill Jr." @year=<not loaded>> #<Film @id=12 @title="The Cameraman" @year=<not loaded>> #<Film @id=13 @title="Spite Marriage" @year=<not loaded>> |
#<Film @values={:title=>"The Saphead"}> #<Film @values={:title=>"Three Ages"}> #<Film @values={:title=>"Our Hospitality"}> #<Film @values={:title=>"Sherlock Jr."}> #<Film @values={:title=>"The Navigator"}> #<Film @values={:title=>"Seven Chances"}> #<Film @values={:title=>"Go West"}> #<Film @values={:title=>"Battling Butler"}> #<Film @values={:title=>"The General"}> #<Film @values={:title=>"College"}> #<Film @values={:title=>"Steamboat Bill Jr."}> #<Film @values={:title=>"The Cameraman"}> #<Film @values={:title=>"Spite Marriage"}> |
Nicht wirklich die Lösung. Alternative 'lazy':
|
Datenbankinformationen
Tabellenbeschreibungen
active_record | datamapper | sequel |
---|---|---|
Testvariante :check_schema | ||
Film.reset_column_information() #Change in meantime? p Film.base_class puts Film.columns_hash.to_yaml Film.columns #Array |
Film.properties.each{|prop| p prop } |
puts DB.schema(:films).to_yaml |
Film(id: integer, title: string, year: string) --- title: !ruby/object:ActiveRecord::ConnectionAdapters::SQLiteColumn default: limit: 255 name: title "null": true precision: primary: false scale: sql_type: varchar(255) type: :string id: !ruby/object:ActiveRecord::ConnectionAdapters::SQLiteColumn default: limit: name: id "null": false precision: primary: true scale: sql_type: INTEGER type: :integer year: !ruby/object:ActiveRecord::ConnectionAdapters::SQLiteColumn default: limit: 255 name: year "null": true precision: primary: false scale: sql_type: varchar(255) type: :string |
#<DataMapper::Property::Serial @model=Film @name=:id> #<DataMapper::Property::String @model=Film @name=:title> #<DataMapper::Property::Integer @model=Film @name=:year> |
--- - - :title - :type: :allow_null: true :primary_key: false :default: :ruby_default: :db_type: string - - :year - :type: :allow_null: true :primary_key: false :default: :ruby_default: :db_type: string(4) |
|
||
Testvariante :check_schema_columns | ||
p Film.column_names |
p Film.properties.map{|prop| prop.name} |
p DB[:films].columns |
["id", "title", "year"] |
[:id, :title, :year] |
[:title, :year] |
|
Datenbankaktion ohne Model
active_record | datamapper | sequel |
---|---|---|
Testvariante :no_model_insert | ||
DB[:films].insert( :title => 'Three Ages', :year => 1923 ) |
||
Insert funktioniert auch ohne Model |
||
Testvariante :no_model_select2array | ||
res = DB[:films].all puts res.inspect[0,45] |
||
[{:year=>1920, :title=>"The Saphead"}, {:year |
||
Testvariante :no_model_select2block | ||
DB[:films].each{|film| p film } |
||
{:year=>1920, :title=>"The Saphead"} {:year=>1923, :title=>"Three Ages"} {:year=>1923, :title=>"Our Hospitality"} {:year=>1924, :title=>"Sherlock Jr."} {:year=>1924, :title=>"The Navigator"} {:year=>1925, :title=>"Seven Chances"} {:year=>1925, :title=>"Go West"} {:year=>1926, :title=>"Battling Butler"} {:year=>1926, :title=>"The General"} {:year=>1927, :title=>"College"} {:year=>1928, :title=>"Steamboat Bill Jr."} {:year=>1928, :title=>"The Cameraman"} {:year=>1929, :title=>"Spite Marriage"} |
||
Testvariante :no_model_select_where | ||
DB[:films].filter( :year => 1923 ).all DB[:films].where( :year => 1923 ).each{|f| p f } |
||
{:year=>1923, :title=>"Three Ages"} {:year=>1923, :title=>"Our Hospitality"} |
||
Select funktioniert auch ohne Model |
||
Testvariante :no_model_select_col | ||
DB[:films].select(:title).each{|f| p f } |
||
{:title=>"The Saphead"} {:title=>"Three Ages"} {:title=>"Our Hospitality"} {:title=>"Sherlock Jr."} {:title=>"The Navigator"} {:title=>"Seven Chances"} {:title=>"Go West"} {:title=>"Battling Butler"} {:title=>"The General"} {:title=>"College"} {:title=>"Steamboat Bill Jr."} {:title=>"The Cameraman"} {:title=>"Spite Marriage"} |
||
Select funktioniert auch ohne Model |
||
Testvariante :no_model_update | ||
DB[:films].update(:year => 'egal') |
||
|
'Native' SQL verwenden
Als ORM macht es nur bedingt Sinn reines SQL zu verwenden. In manchen Situationen kann es aber sinnvoll bzw. notwendig sein.
Bei lesenden Zugriffen mag es interessant sein, eine entsprechende View zu definieren und auf diese über Models zuzugreifen.
Zum nachlesen:
active_record | datamapper | sequel |
---|---|---|
Testvariante :use_sql | ||
ActiveRecord::Base.connection.execute( "create table actors ( id integer primary key, name varchar(255) )" ) |
puts "Before: #{DB.tables.inspect}" DB.run( "create table actors ( id integer primary key, name varchar(255) )" ) puts "After: #{DB.tables.inspect}" |
|
Before: [:films] After: [:films, :actors] |
||
|
||
Testvariante :use_sql_select | ||
ActiveRecord::Base.connection.select_values( "select title from films where year = '1923'" ).each{|f| p f } |
p DB.select( "select title from films where year = '1923'" ) |
DB.fetch( "select title from films where year = 1923" ){|f| p f } |
"Three Ages" "Our Hospitality" |
["Three Ages", "Our Hospitality"] |
{:title=>"Three Ages"} {:title=>"Our Hospitality"} |
|
|
|
Testvariante :use_sql_inverse | ||
puts Film.select(:title). where(:year => 1923).sql |
||
SELECT `title` FROM `films` WHERE (`year` = 1923) |
||
|