Wednesday, 31 March 2010

Rails - Day 14 - Rails Forms Part I

Rails  - Day 1 - The RubyGem Love Story
Rails -  Day 10 - Testing Rails Controllers
Rails -  Day 13 -ActiveRecord Relationships Part II
Rails - Day 15 - Rails Forms Part II
In the last post we finished off our sparse introduction to Rails ActiveRecord.  

We have now created the UI shell of our example application and we also have tested and defined our model that reflects the job in hand.  We re now ready to complete the server side code to tie the two together.

At the time I was writing this post, ASP.NET MVC 2.0 has just arrived and it is fairly obvious that they have taken a lot of their new ideas for their form builders from the rails form builders. 

We have already defined mark up for how the page will look

At the moment our new.html.haml page looks like this:

We have a simple table contained in a div.

Form Helpers

We are now going to refactor our page to take advantage of the Form helpers that come as part of the Rails library.  Form helpers are designed to make working with models easier compared to using standard HTML elements by providing a set of methods for creating forms based on your models.  The core method of this helper, form_for, gives you the ability to create a form for a model instance.  This helper generates the HTML for forms, providing a method for each type of input (e.g. text, password, select etc.).  When the form is submitted, the form inputs will be bundled into the params object and passed back to the controller.

In our example, we are going to create a form for the expense model object.  First we will only work on saving the Expense object and then move onto saving the Expense's child objects ExpenseType and ExpensePayment objects.  To take advantage of the rails form helpers, we refactor the above haml to the following:

The form_for method is possibly the most popular helper used when generating a form that binds to one type of object.  The parameter you pass through fomfor f is of type FormBuilder which offers the other methods you can see like check_box, date_select, text_field, text_area etc.

The above will generate the following HTML:

You can see from the above that the form is going to post to the /expenses url.  This URL is identical to the index action url of the ExpensesController but you should also notice that the method of the form above is of type post.  Rails maps the plural url and the post method to an action called create that is one of the seven default actions that rails creates for us with every resource we create. 

Another item of noteworthy mention is how the formbuilder has constructed the name fields of the inputs that you can see in the above html.  For example the checkbox for the Paid by Director field has a name attribute of expense[paid_by_director] which has been generated from the check_box method:

=f.check_box :paid_by_director,  { :class => 'check' }

When we submit this form rails will use nesting to nest the paid_by_director attribute inside a hash called expense.  Rails uses this to group the attributes of a single model inside a single hash.

It is also worth noting that for the checkbox, the formbuilder has generated 2 inputs for the f.check_box call.  One input is obviously of type checkbox but the other is a hidden field.  The hidden field is used to record the state of the checkbox when it is posted.  If a checkbox is not checked then it will not appear in the form post collection.  In this case rails will use the value in the hidden field.

When the form is submitted, the form inputs will be bundled into the params hash.

As I have mentioned in the last couple of posts, we are being good little TDD soldiers and because of this, we create the following functional test or controller test to test the create action of the .  I introduced functional testing in this post.

We create the following test to test passing the form fields to the form:

This is exactly the type of hash that gets routed to the create action of our ExpensesController from the above HTML form that is generated from our HAML.

If we want to know exactly what values are being passed to our create action, we can create add the following code to the create action:

Which will display the values of the params hash that is created when the submit button is clicked and should display something like the following in your browser of choice:

Another way of doing this is to change the action to the following:

Here we are raising an exception and calling the to_yaml method of the params hash to display the details of the hash hierarchically:

This will of course cause our test to fail so we will fall back to the original.

We have now tested that our create expense form is hooked up correctly.  We now need to add the code to complete the deed of creating a new expense.  We do this test first.

We update the previously created functional test to the following which is pretty reflective of a full functional test:

We are using flexmock to mock out the ActiveRecord calls as I am not a great believer in writing tests that run against the database.

We mock out the class methods of the Expense object to return exactly what we want to drive our tests in a more logical manner.

We then write the code to make the create action pass:

From the above, you should notice the following:

  1. We are creating a new Expense instance from the params hash that contains the form fields.  We are assigning this new object to the @expense instance variable.  This technique of setting multiple attributes at one time on a model is called mass assignment.
  2. The ActiveRecord save method returns a boolean that indicates whether the save was successful or not.
  3. In the event of a successful save, we are placing a notice in the flash provider and redirecting the index action which we access using the short cut convention expenses_url.  The flash provider is a way to pass temporary objects between actions.   Anything you place in the flash will be exposed to the very next action and then cleared out.  This is a great way of doing notices and alerts.  I seem to remember something similar in Castle.Igloo that later became Rhino.Igloo.
  4. If the save was not successful, we redirect back to the new action.

One problem that is often ignored with mass assignment is that any method can be called from a model that is contained in the params hash including database calls.  In order to prevent this we add the attr_accessible  method to our model and define the methods that we want editable.  To accomplish this, we update the Expense model class:

If we the save is successful, the browser will be redirected to the index action that displays all the Expenses.  We need to update the code to actually render the expenses from the database.

In order to do this, we add a new functional test that checks that the index method runs with records in the database:

Now we update our index.html.haml to the following:

If we successfully insert a record, we are greeted with the following confirmation in the expenses index view:

This post is much longer than I intended so I will leave things here.

In the next post, I will illustrate how to save the Expense as a whole.  This will include the child objects that are part of the complex expense object.

No comments:

Post a Comment