Rails plugin: form_assistant
Update: this plugin has undergone significant changes since this post was written (see the updated post). The version described here has been tagged as v1.0 and can be found here.
I just finished wrapping up some internal code I’ve been using into a plugin. It’s called form_assistant.
What does it do?
It makes your forms friendly. Essentially it gives you access to two custom form builders:
FormAssistant::FormBuilder
- automatically attaches labels to all traditional field helpers
- adds a
method_missinghook for an easy way to wrap content in HTML tags using Ruby blocks - simple infrastructure to easily add your own form helpers (comes pre-packaged with a couple of handy one’s, such as automatic “Cancel” links)
FormAssistant::InlineErrorFormBuilder
- inherits from
FormAssistant::FormBuilder(so it has all of the goodies above, automatically) - adds inline error handing attached to each field (removes the need for
error_messages_for) - field/error formatting is done via partials, which is extremely flexible
How do I use it?
There are a number of ways. If you’ve ever used a custom builder on a form, then you’re familiar with passing the :builder option. Well, to some, that can be a bit ugly and verbose. At least, that’s what form assistant thinks.
So there’s an even better way:
1 2 3 4 5 6 7 | <% form_assistant_for @project do |form| %> // your fancy form stuff <% end %> <% inline_error_form_assistant_for @project do |form| %> // your fancy form stuff <% end %> |
If you’re familiar with form_for, then this should come natural. If you’re not, I can’t help you.
Now, what if you want to use the form assistant, but you already have form_for’s sprinkled everywhere? What a hassle. Not really. All you have to do is update your default form builder. I suggest doing that in config/initializers/form_assistant.rb, but that’s up to you.
1 2 | ActionView::Base.default_form_builder = RPH::FormAssistant::FormBuilder ActionView::Base.default_form_builder = RPH::FormAssistant::InlineErrorFormBuilder |
Then all of your form_for calls will use whichever builder you’ve specified.
The nice thing about the inline error handling is that the form assistant doesn’t decide how the errors should show up. The form fields get formatted using partials, which means the sky is the limit. A partial gets handed the field and the errors (if any), and away you go. The form partials are kept in app/views/forms. And to get started, you can do:
1 | $> rake form_assistant:install |
That will throw some bare bones partials into app/views/forms for you to use.
Hey form, let me assist you
As mentioned above, form assistant redefines the field helpers to have labels attached to them, automatically.
1 2 3 4 5 6 7 8 9 |
<%= form.text_field :title %>
<label for="project_title">Title</label>
<input type="text" id="project_title" name="project[title]" ... />
// other options:
<%= form.text_field :title, :label_text => 'Project Title' %>
<%= form.text_field :title, :label_class => 'required' %>
<%= form.text_field :title, :label => { :text => 'Project Title', :class => 'required' } %>
|
That works for all field helpers (checkbox, select, etc). Also, if you’d like the form assistant to wrap your label and input tags within a paragraph, you can tell it to do so via:
1 2 | # config/initializers/form_assistant.rb RPH::FormAssistant::FormBuilder.wrap_fields_with_paragraph_tag = true |
And the example above would become:
1 2 3 4 5 6 | <%= form.text_field :title %> <p> <label for="project_title">Title</label> <input type="text" id="project_title" name="project[title]" ... /> </p> |
Note: this cannot be set for the inline error form builder, simply because it leaves all formatting decisions up to the markup provided within the partials.
Form helpers
An example is worth more than my explanation…
1 2 3 4 5 6 7 8 9 10 |
<%= form.cancel %>
<span class="cancel">
<a href="/wherever/the/user/came/from">Cancel</a>
</span>
// other options:
<%= form.cancel 'Go Back' %>
<%= form.cancel 'Nevermind', :url => some_path %>
<%= form.cancel 'Go Back', :attrs => { :class => 'go-back' } %>
|
The cancel helper uses HTTP_REFERER to determine where the user came from.
Another helper I find useful relates to the submit button. I like to give my submit buttons special attention, so that’s why this helper exists:
1 2 3 4 5 6 7 8 9 10 |
<%= form.submission %>
<p class="submission">
<input type="submit" value="Save Changes" ... />
</p>
// other options:
<%= form.submission 'Save Project' %>
<%= form.submission 'Save', :class => 'button' %>
<%= form.submission 'Save', :attrs => { :class => 'submit-wrapper' } %>
|
“On-the-fly” assistance
The form assistant adds a method_missing hook that catches any undefined (er, missing) methods called on the form object. It allows you to do things like this:
1 2 3 4 5 6 7 8 9 10 11 | <% form.div :class => 'admin' do %> // admin fields <% end %> <div class="admin"> // admin fields </div> // other options: <% form.p :id => 'notice' do %> <% form.span :class => 'highlight' do %> |
Now for an easier way to handle the div case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <% form.admin_operations do %> // admin-operations <% end %> <div class="admin-operations"> // admin-operations </div> <% form.admin_operations :glue => ' ' do %> // admin operations <% end %> <div class="admin operations"> // admin operations </div> |
Since a div is probably the most common generic HTML element, just calling some random string of words will assume you’re wanting a div with those CSS classes. It also reinforces the practice that you should give your markup some integrity by naming things with meaning.
Wrapping it up
Again, this plugin was designed to be extended to suit your specific needs. It’s extremely easy to add your own helpers.
Let’s say you had an area of your forms where you wanted to place little notes to guide the user. You could add a helper on the form object by doing:
1 2 3 4 5 6 | # vendor/plugins/form_assistant/lib/form_assistant/helpers.rb # defining a span wrapper for notes def note(attrs = {}, &block) wrapper(:span, { :class => 'note' }.merge!(attrs), @template.capture(&block), block.binding) end |
That would allow you to do something like:
1 2 3 | <% form.note do %> // your note <% end %> |
The magic really lies within the wrapper method. Essentially, it allows you to wrap and element having certain attributes around some content for a template (pay close attention to the bold words). Given that, here’s the implementation:
1 2 3 | def wrapper(e, attrs, content, binding = nil) Collector.wrap(e).having(attrs).around(content).for(@template, binding) end |
Simple and clean. Plus, it reads about as good as its English explanation.
If you have a helper that doesn’t require a block, just leave off the binding (see the submission and cancel helpers).
Speaking of binding, Rails 2.2.0 no longer requires the binding argument for concat. However, everything below Rails 2.2.0 still does. The form assistant accounts for that, so no worries (what kind of assistant would it be otherwise?).
You can find this bad boy on GitHub. And as always, feedback is welcome!

grosser Saturday, 08 Nov, 2008 Posted at 03:14AM
nice ideas, i like the labe_text vs label => text approach and the cancel/submission could come in handy.
But i still do not like the text_field => something huge (reference).
good luck with your builder :>
Bean Friday, 14 Nov, 2008 Posted at 11:23PM
Thanks for the plugin. One glitch: No matter what I do, I can’t get the syntax for specifying the form action to work. Am I just being dense?
Ryan Friday, 14 Nov, 2008 Posted at 11:36PM
If I understand your problem correctly, you’re literally referring to the “action” attribute of the form itself? Meaning, where the form will POST to?
If so, it should work the same as
form_for():The
:urloption will set the action on the form. And just likeform_for(), you can take advantage of the shortcuts (if you’re using RESTful routing):That will automatically set the appropriate “action” based on if the object is new or not (i.e. creating or editing).
Hopefully I understood your issue. If not, please let me know.
Paul Monday, 17 Nov, 2008 Posted at 03:05PM
I was definitely being dense. I’m working with hobo, which doesn’t currently provide support for multi-model forms. I grabbed this plugin to deal with the form_for / fields_for builder issue. I was getting a cryptic series of error messages that had me pulling my hair out. That’s why I thought I wasn’t specifying the action correctly.
Hobo has a built-in user model that I’m trying to extend. It’s very helpful. I’m trying to create a signup form that captures a bunch of data. My app is in the health 2.0 space—I can’t say much more. Hobo does validations that puked on the association “user has_one account”. The error message that I finally figured out was “got HashWithIndifferentAccess, exepected Account”. Since I will be adding more models, I looked at the Presenter pattern. It made for a really clean view, but I’m stuck dealing with hobo user model issues. The main issue is that I don’t understand it ; ) It uses a lifecycle for user that provides hooks like after_create. I’m sure I’ll sort it out eventually.
Any tips on how I might deal with the data type mismatch issue in cases where a presenter doesn’t make sense? Is there a way for the “form_assistant, fields_for” to send the account object that I defined instead of the hash that contains a single instance of said object?
Thanks for the help!
Danny Hiemstra Wednesday, 10 Dec, 2008 Posted at 04:10AM
Thanks for this great plugin. We tested it yesterday and we noticed the “inline_error_form_assistant_for” is gone in the master branch. Is there any new way to do this?
Ryan Wednesday, 10 Dec, 2008 Posted at 08:08AM
@Danny -
Sorry for the confusion. I’ve made some significant changes to this plugin since this post was written, but it’s for the better. You can find the up-to-date examples, configuration, etc. on the README.
Regarding your issue with
inline_error_form_assistant_for. You’re right, it is gone in the master branch. Now, you get all of the goodness from usingform_assistant_for. But it’s a little different.Now, the form helpers are formatted with templates (partials) so you are able to do what you want with how they’re presented. It’s much more extensible. Here’s a snippet so you can see all of the variables that get handed to each partial:
By default the form helpers will be looking for a template in app/views/forms that matches the name of the helper (so a “text_field” helper would look for app/views/forms/_text_field.html.erb). To get started with templates, run:
That will setup the app/views/forms directory with some basic templates. Oh, and if you have a custom template that you’d rather use, just pass it to the helper:
Again, you can find all of this in the README. Also, if (for whatever reason) you don’t want to use the new and improved version of this plugin, I’ve tagged the old version that is described in this post. You can find it here.
Hope that helps. And please let me know if you have any other issues.
Thanks!
Danny Thursday, 11 Dec, 2008 Posted at 06:06AM
Great, thanks for the reply
Danny Thursday, 11 Dec, 2008 Posted at 06:11AM
p.s. i have a little suggestion. It would be great to be able to assign to the template if a field is required. Then you are able to change the template (add an required star for example) if a field is required.
Ryan Thursday, 11 Dec, 2008 Posted at 07:51AM
@Danny -
That’s not a bad suggestion. I’ll think about it.
In the meantime, the way I typically handle this is to give the label a class of ‘required’ or something, then handle it’s distinction in the CSS.
- or -
I’ll post a comment here if I decide to add the capability to mark a field as required.
Ryan Thursday, 11 Dec, 2008 Posted at 08:12AM
@Danny -
It didn’t take me long to decide on this one. I do think it’s worth having the option to mark a field as required, so now you can.
It makes sense, as labels shouldn’t be the only means to indicate required fields (especially since they can be turned off).
As you might expect, use it like this:
That will hand a local variable named ‘required’ over to the template, so you can check that and act accordingly. And to be clear, this defaults to false.
Thanks for the suggestion!
Danny Thursday, 11 Dec, 2008 Posted at 11:13AM
Thanks a lot Ryan, we are planning to use your plugin on some large projects for our company.
Jon Roberts Thursday, 08 Jan, 2009 Posted at 09:00AM
Ryan,
Hi, I want to use form_assistant to render error messages next to the fields that they refer to.
So I need to edit the _text_field.html.erb partial to show the error message. This may be me being stupid, but how can I find the form’s object (eg @user) and the particular field being rendered ?
I can hardcode
and it works, but really I need to find the forms object and the field name in _text_field.html.erb.
I have used the debugger but can’t see anything in scope that would provide the model object or field.
Maybe I have missed something obvious but can you give an example ? It might enhance the (good) tutorial too.
Thanks.
Ryan Thursday, 08 Jan, 2009 Posted at 10:35AM
@Jon -
If you notice on lines 70-79 of lib/form_assistant.rb (with the actual assignment happening on line 89), I’m already handling everything for you, automatically.
All you have to do is put the “errors” local variable where you want it to be in _text_field.html.erb. So your _text_field.html.erb may look like this:
<%= label %> <p> <%= element %> <span><%= tip %></span> <span class="errors"> <%= errors %> </span> </p>The form_assistant will only pass the errors that correspond to the particular field at hand. And if there are more than a single error, they will be comma delimited (or “to_sentence’d”). So a typical error on a password might be:
Password can’t be blank and must be between 6 and 20 characters
At least, that’s how it should behave :-)
Also, just to be clear, this post is a bit out-dated now. I’m working on a new one to give better instructions. It seems like you’re using the new version of form_assistant, though, since you’re concerning yourself with the form templates, but even so, it’s always a good idea to make sure you have the latest and greatest.
Please let me know if that cleared things up. If you’re still having issues, don’t hesitate to ask!
Thanks!
Jon Roberts Friday, 09 Jan, 2009 Posted at 10:18AM
Ryan,
I found that the call to <= errors %> in the partial was not working in my project, even though <= error_messages_for :user %> worked.
Now my project is a bit cluttered with all sorts of plugins (restful_authentication came with lots of them), so I simplified the problem by creating a new test rails project, added your plugin and it worked exactly as you say.
This is an opportunity for me to learn a bit more ruby by looking at how your plugin works and at the ActionView code, so I’ll drop you a note when I find out what my problem is.
(The method has_errors? defined in form_assistant.rb always returns false so the errors collection is never initialized.)
Thanks again.
Jon Roberts Monday, 12 Jan, 2009 Posted at 07:11AM
My mistake was that I used a symbol as the first argument to form_for.
<% form_assistant_for :user, :url => user_profiles_path do |f| -%>
And this is what actually worked;
<% form_assistant_for @user, :url => user_profiles_path do |f| -%>
In the definition of form_for in form_helper.rb, the object variable is not set if the first argument to form_for is a string or a symbol, so in form_assistant, the has_errors? method returned false because the object was nil.
Do you think the form_assistant should try to get the model object just using a symbol or string ?
Thanks for making the plugin, it will be most useful.
Ryan Monday, 12 Jan, 2009 Posted at 10:22AM
@Jon -
I see your issue, and I’m glad you’ve found a resolution.
Regarding your question about the form_assistant trying to get at the model object by using a symbol or a string, I’m a little hesitant to alter how it currently behaves. Currently, it’s using the #object method, which will return the object if it exists or find it using a name or symbol if it doesn’t.
I realize it was behaving unexpectedly, but I don’t know if that’s an issue with my plugin or if this was cascaded down from how Rails behaves, since my plugin just uses Rails’
objecthelper to get at the errors—it doesn’t implement its own logic in that regard.At any rate, I will be sure to investigate this a little further to make sure I’m not causing issues on my end.
Thanks for the catch and best of luck.
And just a tip… if you design your application RESTfully, you can do:
It sure beats explicitly passing in the path. I realize there are those edge cases where it’s mandatory to do that, and if this is one of those cases, please ignore me :-)
Zac Zheng Monday, 23 Feb, 2009 Posted at 05:15PM
I love the plugin so far. I have been looking for something like this for ages!
There’s just one thing that I want to do that this plugin doesn’t currently allow, that is to set default class and other attributes per field type.
For example my form looks like this:
All my text_field will have a default class of ‘text’, which can be overwritten to ‘title’. Is there anyway to specify this default behaviour?
After looking in the partials, and the plugin code, there doesn’t appear to be a simple solution to this.
Thanks for releasing this plugin again!
Ryan Monday, 23 Feb, 2009 Posted at 09:32PM
@Zac -
Firstly, thanks for the kind words.
Secondly, you have an awesome site! I wish I was capable of creating something that simple. It’s amazing how a single line can be so classy. Unfortunately, it’s something only designers can appreciate. I took a similar approach on my personal github page, but even it ended up more than I wanted. Simplicity truly is a gift.
Secondly, you’re right, there currently isn’t any way (without hacking) to set a default class for all fields. However, I wrote a plugin called input_css that automatically sets a class name based on the “type” of field. For instance, a
text_fieldwould get a class of “text”. Asubmit_buttonwould get a class of “submit”. And the same deal for check boxes, radio buttons, etc. While that’s not a direct solution to your problem, it may help. It’s now mandatory on all of my projects.In the meantime, I’ll think about adding support for that, but I don’t want to get too crazy with the features. And based on the simplicity of your web site, I’m sure you understand :-)
Thanks for the feedback!
Zac Zheng Tuesday, 24 Feb, 2009 Posted at 06:39AM
Hi Ryan,
Thanks for complimenting about my single-lined website. Unfortunately I can’t take credit for it:S Quickly moving on…
I will check out input_css shortly. It seems to solve my problem, or rather BlueCSS’s opinionated ways.
I have found a better solution to my problem similar problem. Rather than setting each field with a default class, I give the form a class name instead. I overwrote your form_assistant_for like so:
I have put the above in a helper, and please beware it’s more of a hack. However, I can then treat labels/inputs within .standard_form in a way I would like.
Thank you for your quick response.
Zac
Zac Zheng Tuesday, 24 Feb, 2009 Posted at 06:54AM
Oops, I messed the formatting there. Let’s try again…