vluchtige ActiveRecord objecten
Ik ben een simpele web applicatie met rails aan het bouwen. Niets bijzonders; wat berichtjes, een gastenboek en wat formulieren. Deze laatste formulieren hoeven niet opgeslagen te worden maar moeten verstuurd worden via e-mail. Dat versturen is geen probleem; recht toe recht aan ActiveMailer. De uitdaging zit hem in het valideren van deze formulieren.
Uitdaging is misschien een beetje sterk uitgedrukt maar als je net het beheer van berichten met ActiveRecord hebt gebouwd, is het een beetje jammer als je dan toch weer dingen gaat schrijven als:
if params[:email].nil? || !EMAIL_PATTERN.match(params[:email]) @errors[:email] = 'E-mail adres moet correct ingevuld worden.' end
Neeh, daar pas ik voor! Ik wil gewoon form.valid? kunnen vragen zoals bij ActiveRecord objecten. Maar hoe doe je dat?
ActiveRecord classes vragen aan de database welke kolommen er in een record zitten. Omdat het type object waar ik mee aan de slag wil helemaal geen records opslaat wil ik dus ook geen tabel aanmaken. Op de Rails Weenie site vond ik een hint voor m’n oplossing. Door alleen self.columns te overschrijven kan je het standaard gedrag van AR overschrijven. Onze eigen implementatie moet dan alleen zelf zorgen dat self.columns een lijst column definities terug geeft.
class MyForm < ActiveRecord::Base
def self.columns
[
ActiveRecord::ConnectionAdapters::Column.new('email', nil),
ActiveRecord::ConnectionAdapters::Column.new('message', nil)
]
end
validates_presence_of :email, :message
end
Dit werkt! Zolang we natuurlijk geen form.save proberen natuurlijk. Maar de aanpak in de eerder genoemde hint is een stuk eleganter. Het kan natuurlijk nog beter:
class ActiveForm < ActiveRecord::Base
def self.columns; @columns ||= []; end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s, default, sql_type.to_s, null)
end
end
Hiermee kunnen we MyForm als volgt schrijven:
class MyForm < ActiveForm
%w{email message}.each { |c| column c }
validates_presence_of :email, :message
end
en m’n controller actie:
def feedback
@form = MyForm.new(params[:form])
if request.post? && @form.valid?
MyMailer.deliver_feedback(@form)
redirect_to :action => 'thanks'
end
end
Auw, ik trek krom van geluk!
hmmm ik tel drie regels in je aller eerste voorbeeld en een heleboel ingewikkelde regels in je uiteindelijke oplossing. Oke DRY maar aangezien je verder geen Repeat hebt volgens mij is de eerste oplossing misschien wel zo simpel.
attr_accessorgebruiken enself.columnseen lege lijst column terug laten geven: Validatie werkt maar je object gedraag zich niet helemaal als een AR object omdat je decolumnslijst niet hebt. Deerror_messages_forhelper methode gebruikt bijvoorbeeld deze lijst om errors “op de juiste” volgorde te tonen. Deze helper toont voor de bovenstaande class nooit validatie fouten. Daarnaast verlies je de mogelijkheid omMyFormautomagisch op te bouwen in je view mbv de columns lijst.En de mailer in Rails heet ActionMailer ipv ActiveMailer ;)
Interessante post! Ik ben op dit moment met hetzelfde bezig, en heb dit ook op zo’n beetje dezelfde manier geimplementeerd. Je gebruikt toch wel vaak een support / contact formulier in een applicatie. Ik vraag me trouwens af of er geen nettere manier gebruikt kan worden. Het zou mogelijk zijn om een plugin te maken en dus gewoon daar van te erven.
Ik heb de
ActiveForm.rbin delibdirectory gezet en m’n form objecten zijn nu redelijk netjes. Zie m’n code snippet voor de gedocumenteerde code.class Mailafriend < ActiveRecord::Base unless const_defined? :SQLITE_SETUP SQLITE_SETUP = true self.establish_connection(:adapter => 'sqlite', :database => ':memory:') conn = self.connection conn.create_table(self.table_name, :primary_key => self.primary_key) do |t| t.column :from_name, :string t.column :from_email, :string t.column :to_name, :string t.column :to_email, :string t.column :message, :string end end attr_accessible :from_name attr_accessible :from_email attr_accessible :to_name attr_accessible :to_email attr_accessible :message validates_presence_of :from_name, :message => "U dient een naam van de afzender in te vullen".t validates_presence_of :from_email, :message => "U dient een email-adres van de afzender in te vullen".t validates_presence_of :to_name, :message => "U dient een naam van de ontvanger in te vullen".t validates_presence_of :to_email, :message => "U dient een email-adres van de ontvanger in te vullen".t endCool! Wel jammer dat SQLite dan ook geinstalleerd moet worden. Daar wordt m’n klant niet echt gelukkig van.
Over je hosting weet ik niks, maar als je op linux servers, of freebsd zit. Is sqlite geen enkel probleem. En de installatie stelt helemaal niks voor. Die is voor de meeste hostingproviders een stuk minder erg als rails :)