Rails 5.1 Consulting und Schulung vom Autor:
www.wintermeyer-consulting.de/rails/

4.2. Datenbank/Model anlegen

Anmerkung

Model bezieht sich hier auf das Daten-Modell aus Model-View-Controller (MVC).
Als erstes Beispiel nehmen wir eine Liste mit europäischen Ländern in Europa. Zuerst legen wir ein neues Rails-Projekt an:
MacBook:~ xyz$ rails new europe
      create  
      create  README.rdoc
[...]
Using uglifier (1.2.4) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
MacBook:~ xyz$ cd europe 
MacBook:europe xyz$
Nachfolgend schauen wir uns die Hilfeseite von rails generate model an:
MacBook:europe xyz$ rails generate model
Usage:
  rails generate model NAME [field[:type][:index] field[:type][:index]] [options]

Options:
      [--skip-namespace]  # Skip namespace (affects only isolated applications)
      [--old-style-hash]  # Force using old style hash (:foo => 'bar') on Ruby >= 1.9
  -o, --orm=NAME          # Orm to be invoked
                          # Default: active_record

ActiveRecord options:
      [--migration]            # Indicates when to generate migration
                               # Default: true
      [--timestamps]           # Indicates when to generate timestamps
                               # Default: true
      [--parent=PARENT]        # The parent class for the generated model
      [--indexes]              # Add indexes for references and belongs_to columns
                               # Default: true
  -t, [--test-framework=NAME]  # Test framework to be invoked
                               # Default: test_unit

TestUnit options:
      [--fixture]                   # Indicates when to generate fixture
                                    # Default: true
  -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked

Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -q, [--quiet]    # Supress status output
  -s, [--skip]     # Skip files that already exist

Description:
    Create rails files for model generator.
MacBook:europe xyz$
Die Usage-Beschreibung rails generate model NAME [field[:type][:index] field[:type][:index]] [options] sagt aus, dass nach rails generate model der Name des Models und danach die Tabellenfelder kommen. Wird nach einem Tabellenfeldnamen kein :type gesetzt, so wird per Default von einem String ausgegangen. Legen wir einmal das Model country an:
MacBook:europe xyz$ rails generate model country name population:integer
      invoke  active_record
      create    db/migrate/20120426092916_create_countries.rb
      create    app/models/country.rb
      invoke    test_unit
      create      test/unit/country_test.rb
      create      test/fixtures/countries.yml
MacBook:europe xyz$ 
Der Generator hat eine Datenbank-Migrations-Datei namens db/migrate/20120426092916_create_countries.rb angelegt. Eine Migration enthält Datenbankveränderungen. In dieser Migration wird eine Klasse CreateCountries als Tochter von ActiveRecord::Migration definiert. Die Klassen-Methoden change wird benutzt, um eine Migration und den dazu gehörigen Roll-Back zu definieren.
class CreateCountries < ActiveRecord::Migration
  def change
    create_table :countries do |t|
      t.string :name
      t.integer :population

      t.timestamps
    end
  end
end
Mit rake db:migrate können wir die Migrationen ausführen, also die entsprechende Datenbank-Tabelle anlegen:
MacBook:europe xyz$ rake db:migrate
==  CreateCountries: migrating ================================================
-- create_table(:countries)
   -> 0.0015s
==  CreateCountries: migrated (0.0016s) =======================================

MacBook:europe xyz$ 
Weitere Details zu Migrationen werden Sie später in Abschnitt 4.16, „Migrations (Migrationen)“ kennenlernen.
Schauen wir mal in die Datei app/models/country.rb:
class Country < ActiveRecord::Base
  attr_accessible :name, :population
end
Hmmm … die Klasse Country ist also eine Tochter von ActiveRecord::Base. Logisch, da wir ja in diesem Kapitel ActiveRecord besprechen. ;-)
attr_accessible definiert eine White-List mit Model-Attributen, die per sogenanntem Mass-Assignment[23] mit Werten gefüllt werden können. Stark vereinfacht beschrieben sind das die Attribute, die wir später per Web-Interface befüllen oder verändern können.

Anmerkung

Die eingebaute Hilfeseite zu ActiveRecord::Base ist immer greifbereit, falls Sie mal etwas nachschlagen wollen:
stefan@swmbp 0 1.9.2-p0 jukebox$ ri -T ActiveRecord::Base

Die Attribute id, created_at und updated_at

Auch wenn in der Migration nichts davon steht, bekommen wir per Default immer auch die Attribute id, created_at und updated_at zu jedem ActiveRecord Model. In der Console können wir uns die Attribute der Klasse Country durch die Eingabe des Klassennames ausgeben lassen:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country
 => Country(id: integer, name: string, population: integer, created_at: datetime, updated_at: datetime) 
1.9.3p194 :002 > exit
MacBook:europe xyz$ 
In created_at wird immer der Zeitpunkt gespeichert, an dem der Datensatz initial angelegt wurde. updated_at gibt immer den Zeitpunkt des letzten Updates auf diesen Datensatz wieder.
id wird als zentrale Identifikation des Datensatzes benutzt (Primärschlüssel). Die id wird automatisch bei jedem Datensatz um 1 hochgezählt.

Mögliche Datentypen in ActiveRecord

ActiveRecord ist ein Layer (Schicht, Ebene) zwischen Ruby und verschiedenen relationalen Datenbanken. Leider haben SQL-Datenbanken unterschiedliche Sichtweisen auf die Definition von Spalten und deren Inhalt. Darüber müssen Sie sich aber keine Sorgen machen, da ActiveRecord dieses Problem für Sie transparent löst.
  • Vorteil:
    Wir können die Datenbank hinter einer Rails-Applikation austauschen, ohne dass dafür der Programmcode angefasst werden muss.
  • Nachteil:
    Wir können nicht alle Besonderheiten der jeweiligen Datenbank benutzen. Wir müssen quasi einen kleinsten gemeinsamen Nenner verwenden.
Um ein Model zu generieren, können Sie folgende Feldtypen nutzen:
  • binary
    Das ist im klassischen Sinn ein BLOB (Binary Large Object). Noch nie gehört? Dann werden Sie es wahrscheinlich nicht brauchen.
  • boolean
    Ein Bool'scher Wert. Kann true (wahr) oder false (falsch) sein.
  • date
    Hier kann ein Datum abgespeichert werden.
  • datetime
    Hier kann ein Datum inklusive Uhrzeit abgespeichert werden.
  • float
    Zum Speichern einer Gleitkommazahl.
  • integer
    Zum Speichern einer Ganzzahl.
  • decimal
    Zum Speichern einer Dezimalzahl.

    Tipp

    Sie können auch mit dem Model Generator direkt ein decimal eintragen. Dafür müssen Sie aber die besondere Syntax beachten. Beispiel:
    MacBook:jukebox xyz$ rails generate model product name 'price:decimal{7,2}'
          invoke  active_record
          create    db/migrate/20120430150913_create_products.rb
          create    app/models/product.rb
          invoke    test_unit
          create      test/unit/product_test.rb
          create      test/fixtures/products.yml
    MacBook:jukebox xyz$ cat db/migrate/20120430150913_create_products.rb
    class CreateProducts < ActiveRecord::Migration
      def change
        create_table :products do |t|
          t.string :name
          t.decimal :price, :precision => 7, :scale => 2
    
          t.timestamps
        end
      end
    end
    MacBook:jukebox xyz$
  • primary_key
    Das ist ein Integer (Ganzzahl), der von der Datenbank automatisch bei jedem neuen Eintrag um 1 hochgezählt wird. Dieser Feldtyp wird häufig als Schlüssel für die Verknüpfung von verschiedenen Datenbank-Tabellen bzw. Models genommen.
  • string
    Ein String – also eine beliebige Zeichenkette, bis max. 28-1 (= 255) Zeichen.
  • text
    Auch ein String – allerdings ein gutes Stück größer. Hier können per Default bis zu 216 (= 65536) Zeichen gespeichert werden.
  • time
    Eine Uhrzeit.
  • timestamp
    Eine Uhrzeit mit Datum, die von der Datenbank automatisch befüllt wird.
Wir gehen in Abschnitt 4.16, „Migrations (Migrationen)“ auf die einzelnen Datentypen noch näher ein und sprechen über verfügbare Optionen. Dies soll im Rahmen eines Einsteigerbuches eine Kurzübersicht sein. Wer sich intensiver mit den verschiedenen Datentypen beschäftigen will, sei auf Anhang A, Weiterführende Rails Dokumentation verwiesen.

Namenskonventionen (Country vs. country vs. countries)

Für den Rails-Anfänger ist es oft schwer zu erfühlen, wann man z. B. Country oder country benutzt (das eine ist eine Klasse und das andere ein Model). Dabei geht es nicht um die Klasse an sich, sondern ausschließlich um die Schreibweise oder das Wording. So viel vorweg: Es ist alles logisch und man bekommt den Dreh auch recht schnell heraus. Wichtig ist nur, immer bei englischen Wörtern zu bleiben (siehe „Warum alles auf Englisch?“).
An dieser Stelle wollte ich ursprünglich in aller Länge und Breite über die Namenskonventionen philosophieren. Dann habe ich mir gedacht: Mein Gott, die Leser wollen schnell weiterkommen und nicht hier Ewigkeiten mit Theorie verbringen. Deshalb stelle ich Ihnen jetzt die Methoden vor, mit denen Sie selbst die Schreibweisen in der Rails-Console herausfinden können:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > 'country'.classify
 => "Country" 
1.9.3p194 :002 > 'country'.tableize
 => "countries" 
1.9.3p194 :003 > 'country'.foreign_key
 => "country_id" 
1.9.3p194 :004 > exit
MacBook:europe xyz$ 
ActiveRecord nimmt automatisch den englischen Plural. Bei der Klasse Country ist dies deshalb countries. Wenn Sie sich bei einem Begriff nicht sicher sind, können Sie auch mit der Klasse und der Methode name arbeiten.
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.name.tableize
 => "countries" 
1.9.3p194 :002 > Country.name.foreign_key
 => "country_id" 
1.9.3p194 :003 > exit
MacBook:europe xyz$ 
Auf http://rails.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html finden Sie eine komplette Liste der entsprechenden Methoden. Ich empfehle Ihnen aber jetzt erst mal, sich in der Strömung treiben zu lassen. Falls Sie einmal nicht sicher sind, können Sie mit den eben gezeigten Methoden die richtige Schreibweise herausfinden.

Datenbank-Konfiguration

Welche Datenbank wird per Default benutzt? Schauen wir kurz in die Konfigurationsdatei für die Datenbank (config/database.yml):
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000
Da wir im development-Modus arbeiten, hat Rails beim rake db:migrate eine neue SQLite3-Datenbank db/development.sqlite3 angelegt und dort alle Daten gespeichert.

Anmerkung

Fans von Kommandozeilen-Clients können sqlite3 zur Einsicht in diese Datenbank benutzen:
MacBook:europe xyz$ sqlite3 db/development.sqlite3 
SQLite version 3.7.7 2011-06-25 16:35:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
countries          schema_migrations
sqlite> .schema countries
CREATE TABLE "countries" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "population" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
sqlite> .exit
MacBook:europe xyz$


[23] Ryan Bates hat zum Thema Mass-Assignment und dazu wichtigen Sicherheitsaspekten einen sehr guten Screencast unter http://railscasts.com/episodes/26-hackers-love-mass-assignment-revised veröffentlicht.