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

4.17. Sonstiges

In diesem Abschnitt gehe ich exemplarisch auf Themen und Fragen ein, die im täglichen Arbeiten wichtig sind, aber in der Gesamtheit zu komplex für ein Anfängerbuch. Es gibt kochrezeptartig Lösungen für konkrete ActiveRecord-Probleme.

Callbacks

Callbacks sind definierte Programmier-Einstiegspunkte (Hooks) im Leben eines ActiveRecord-Objektes. Eine Aufstellung aller Callbacks finden Sie auf http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html. Hier sind die am häufigsten verwendeten:
  • before_validation
    Wird vor der Validierung ausgeführt.
  • after_validation
    Wird nach der Validierung ausgeführt.
  • before_save
    Wird vor jedem Abspeichern ausgeführt.
  • before_create
    Wird vor dem ersten Abspeichern ausgeführt.
  • after_save
    Wird nach jedem Abspeichern ausgeführt.
  • after_create
    Wird nach dem ersten Abspeichern ausgeführt.
Ein Callback wird immer im Model ausgeführt. Nehmen wir einmal an, dass Sie E-Mail-Adresse in einem User-Model immer in Kleinbuchstaben speichern wollen, aber dem Benutzer des Webinterfaces die Möglichkeit geben möchten, auch Großbuchstaben einzugeben. Dann könnten Sie mit einem before_save-Callback das Attribute email mit der Methode downcase in Kleinbuchstaben umwandeln. Das Model sähe dann so aus:
class User < ActiveRecord::Base
  attr_accessible :email, :name

  validates :name,
            :presence => true

  validates :email,
            :presence => true,
            :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }

  before_save :downcase_email

  private

  def downcase_email
    self.email = self.email.downcase
  end
end
Probieren wir einmal in der Console aus, ob es auch so funktioniert, wie wir es uns vorstellen:
MacBook:webshop xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > User.create(name: 'Wintermeyer', email: 'Stefan.Wintermeyer@amooma.de')
   (0.1ms)  begin transaction
  SQL (5.9ms)  INSERT INTO "users" ("created_at", "email", "name", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", Wed, 30 May 2012 18:20:21 UTC +00:00], ["email", "stefan.wintermeyer@amooma.de"], ["name", "Wintermeyer"], ["updated_at", Wed, 30 May 2012 18:20:21 UTC +00:00]]
   (2.8ms)  commit transaction
 => #<User id: 1, name: "Wintermeyer", email: "stefan.wintermeyer@amooma.de", created_at: "2012-05-30 18:20:21", updated_at: "2012-05-30 18:20:21"> 
1.9.3p194 :002 > exit
MacBook:webshop xyz$ 
Obwohl die E-Mail-Adresse mit einem großen 'S' und 'W' angegeben wurde, hat ActiveRecord automatisch innerhalb des before_save-Callbacks alle Buchstaben in Kleinbuchstaben umgewandelt.
In Kapitel 9, Action Mailer finden Sie ein Beispiel für das gleiche Model, bei dem mit einem after_create-Callback automatisch eine E-Mail an einen neu angelegten User verschickt wird. In „Default-Werte“ finden Sie ein Beispiel, wie mit einem after_initialize-Callback ein Default-Wert für ein neues Objekt definiert wird.

Default-Werte

Wenn Sie bei einem ActiveRecord-Objekt bestimmte Default-Werte brauchen, so können Sie das am einfachsten mit dem after_initialize-Callback realisieren. Diese Methode wird von ActiveRecord beim Erstellen eines neuen Objektes aufgerufen. Angenommen, wir haben eine Model-Bestellung (Order) und eine Mindestbestellmenge liegt immer bei 1, dann können wir beim Erstellen eines neuen Datensatzes ja direkt 1 als Default-Wert eintragen.
Setzen wir das Beispiel kurz auf:
MacBook:~ xyz$ rails new shop
[...]
MacBook:~ xyz$ cd shop
MacBook:shop xyz$ rails generate model order product_id:integer quantity:integer
[...]
MacBook:shop xyz$ rake db:migrate
[...]
MacBook:shop xyz$
Wir schreiben in die Datei app/models/order.rb einen after_initialize-Callback:
class Order < ActiveRecord::Base
  attr_accessible :product_id, :quantity
  after_initialize :set_defaults

  private
  def set_defaults 
    self.quantity ||= 1
  end
end
Und jetzt probieren wir in der Console aus, ob ein neues Order-Objekt automatisch die Menge 1 enthält:
MacBook:shop xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > order = Order.new
 => #<Order id: nil, product_id: nil, quantity: 1, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > exit
MacBook:shop xyz$ 
Funktioniert wie gewünscht.

Anmerkung

Einige Leser werden sich fragen, warum wir nicht in der Migration einen Default in der Datenbank gesetzt haben. Wie so oft, gibt es auch für dieses Problem mehrere Lösungsmöglichkeiten.