Einfaches Form Objekt

Formular Objekte sind eine zusätzliche Schicht zwischen dem Controller und dem ActiveRecord Model.
Diese Schicht bildet für das Model ein ganz bestimmtes Formular ab und übernimmt dafür die Formularspezifische Geschäftslogik (Validierungen, Virtuelle Attribute, Persistenz abhängiger ActiveRecord Objekte).
Dadurch wird das ActiveRecord Model von Verantwortlichkeiten entlastet:

  1. Callbacks/ Observer, die auch andere Model-Objekte modifizieren und persistieren
  2. Kontextabhänginge Model Validierungen (Validerungen mit if-else-Bedingungen)
  3. Virtuelle ActiveRecord Methoden, die nur dem Zwischenspeichern von Formularwerten dienen
  4. ActiveRecord Models mit Abhängigkeiten zu externen Systemen (z.B. Mailer)

Mit Formular Objekten können ActiveRecord Objekte sich wieder um ihre ursächliche Aufgabe kümmern: CRUD.

Der Controller

Als Beispiel soll die Registrierung eines Bankkontos sein. Dabei soll bei der Registrierung auch eine Adresse angegeben werden können.
Die Models:

rails g model BankAccount iban:string address:references
rails g model Address name:string family_name:string street:string zip:string

Der Controller weist keine Besonderheiten auf. Die create Action nimmt den Request an, ruft die Geschäftslogik auf (in diesem Fall das Form Objekt) und antwortet mit JSON:

# app/controllers/bank_accounts_controller.rb
class BankAccountsController < ApplicationController
  def create
    @bank_account = BankAccountRegistration.new params.require(:bank_account).permit!
    if @bank_account.save
      render nothing: true, status: :created
    else
      render json: { errors: @bank_account.errors }, status: :bad_request
    end
  end
end

Bemerkenswert ist lediglich, dass die strong parameters nicht definiert werden müssen, da das Form Objekt ja sowieso nur die Attribute durchläßt, die in dem Formular abgebildet werden.

Das Form Objekt

Das Form Objekt selber entspricht ActiveModel. Zusätzlich enthält es:

  • Getter und Setter Methoden für die Eingaben aus den Formularfeldern
  • Validierungen
  • Eine Persistenzmethode save

Die save Methode beinhaltet die Geschäftslogik. Nach der Validierung der Formulareingaben startet die Datenbanktransaktion:

# app/forms/bank_account_registration.rb
class BankAccountRegistration
  include ActiveModel::Model

  attr_accessor :iban,
                :name,
                :family_name,
                :street,
                :zip

  validates :iban, :name, :family_name,
    presence: true

  def save
    return false if invalid?
    bank_account.transaction do
      bank_account.address = address
      bank_account.save!
      # do more stuff like mailer ...
    end
  end

  private

  def bank_account
    @bank_account ||= BankAccount.new iban: iban
  end

  def address
    @address ||= Address.new name: name,
                             family_name: family_name,
                             street: street,
                             zip: zip
  end
end

Der Vorteil von Formular Objekten ist, dass der Aufwand bei steigender Komplexität nur geringfügig steigt. Außerdem lassen sich Formular Objekte sehr gut in Isolation testen.
Ein einfacher curl Request an das Formularobjekt mit dem Response:

curl -X POST -d 'bank_account[iban]=DE450123456789' localhost:3000/bank_accounts
{"errors":{"name":["can't be blank"],"family_name":["can't be blank"]}}