1.5. Building Ingredients

Having now gotten a thorough tour of the new mechanisms that RESTful Rails provides by default, it's time for you to start writing some code and making this site come to life. The first task is to enable simple entry of a recipe, and allow the most recently entered recipes to be displayed on the user-centered front page, blog-style.

The following problems stand between you and that goal:

  • The database schema and sample code as generated do not associate recipes and ingredients, so the forms that were created by the scaffold do not have a place to enter ingredient information.

  • You changed the default routing after the scaffolds were generated, and therefore the ingredient forms, as generated, use invalid methods to create URLs.

  • The basic index listing of recipes is useful from an administrative point of view, but it is not what you want to present to a user. In addition to the functional changes, you'll need it to be much nicer looking.

That list will take you through the end of this chapter. Time to build a webapp!

1.5.1. Setting Up Your Database

Most of the work of setting up the initial database was already done when you created the resources and generated migrations, but you still need to actually create the database instances. You'll need to go to the database.yml file first and adjust the database information for all three database environments — development, test, and production. If you are using MySQL (version 5.x, please) and the database is on your local development box, then you probably only need to put your root password into the file. (More complicated database setups are discussed in Chapter 6, "The Care and Feeding of Databases.")

A late change in Rails 2.0.2 has made SQLite3 the default database for new Rails projects. The examples in this book use MySQL for the database connections.

Once that is done, you can use Rake to do all the database creation work, without touching your MySQL administration application. The first rake command (new in Rails 2.0) is this:

rake db:create:all

This command goes through your database.yml file and creates a database schema for each database listed for your local host.

Similarly, the rake db:create command creates only the development environment. The command creates empty database schemas. To populate the development environment with the tables and columns defined in the migration, enter the following command:

rake db:migrate

And to take that development environment and copy it to the test database, enter the following command:

rake db:test:prepare

This gives you all the database setup you need to get started.

1.5.2. Aligning Your Tests to the Nested Resource

I'm a firm believer in automated testing — unit, functional, and integration — so I love the fact that Rails includes such a complete test suite. It's very important to keep that suite current and running clean. I know that some of the tests will fail based on the routing changes that were made, but the first thing to do is get a sense of the damage with the following (this output has been modified slightly for readability):

$ rake
(in /Users/noel/Documents/Programming/ruby/soupsonline)

/usr/local/bin/ruby -Ilib:test
   "/usr/local/lib/ruby/gems/1.8/gems/rake-
   0.7.3/lib/rake/rake_test_loader.rb"
   "test/unit/ingredient_test.rb"
   "test/unit/recipe_test.rb"
Started
..
Finished in 0.327569 seconds.

2 tests, 2 assertions, 0 failures, 0 errors
/usr/local/bin/ruby -Ilib:test
   "/usr/local/lib/ruby/gems/1.8/gems/rake-
   0.7.3/lib/rake/rake_test_loader.rb"
   "test/functional/ingredients_controller_test.rb"
   "test/functional/recipes_controller_test.rb"
Loaded suite /usr/local/lib/ruby/gems/1.8/gems/rake-
   0.7.3/lib/rake/rake_test_loader
Started
EEEEEEE.......
Finished in 1.732989 seconds.
14 tests, 13 assertions, 0 failures, 7 errors

Looking at the errors, it seems that all the functional tests of the ingredients controller failed, as expected. The following section describes what you need to do to clean them up.

1.5.2.1. The Test Object

Rails sets up some test data in the fixtures directory, which can be loaded into your test directories to enable database-backed objects to work. By default, each controller test loads the fixtures for the data type the controller manages. However, now that the ingredients resource is subordinate to the recipe resource, the ingredients controller test also needs to load the recipe fixtures. This enables the controller to access recipe data during testing. Add the following line to test/functional/ingredients_controller_test.rb, right below where the ingredient fixture is loaded:

fixtures :recipes

Now, in the tests, there are two things that need to be fixed consistently throughout the test. Each individual test calls the get, post, or put helper method to simulate the HTTP call. Each and every one of those calls needs to add a parameter for the recipe_id. You can do this by adding the argument to each of the calls (remember to place a comma between hash arguments — for some reason I always forget that comma):

:recipe_id => 1

A couple of the tests also confirm that Rails redirects to the ingredient index listing, with a line like this:

assert_redirected_to ingredient_path(assigns(:ingredient))

This line no longer works because, now that ingredients are a nested resource, the pathnames are all defined in terms of a parent recipe. Change that line every time it appears to this:

assert_redirected_to
   recipe_ingredient_path(assigns(:recipe),
    assigns(:ingredient))

This changes the name of the helper method, and adds the recipe object to the arguments. The assigns method gives access to any instance attributes set in the controller action.

1.5.2.2. The Controller Object

Because you are going to be testing for it, you need to make sure that every controller method actually does assign a @recipe attribute. The best way to do that is with a before filter. The before_filter method allows you to specify a block or method that is performed before every controller action gets started. Add the following line to the beginning of the IngredientController class in app/controllers/ingredient_controller.rb:

before_filter :find_recipe

This specifies that the find_recipe method needs to be run before each controller action. To define that action, add the method to the end of the class as follows:

private

def find_recipe
  @recipe = Recipe.find(params[:recipe_id])
end

It's important that the method go after a private declaration; otherwise, a user could hit /ingredients/find_recipe from their browser, and invoke the find_recipe method, which would be undesirable. This mechanism ensures that every controller action will have a recipe defined, and you no longer need to worry about consistency. Readability can be an issue with filters, though, because it can sometimes be hard to track back into the filter method to see where attributes are defined. It helps to make smaller controllers where the filters are simple and clear. You'll see another common use of filters in Chapter 3, "Adding Users."

Next, you need to clean up the redirections. Two actions in this controller redirect to the show action using redirect_to(@ingredient). Change those as follows:

redirect_to([@recipe, @ingredient])

The redirection method automatically handles the list of nested resource objects. The destroy action redirects to the list action, so you need to change that redirection as follows:

redirect_to(recipe_ingredients_url)

In this case, the controller automatically infers that it should use the @recipe attribute to generate the correct index path.

1.5.2.3. The Views

All you need to do to the view objects at this point is change the URLs for the forms and links. The form declaration in the edit and new views (in app/views/ingredients/edit.html.erb and app/views/ingredients/new.html.erb) should now read as follows:

<% form_for([@recipe, @ingredient]) do |f| %>

Again, this implicitly creates the correct URL from the two objects.

You also need to change the URL in the edit page (app/views/ingredients/edit.html.erb) as follows:

<%= link_to 'Show', [@recipe, @ingredient] %>

You make the same change to the URL on the index page (app/views/ingredients/index.html.erb), except in this case, ingredient is a loop variable, not an instance variable, so you don't include the @ sign.

Similarly, you need to change all the named routes by adding the prefix recipe_ to the method name and including the @recipe variable in the argument list. The link to the index page, accessed via the back link on several pages in app/views/ingredients should be changed to this:

<%= link_to 'Back', recipe_ingredients_path(@recipe) %>

You also need to make changes to the other named links. Here are some examples:

<%= link_to 'Edit', edit_recipe_ingredient_path(@recipe, @ingredient) %>
<%= link_to 'Destroy', [@recipe, ingredient],
    :confirm => 'Are you sure?', :method => :delete %>
<%= link_to 'New ingredient', new_recipe_ingredient_path(@recipe) %>

At this point, all your tests should run cleanly. If not, an error message will likely be displayed, showing you exactly which method name change you missed. When you make the analogous change in the edit view, note that the edit link in the index.html.erb page does not include the @ sign for the ingredient, as it is a loop variable, not an instance variable.

Rails Testing Tip

The default test runner text is fine as far as it goes, but sometimes it's not very easy to tell which methods have failed. If you include diagnostic print statements in your tests while debugging, it can be difficult to tell which output goes with which tests.

There are a few options for more useful test output. Most IDEs include some kind of graphical text runner, and over the past year or so, several Java IDEs have added Rails support — Aptana for Eclipse, NetBeans, and IntelliJ all have graphical Rails test runners. There are also a couple of available stand-alone GUI test runners, depending on the operating system you are running.

I've come to like a little gem called turn, which you can install and then place the line require 'turn' in your test_helper.rb file. It produces somewhat more useful and verbose test-runner output. The error message for each test is associated with that test, as is any diagnostic output. And if your command shell supports it, tests that pass are in green and tests that fail are in red. Here is some sample output:

IngredientsControllerTest
    test_should_create_ingredient PASS
    test_should_destroy_ingredient PASS
    test_should_get_edit PASS
    test_should_get_index PASS
    test_should_get_new PASS
    test_should_show_ingredient PASS
    test_should_update_ingredient PASS
RecipesControllerTest
    test_should_create_recipe PASS
    test_should_destroy_recipe PASS
    test_should_get_edit PASS
    test_should_get_index PASS
    test_should_get_new PASS
    test_should_show_recipe PASS
    test_should_update_recipe PASS
====================================================================
   pass: 14,  fail: 0,  error: 0
   total: 14 tests with 25 assertions in 1.768561 seconds
====================================================================

Because turn changes the format of your text output, other plugins or tools that depend on the test output — most notably Autotest (see Chapter 7 — might have problems.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.181.66