protected attributes en de form methode

Gepubliceerd op: 10.IX.2006 00:29 CEST
Categorieën: rails

Door m’n werk aan de dutchify plugin en met name de dynamische scaffold, heb de form methode van ActiveRecordHelper ontdekt. Deze functie bakt voor een ActiveRecord instance een HTML formulier en haalt daarmee veel onnodig werk uit handen.

Deze functie heeft echter een probleempje, het geleverde formulier bevat alle inhouds attributen. Dat is jammer als je allerlei programmatisch gevulde attributen hebt zoals created_at en updated_at. ActiveRecord geeft je de mogelijkheid om attributen te markeren als protected met de attr_protected methode. Protected attributen worden niet in bulk assignments zoals new(attributes) en attributes=(attributes) meegenomen en omdat een typische CRUD controller deze methoden gebruikt, lijkt het logisch dat de form methode deze weglaat of op z’n minst onschrijfbaar maakt. Jammer genoeg doet form hier helemaal niets mee..

Enter :input_block. Om attributen te renderen in een formulier kan je een blok code meegeven dmv van de :input_block optie. Standaard wordt er een :input_block gebruikt welke een <label> en een <input>, <textarea> of <select> renderd. Om protected attributen onschrijfbaar te maken gaan we een :input_block bakken welke deze attributen anders behandelt en deze bijbehoren de <input> etcetera tags disabled.

Een :input_block krijgt twee argumenten mee, de naam van de instance variable waarvoor het formulier opgebouwd wordt en het column object dat er mee geassocieerd wordt. Helaas weet een column object niet of deze protected is, dit kunnen we achterhalen dmv de class methode protected_attributes van het record. Deze methode bevat een lijst symbols met de namen van alle protected attributes. De volgende methode bepaalt of, gegeven de naam van de instance variable en het column object, het attribute protected is:

def attr_protected?(record, column)
  o = instance_variable_get("@#{record}")
  o && o.class.protected_attributes &&
      o.class.protected_attributes.include?(column.name.to_sym)
end

Nu kunnen we onze eigen form methode maken met een protected bewust :input_block:

def my_form(record)
  form record, :input_block => Proc.new { |record, column|
    options = attr_protected?(record, column) ? { :disabled => true} : {}
    <<-"end_html" 
      <p>
        <label for="#{record}_#{column.name}">#{column.human_name}</label>
        <br />
        #{input(record, column.name, options)}
      </p>
    end_html
  }
end

Plaats beiden methoden in een helper en met je kan met onze my_form methode gemakkelijk formulieren maken waarin de protected attributen netje onschrijfbaar zijn gemaakt.

Maar we willen dit natuurlijk altijd voor formulieren die met form worden gemaakt en dus dat dit ook voor dynamische scaffolds werkt. Een beetje graven in de ActiveRecordHelper code wijst uit dat de form bij het missen van een :input_block optie deze vraagt aan de default_input_block methode. Als je deze methode zelf in je helper opneemt, gebruikt form deze en wordt je eigen variant dus altijd gebruikt, ook door de dynamische scaffold!

Dus plaats het volgende in je application_helper.rb en al je protected attributen zijn beschermd:

def attr_protected?(record, column)
  o = instance_variable_get("@#{record}")
  o && o.class.protected_attributes &&
      o.class.protected_attributes.include?(column.name.to_sym)
end

def default_input_block
  Proc.new { |record, column|
    options = attr_protected?(record, column) ? {:disabled => true} : {}
    <<-"end_html" 
      <p>
        <label for="#{record}_#{column.name}">#{column.human_name}</label>
        <br />
        #{input(record, column.name, options)}
      </p>
    end_html
  }
end

Prachtig! De attributen created_at en consorten automagisch protected maken laten we open als huiswerk voor de lezer.