Rails gives a developer a lot of support, but it isn’t always clear exactly what’s going on under the covers. One area where I’ve had a bit of frustration is trying to understand exactly what Rails is doing for me when it parses my querystring. Somehow it knows that the querystring cart[items][]=5&cart[items][]=6
should result in an object that looks like: { "cart" => { "items" => [ "5", "6" ] } }
.
I did some digging, and it looks like Rails uses Rack::Utils
‘ parse_nested_query
helper. Their documentation is somewhat less comprehensive than I’d hoped, so I experimented a bit to figure out what’s going on. Below are a few examples of what I discovered.
I’ve broken the query strings onto multiple lines simply for readability.
Simple Input
If your fields are named without any brackets, Rails will assume you don’t have any fancy nested data structure. This is the normal case for the web.
Querystring:
cart_item_1=5&
cart_item_2=6
Parsed data structure:
{ "cart_item_1" => "5",
"cart_item_2" => "6" }
Attributes of an Object
Using square brackets to name attributes, you can nest attributes inside an object:
Querystring:
cart[items]=5,6
Parsed Data Structure:
{ "cart" => {
"items" => "5,6"
} }
Arrays of Scalars
It is also possible to submit arrays. If a field name ends in the string literal “[]”, Rails will assume it’s an array. For example:
Querystring:
cart[items][]=5&
cart[items][]=6
Parsed Data Structure:
{ "cart" => {
"items" => [ "5", "6" ]
} }
Arrays of Complex Objects
Using the simple rules defined above, we can combine them to specify complex nested objects. The tricky part is knowing when a submitted value represents another field in this object and when it represents a value in the next entry in the array. Rails has a rule for handling this: it takes the attributes from left to right in the querystring (or top to bottom in your HTML), and creates a new object each time it sees a repeated attribute. A few examples should make this clear:
Querystring:
cart[items][][id]=5&
cart[items][][id]=6
Parsed Data Structure:
{ "cart" => {
"items" => [
{ "id" => "5"},
{ "id" => "6"}
]
} }
And if we add more fields to each entry such as:
Querystring:
cart[items][][id]=5&
cart[items][][name]=i1&
cart[items][][id]=6&
cart[items][][name]=i2
Parsed data structure:
{ "cart" => { "items" => [
{ "id" => "5", "name" => "i1"},
{ "id" => "6", "name" => "i2"}
]}}
Note that this is order dependent. Consider an example that erroneously interleaves the two objects. The result is probably not what was intended:
Querystring:
cart[items][][id]=5&
cart[items][][id]=6&
cart[items][][name]=i1&
cart[items][][name]=i2
Parsed data structure:
{ "cart"=> {"items"=> [
{"id"=>"5"},
{"id"=>"6", "name"=>"i1"},
{"name"=>"i2"}
]}}
Hopefully these examples will help you determine exactly what’s going on when Rails turns your form’s data into hashes.
I noted this behavior for the first time yesterday when trying to pass a parameter into a link_to in order to set a pre-determined form value. Thanks for the detail.