At this point, you have most of the ingredients needed to create simple web applications in Rails, provided you’re willing to stick to a single database table. There’s one large problem remaining: users (and programs connecting through web services) don’t always put in the data you want them to put in. Making your application work reliably requires checking information as it comes in and interacting with users so that they know how to get it right.
As you’ll see throughout this chapter, Rails expects all data validation to happen in the model layer and provides tools that make it easy to do there. If you find yourself putting data-checking code into the views or the controllers, pause for a moment—you’re quite likely doing something wrong.
The one probable exception is if you’re adding warnings for users working in your forms, avoiding a round trip to the server, but you should never rely on those to limit your data to the correct types. All that work should do is give users more information more rapidly.
You might think, since the examples in Chapter 6 defined data types, that Rails will be doing some basic content checking—ensuring that numeric data actually includes numbers, for example.
Nope. Rails and the Rails scaffolding give you places where you can add validation code, but absolutely none of it is built-in. The easiest way to see what happens is to try putting in bad data, as shown in Figure 7-1.
The text fields might not be the data you want, but at least they’re text. The boolean value and the dates are constrained to a few choices by the interface design already—you can’t choose bad data. However, “thousands,” “twenty-six,” and “not” aren’t numbers. But Rails doesn’t care—it accepts those strings and converts them to a number: 0 (zero), as shown in Figure 7-2.
You can see what happened by looking at the data that scrolled by
in the script/server
window (or in
the logs in Heroku) when the request went in. You don’t need a detailed
understanding of SQL to find the problem—looking at the data going in
will show it. Example 7-1 lists the data
going into the Rails app and then shows the SQL INSERT with the data
moving out from the Rails app to the database.
Example 7-1. Behind the scenes for bad data flowing to the application
Processing PeopleController#create (for 127.0.0.1 at 2008-03-08 18:42:04) [POST] Session ID: BAh7ByIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7 ADoMY3NyZl9pZCIlNmMxZGE3NDdiMmY3NDQzZWIz%0AZWM1NmQzNDFkZjBhYjM%3D-- ca58de858eb31bb2b1c88e0f1939fe085641c576Parameters: {"commit"=>"Create",
"authenticity_token"=>"8a2e7ade080a91f5c872cd8c783d2282576d6117",
"action"=>"create", "controller"=>"people", "person"=>{"name"=>"Sploink",
"birthday(2i)"=>"3", "favorite_time(5i)"=>"54", "birthday(3i)"=>"8",
"favorite_time(6i)"=>"49", "price"=>"not", "country"=>"Canada",
"body_temperature"=>"twenty-six", "description"=>"sdfsdfdsfasdfd",
"graduation_year"=>"thousands", "favorite_time(1i)"=>"2008",
"favorite_time(2i)"=>"3", "secret"=>"", "can_send_email"=>"1",
"favorite_time(3i)"=>"8", "email"=>"sasdas", "birthday(1i)"=>"2008",
"favorite_time(4i)"=>"11"}}
Person Create (0.000524) INSERT INTO people ("name", "updated_at", "price",
"country", "body_temperature", "description", "birthday", "graduation_year",
"can_send_email", "favorite_time", "secret", "created_at", "email")
VALUES('Sploink', '2008-03-08 18:42:04', 0.0, 'Canada', 0.0, 'sdfsdfdsfasdfd',
'2008-03-08', 0, 't', '2008-03-08 11:54:49', '', '2008-03-08 18:42:04', 'sasdas')
Redirected to http://localhost:3000/people/2 Completed in 0.01586 (63 reqs/sec) | DB: 0.00052 (3%) | 302 Found [http://localhost/people]
The parameters are complicated by the many pieces of incoming
dates that use a naming convention to identify their parts, but it’s
clear that “thousands,” “twenty-six,” and “not” went into the Rails
application. In the SQL command going to the database, price
and body_temperature
went in as 0.0, while
graduation_year
went in as 0.
Between receiving the data and sending it to the database, Rails converted those values to numbers. The strings became zero (0.0), since they weren’t actually numeric. Fixing this problem will require spending some time in the model, developing barriers that check incoming data and stop it if they don’t match your application’s requirements.