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:
- Callbacks/ Observer, die auch andere Model-Objekte modifizieren und persistieren
- Kontextabhänginge Model Validierungen (Validerungen mit if-else-Bedingungen)
- Virtuelle ActiveRecord Methoden, die nur dem Zwischenspeichern von Formularwerten dienen
- 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"]}}