csiszarattila.com / Rubysztán

Egyedi FormBuilder Railsben

A Rails sok automatizmust ad a kezünkbe, ezeket legtöbbször azonban felül kell bírálnunk. Viszont a Rubynak és a Rails logikus tervezésének köszönhetően könnyű dolgunk van.

Tegyük fel, hogy a következőre van szükségünk:

Az első két pontot akár le is kódolhatnánk minden form esetében, vagy használhatnánk partialt, esetleg helpert, a megoldás majdnem nyert ugyanis szükségünk lesz egy egyedi FormBuilder-re.

FormBuilder létrehozása

Hozzunk létre egy új osztályt a helpereink közé (újabb rails konvenció: app/helpers mappában a form builder-ünk nevével hozzunk létre egy fájlt, pl my_form_builder.rb), így automatikusan be fog töltődni.

Ahelyett, hogy megírnánk minden form helper metódust származtassuk le az osztályt a Rails alapértelmezett builder-jéből - ActionView::Helpers::FormBuilder.

Kezdjük a text_field metódus felülírásával

...
def text_field(field, *args)
  label = label(field,options[:label])1
  
  2@template.content_tag(:div,label+"<br />"+super3,:class=>"input")
end
...

A label metódus1 egy sima label tag-et hoz létre, ne feledjük ez az osztályunk metódusa, egyenlő a template-ben meghivott ActionView helperrel. A 2 pedig a szülő builder osztályunkból érjük el, ez megintcsak egyenlő a template-ben meghivott content_tag ActionView helperrel. A super-rel3 pedig egyszerűen meghívjuk az ősosztály hasonló metódusát, hogy ne kelljen megírnunk a működését.

Az osztály használatához adjuk meg a formok esetében a builder opciót:

form_for ... :builder=>MyFormBuilder ... do |f|

A text típusú űrlap beviteli(form input) tagokat tehát sikerült div-be csomagolni, mostmár csak a többi helpert kell megírnunk, amelyek gyakorlatilag megegyeznek a fentivel. A DRY elveit követve azonban nem akarunk kódismétlésbe kezdeni. Vessük be a Ruby dinamizmusát: a define_method a segítségünkre van!

...
helpers = field_helpers - %w(hidden_field label fields_for)1

helpers.each do |name|
  define_method(name) do |field,*args|
    label = label(field,options[:label])
    @template.content_tag(:div,label+"<br />"+super :class=>"input")
  end
...
1 A szülő osztály majd elbánik ezekkel, nekünk nincs szükségünk rájuk.

Márcsak a hibás mezők kezelését kell megoldanunk.

Rails form hibakezelésének felülbírálása

Hibás adat esetén a div-hez könnyen hozzáadhatjuk a kívánt osztálynevet(field-with-error), mindösszesen a felülbírált formbuilderünkben kell ellenőrizni, hogy tartozik-e a mezőhöz(field) hibaüzenetet.

...
wrapper_div_classes << INPUT_CLASS_NAME
wrapper_div_classes << " #{ERROR_CLASS_NAME}" unless 1@object.errors.on(field).nil?
@template.content_tag(:div,label+"<br />"+super,:class=>wrapper_div_classes2)
...

Mindössze annyit kell tudnunk, hogy a leszármaztatás miatt az @object osztályváltozóból1 elérhetjük a mezőhöz tartozó ActiveRecord tipusú objektumot, ahonnan megtudhatjuk tartozik-e az adott mezőhöz hibaüzenet. A 2-es pontban pedig hozzáadjuk a div osztályait.

Frissítés után láthatjuk, hogy a hibás mezők esetében valóban hozzáadódott az osztálynév, de hogyan iktassuk ki a Rails alapértelmezett hibakezelését, ami div-vel veszi körül nemcsak a hibás mezőt, de a hozzá tartozó címkét(labelt) is.

Nos, kevéssé dokumentált és nem is szép megoldás, de működik: felül kell írni az ActionView::Base.field_error_proc-t.

ActionView::Base.field_error_proc = Proc.new do |html_tag,instance_tag|
  html_tag
end

Helyezzük el a fenti kódot a config/initializers könyvtárban pl. field_error_proc.rb néven. Szerver újraindítás után(!) a probléma megoldódik.

A forrás elérhető GitHub-ról, így:

git clone git://github.com/csiszarattila/myformbuilder.git