Rails Views, Preprocessing Parameters
I’m a long-time lover of the simplicity of YAML, especially the underused domain type functionality. There’s just something about taking some chunk of text and saying “turn this into an X using the following process” and having it take care of things for me that’s just cool. Recently, I’ve put together something similar, but less structured, for params hashes, that I’m releasing as a plugin called param types.
Using param types is simple. First we define the different types we want to support, and how to convert values to them. Here’s an example for supporting multiple formats of incoming date parameters (I put this in environment.rb, but you can do it elsewhere):
ActionController::Base.add_param_type 'date' do |value|
case value
when String
Date.parse(value)
when Hash
Date.new(Integer(value['year']), Integer(value['month']), Integer(value['day']))
end
end
Here we’re registering a new param type ‘date’, and giving it a block to convert values. The block will be yielded the raw value of any incoming parameter matching the type you’ve defined, and the value returned by the block will be the new value of the parameter.
How do we say, on the view side, that a parameter (ie, form element) should be considered a particular type? A naming convention, of course.
Param types are defined by the use of parenthesis. For example:
<%= text_field_tag 'birthday(date)' %>
This defines a parameter, ‘birthday’, with the param type ‘date’. Since we’ve defined a ‘date’ param type, we’ll end up with params[:birthday] (converted to a Date by our block) rather than a plain params["birthday(date)"] String.
It works with nesting, too.
<%= text_field_tag 'student[birthday(date)]' %>
In this case, you’d end up with params:student, as you might expect.
The ‘date’ example above could also handle a Hash of date components, like:
<%= text_field_tag 'birthday(date)[day]' %>
<%= text_field_tag 'birthday(date)[month]' %>
<%= text_field_tag 'birthday(date)[year]' %>
… or as selects, of course– and it will put the date together (as params:birthday) for you.
This is really just the tip of the iceberg– since params are converted depth- first, you can use the results of deeper conversions in shallower ones– making the building of really complex types possible.
If this sounds interesting, feel free to look at the README and install the plugin:
ruby script/plugin source http://svn.codefluency.com/rails/plugins/
ruby script/plugin install param_types
As you may know, I’m a big fan of form_for and FormBuilder. I’ll be going over how to integrate param_types with them soon, so stay tuned.
One of the trickiest things to deal with when developing complex interfaces in Rails is how to pass along objects as parameters.
This is easy when you’re dealing with simple bits of data– values in params are strings, and with some PHP-like magic with brackets, we get arrays and hashes of them. This is all part of built-in actionpack functionality. Integers (Fixnums) are easy, since parsing them on the action side is as simple as to_i (or, for the savvy, Integer())– and a lot of the time we’re using them in conjunction with ActiveRecord::Base#find anyway.
So how do we handle more complex cases? How do we pass along more complex objects?
Let’s start with dates. Sure, sure, Rails has date_select, select_date, etc, and they have their own bit of special-case magic that handles date conversions. But let’s be serious here– does anyone love the interface that these provide? Not that we should complain, mind you– they’re free and in a lot of cases they’re all we need… but for a consumer app, the sad fact is that a lot of us end up rolling our own date/time widgets (ie, with AJAX calendars). Then we go about manually parsing dates out of params using beforefilters or even calls to private methods directly from actions.
This manual parsing is no fun, folks– and it’s not limited to dates. There’s a lot of cases where data coming in from requests needs to be munged for one purpose or another, and having to keep track, on the action side, of what needs to be converted is tedious– and, in my opinion, somewhat inappropriate for the action side of the relationship. I want the actions to do stuff, not to be manually told to convert/parse the parameters that tell it what to do, then do stuff.
Can we make this more transparent?






