As usual, I headed off to the Rails API but found little there and much of the external documentation on how to use them was either way over-complicated for what I wanted to do, or going in another direction. I did find a few good resources, including the following:
1. Ryan Bates' "Mastering Rails Forms" episode 3 - Great presentation; worth every penny of the $5 cost per episode.
2. APIDoc description of form_for method - APIDoc is your very good friend.
3. Alex Reisner's "Anatomy of a Form Builder" - Best introduction of the details behind Form Builder I found.
4. Ruby Pond's Semantic Form Builder - Lots of complex changes, nicely done
5. Aldenta post with metaprogramming example - Shows how to replace a slew of methods with metaprogramming; compare to Ruby Pond example where they are all hand-crafted.
Because of their often complicated solutions and the fact that there were different ways of doing things, I found it challenging to extract the minimum that I would have to do make a simple change. In the end, after much reading and trial coding, Ryan Bates' "Mastering Rails Forms, episode 3" completed my current understanding of the process.
Here are the key points that I learned which finally helped me get through the morass and make it work for me. In the discussion that follows, CustomFormBuilder is my class name.
To start, this is the code that I wanted to change...
from this:
<td>f.textfield :name, :size => 12</td>to this:
f.textfield :nameNot a big change at all, but with many lines like this, I thought this would be a nice, easy way to learn about custom form builders.
Here's what I learned that helped me understand how it all works.
1) the basic form builder code looks like this:
class CustomFormBuilder < ActionView::Helpers::FormBuilder ...your code... endThere may be other approaches out there that will work, but this is the most straight-forward way I have found.
You want to be able to reference your custom form builder easily, as in:
form_for :person, :builder => CustomFormBuilder do |f| ...form code... endIt turns out there are ways to customize even this, so that your code looks like this:
custom_form_for :person do |f| ...form code which automatically uses your CustomFormBuilder endbut that's for a little further down the page.
2) There is more than one way to implement the logistics of a custom form builder (reminds me of Perl's TMTOWTDI and I am rapidly coming to the conclusion that this applies to Rails everywhere). This was one of the things that tripped me up at first, since different posts give different instructions on how to create your builder. By logistics I mean where do you put the code that will be your form builder so that you can reference it easily?
- You can put it in a helper file... either in application_helper.rb or controller_helper.rb. The basic code layout is as described above... The really important thing about including this code in an existing helper file is that the code MUST come after the end statement that terminates the helper code! Failing to do this will result in your form builder not being found (this one cost me a few hours) or your having to reference it as:
:builder => ApplicationHelper::CustomFormBuilder
- You can put it in a separate file in the helpers directory, with a name that matches the class name, i.e., custom_form_builder.rb
- You can put it anywhere you like (for example, like Ryan Bates does, in a separate directory that you create called app/form_builders). In this case, however, you must add that directory to Rails' default search path by adding the following to your environment.rb file (just uncomment and modify the skeleton statements that's already there):
config.load_paths += %W( #{RAILS_ROOT}/app/form_builders)
ActionView::Base.default_form_builder = CustomFormBuilder
- However, I think I agree with Ryan Bates that this is not such a great idea -- it hides what's going on and could make it difficult for some one new to your code to figure it out.
- [Note]: Other authors recommend doing this in app/helpers/application_helper.rb and one author explicitly stated that the environment.rb approach would not work because helpers don't get initialized until they are actually called/used {could this be a version-dependent issue?]
def custom_form_for(*args, &block) options = args.extract_options! options.merge!(:builder => CustomFormBuilder) form_for(*(args + [options]), &block) end
- Again, Ryan Bates in his "Mastering Rails Forms" episode 3, had the clearest description that I have found but there is an essentially identical representation at http://apidock.com/rails/ActionView/Helpers/FormHelper/form_for and a pretty good description of the whole form builder approach.
def text_field(name, options = {}) options.reverse_merge! (:size => 12) @template.content_tag(:td, super) end
- The "reverse_merge" is so that I can override the default size of 12 with another value coded in the form
extract_options! is a Rails method that will -- not surprisingly -- pull the options hash out of the method arguments. Very useful for the general method signature which uses *args.
merge! and reverse_merge! are methods that will merge and overwrite the receiving hash with the results. So, for example:
options.merge!(:size => 12)will add :size => 12 to the options hash, while:
options.reverse_merge!(:size => 12)will add :size => 12 to the options hash ONLY if there is no :size element already there.
- Note the bang (!) on each of these methods -- very important. If you use the non-bang version, you won't update the options hash and things just won't work.
content_tag is a method on the @template object (provided to you by the FormBuilder) that wraps selected text in the given HTML tag. Very useful for building your output string.
There's lots more, but that should be enough to get you started -- hopefully a little easier than I did. Now that I understand some of these basics, looking at the more complicated examples is much easier for me.