Topic: Revealing the Magic Behind Forms
Forms in Rails are mysterious. Add a few columns to a table, add a few fields to a form and BAM! You have yourself a functional app. The fields map to the database like magic. In this article I will take you behind the scenes so you can see exactly what Rails is doing.
In this example we have a User model with name and email attributes. We have a form for creating a User model:
# in new.rhtml
<% form_tag :action => 'create' do %>
Name: <%= text_field :user, :name %>
Email: <%= text_field :user, :email %>
<%= submit_tag 'Create' %>
<% end %>
To understand this fully we need to look at the HTML which is generated by this code.
<form action="/users/create" method="post">
Name: <input id="user_name" name="user[name]" size="30" type="text" />
Email: <input id="user_email" name="user[email]" size="30" type="text" />
<input name="commit" type="submit" value="Create" />
</form>
Everything looks pretty normal except for one thing: the name of the text fields. What's with those square brackets in "user[name]" and "user[email]"? This is part of the magic. Rails uses these square brackets to group all of the fields belonging to the User model.
When the form is submitted these text fields (with their wacky names) are sent to the server. Rails interprets these submitted values and puts them in a "params" hash which you then access in the controller. Take a look at the server log when this form is submitted to see how Rails groups the values.
Parameters: {"user"=>{"name"=>"Joe", "email"=>"joe@example.com"}, "commit"=>"Create", "action"=>"create", "controller"=>"users"}Rails is making a mini hash inside of this params hash. In other words, it is grouping all the user fields into its own section so you can easily fetch the User specific values.
This is only half of the story. Now take a look at the "create" action in the controller.
def create
@user = User.new(params[:user])
if @user.save
redirect_to :action => 'index'
else
render :action => 'new'
end
end
Here we are creating a new user (User.new) and passing "params[:user]" to this method. If you look back at the parameters in the log you can see that "user" is the name of the mini hash where the values are grouped. Here's what we're actually passing into the "new" method:
@user = User.new(:name => 'Joe', :email => 'joe@example.com')
If you are familiar with models and ActiveRecord you probably know that you can pass a hash when creating a model to set multiple attributes - exactly as we are doing here. When we save the model it all goes into the database. That is, unless there's a validation error.
When there's a validation error, the "save" method returns false so the "else" condition is executed. As you can see, this renders the form again. But, how do the values get back in the form fields?
Form helpers like "text_field" have a special behavior. They take the first parameter (the name of the model, in this case "user") and look for an instance variable with that same name (@user). It will then look up the attributes in this @user model and display them in the field.
The way Rails uses square brackets in the form field names may be a little strange at first, but it allows you to do some extremely powerful things with very little effort. See my other tutorials on handling multiple models in forms for examples of this.