David Sulc wrote a nice article entitled, Self-marking required fields in Rails 3 that utilized a key element of Ryan Bates' Railscast on Validations in Rails 3. In Ryan's original solution, he creates a new helper method named mark_required
that uses the Rails validation reflection to determine if the model requires that the field to be present (via validates :presence => true
or validates_presence_of
). If the field is required, this helper method adds a '*', otherwise it does nothing. Using this helper method in your code, your code would look like:
<%= f.label :name %><%= mark_required(@user, :name) %> <%= f.text_field :name %>
David improves upon this idea by enhancing the
ActionView::Helpers::FormBuilder
label helper instead of adding a new helper. This eliminates the need to add the extra call to the mark_required
helper for every label, which can be easily forgotten. If the field is required, the enhanced label
helper appends a "*" to the label text to indicate that the field is required. So now your code is back to the normal form of:<%= f.label :name %> <%= f.text_field :name %>
That's a nice improvement, but I thought it would be an even better design practice to simply give the label a
required
CSS class, and then allow the designer to be able to easily modify how required fields should be indicated. My version of config/initializers/form_builder.rb
looks like:class ActionView::Helpers::FormBuilder alias :orig_label :label # add a 'required' CSS class to the field label if the field is required def label(method, content_or_options = nil, options = nil, &block) if content_or_options && content_or_options.class == Hash options = content_or_options else content = content_or_options end if object.class.validators_on(method).map(&:class).include? ActiveModel::Validations::PresenceValidator if options.class != Hash options = {:class => "required"} else options[:class] = ((options[:class] || "") + " required").split(" ").uniq.join(" ") end end self.orig_label(method, content, options || {}, &block) end endWith this revised version in place, all labels of required fields are given a
required
CSS class. If you want to indicate required fields with an asterisk after the label, you can do that by adding this CSS rule to your stylesheet,/* add required field asterisk */ label.required:after { content: " *"; }However, by extracting the method of indicating that the field is required from being hard-coded in the helper to instead using a CSS class, anything you can do with CSS is open to you and can be quickly and easily modified without updating the helper code. You can change colors, fonts, borders, add images before or after, you could even use the dreaded
text-decoration:blink
. The options are nearly endless.Update for Rails 4!
Thanks to the input from alex_m and Dan in the comments below, the follow revised version should work for Rails 4 (with ActiveAdmin):
class ActionView::Helpers::FormBuilder alias :orig_label :label # add a 'required' CSS class to the field label if the field is required def label(method, content_or_options = nil, options = nil, &block) if content_or_options && content_or_options.class == Hash options = content_or_options else content = content_or_options end if object.class.respond_to?(:validators_on) && object.class.validators_on(method).map(&:class).include?(ActiveRecord::Validations::PresenceValidator) if options.class != Hash options = {:class => "required"} else options[:class] = ((options[:class] || "") + " required").split(" ").uniq.join(" ") end end self.orig_label(method, content, options || {}, &block) end end
Thanks again alex_m and Dan!
6 comments:
Thanks for the post Steven.
In Rails 4 the validations class has been changed to ActiveRecord. So replacing ActiveModel::Validations::PresenceValidator with ActiveRecord::Validations::PresenceValidator should do the trick.
Thank you, this is fantastic. And thanks to @alex_m for the Rails 4 update.
There's a bug in interacting with ActiveAdmin and Ransack search, which I fix here: http://stackoverflow.com/a/24985923/1935918
"Update for Rails 4" 2.0
Code above doesn't work, if options[:class] have an array (e.g. ["label"]). So I make some modifications:
if options[:class].instance_of?(Array)
options[:class] = (options[:class].join(" ") + " required").split(" ").uniq.join(" ")
else
options[:class] = (options[:class] + " required").split(" ").uniq.join(" ")
end
How do you write test for this ?
How do you write a test for this ?
Post a Comment