Laziness and Stupidity, Part 2 7

Posted by jeff Tuesday, May 20, 2008 14:23:00 GMT

Last time, I presented a couple screenshots from a recent user experience I had on the web. (If you missed it, go back and read it real quick, or the rest of this post won’t make much sense).

Thanks to everyone who responded. It was fun to see the variety of approaches that people took to solving the problem of cleaning up the “miles driven” field automatically.

For what it’s worth, the approach I would have taken would have been to use the before_validation helper in the model, as a couple of people suggested (hence the words “model validation” in the original article title). There were a few suggestions that seemed to imply that the cleanup would happen in the controller, but I don’t think that’s the right place. By making the model responsible for its data, this logic becomes more reusable and it becomes easier to attach error messages as needed.

For the curious, to just remove commas and ignore any digits after a decimal point, this will work:

miles.delete(',').to_i

but this doesn’t remove any other non-digit characters, so a value like “A123.4” will become zero. This is one of those rare moments where C# is a bit better, as Int32.Parse would come in real handy here.

But rather than try to come up with a more complicated regular expression to get rid of letters and other invalid characters, I would simply use another model validation helper to enforce a greater-than-zero value:

validates_numericality_of :miles, :greater_than => 0, :message => "must be higher than zero"

I guess my point is, I think sometimes we try to do too much. Rails is a pretty big framework and often has something built-in to make your job easier than you think.

Laziness and Stupidity; or, Model Validation in Rails 17

Posted by jeff Sunday, May 18, 2008 00:47:00 GMT

I thought my post on how to customize the ActiveRecord error header messages was a one-off about how we, as web developers, should treat our users as humans. But it looks like this is turning into an occasional series.

Today’s Victim

I’m an avid fan of Consumer Reports. I’ve subscribed for about as long as I can remember. Subscribers get to take part in their annual survey, which they mainly use for their car ratings. I kind of hated in the past, since their survey site was so lame and hard to use. I think it might have been an old-school perl-driven cgi script, if I remember correctly.

But this year, I was pleasantly surprised that it had been given a nice facelift. From the url I can see that they’re now using something more modern (ASP.NET).

But then, about four questions in, I got this:

Don’t use commas? Are you kidding me?

This is the kind of crap that permeates the web and is just sheer stupidity.

Would it really be so hard for the .NET app to remove commas from whatever value I enter? (No, it wouldn’t be, it’s actually pretty easy… but I digress). My guess is, the database column is an integer column, and if there’s a comma in the number, the insert statement will fail. So instead of writing code to take commas out, they tell the user to make sure they only enter integer numbers.

This is amazing. Shame on CR for letting their developers get away with this.

Since I want to help CR by taking the survey, I made sure not to use commas and entered “5000” and clicked Next.

It Gets Even Worse

But curiosity got the better of me, and so I clicked “Previous” to go back. Then I entered “5,000” with the comma, just to see what would happen. I began to think, maybe the bark is worse than the bite? Surely, the asp.net code really could deal with commas in numbers, right?

Nope:

Look at that awful hideous generic error message. Listen to the tone: you did something wrong, you idiot! Now, fix it!

We’ve already gone over how to customize these kinds of error messages in Rails. But even better is to avoid this situation in the first place.

If this kind of thing is hard to do in your favorite framework, then switch to Rails. Because it’s really easy to do in Rails.

Your Mission, Should You Choose To Accept It

I was planning on doing a little “Validation 101” here to demonstrate how your Rails applications can elegantly clean up user input before it gets to the database.

But I thought it might be more fun this time around to let you do the talking. So here’s my challenge to everyone reading this blog:

  1. Leave a comment to this post, giving the most elegant solution you can think of to remove commas from a text field in Rails.
  2. You can assume that there is an ActiveRecord class named Vehicle that has an attribute named miles.
  3. IMPORTANT: Be sure to put your code inside of a big <pre> tag.

Bring it on!

Tooltips for Rails Validation Errors 0

Posted by jeff Friday, May 09, 2008 17:47:00 GMT

A while back we talked about humanizing validation error header messages in Rails. Mike Sepcot now shows you to create error-specific tooltips by using a custom FormBuilder. Very cool stuff, so check it out.

Using Thin Instead Of Mongrel 18

Posted by jeff Sunday, April 27, 2008 17:13:00 GMT

I’m learning how to switch from mongrel to thin. If you’ve never heard about thin, you can try to read the documentation, learn about the new-cool-kid-on-the-block Rack specification, or just take my word for it: thin is better than mongrel, so just use it, ok?

It can be intimidating to try something that’s got buzz about it but you don’t really know how to get started and no one seems to really be stopping to explain it in English. So if you’re still using script/server to start mongrel or webrick, and are interested in something new, then read on.

The main reason people switch from mongrel to thin is for improved performance on their servers. But for me to learn something new, I have to first dogfood it in my development environment first. That’s what I’ll be covering in this article, and in an article coming soon to a blog near you I’ll explain how to replace your mongrel configurations with a thin configuration instead.

One note: thin is currently best suited for Mac and Linux people. I’ve used it on Windows as simple script\server replacement, but on Windows background (“daemon”) mode isn’t supported, nor is clustering, so Mongrel is still my choice on Windows boxes.

gem install thin

It won’t wreck your computer. Honest. Just do it.

Using thin to replace script/server

script/server doesn’t know to startup thin instead of mongrel (as of Rails 2.0.2). So instead, you just go to your RAILS_ROOT and do this:

myapp$ thin start

You’ll see it start up on port 3000 using the “Rails adapter.” (Turns out thin can support a variety of Ruby frameworks, not just Rails).

Need to use a different port, say, 4000?

myapp$ thin start -p 4000

On Mac/Linux, you could use script/server -d. With thin, you also use -d:

myapp$ thin start -d

To stop thin that’s been start with -d, you just, um, stop it:

myapp$ thin stop

By default, thin will create .pid files in your applications /tmp folder. If you prefer to keep your .pid files in the log directory like Mongrel did, you can specify that:

myapp$ thin start --pid log/thin.pid

Just be sure to stop it the same way:

myapp$ thin stop --pid log/thin.pid

More Options

Do a thin -h to see all the possible options. I’ll highlight just a couple more.

To specify your Rails environment, use -e just like with Mongrel:

myapp$ thin start -e production

Stopping and restarting the server is easier than it was with mongrel:

myapp$ thin restart

(Remember to also use your -e, -d,—pid, etc. options here as well if you need to.)

Next Time

Next time we’ll talk about how to setup a cluster of thin instances on your server, instead of using mongrel_cluster.

And I’d also like to be able to answer any other questions about script/server, mongrel, thin, nginx, apache, or why the Blackhawks missed the playoffs again this year. Leave a comment with your suggestions and questions if you’re so inclined.

Creating Edge Rails Projects via Git on Windows or Mac 2

Posted by jeff Thursday, April 24, 2008 15:19:00 GMT

Brian Hogan has a sequel to his original article for creating a local Rails project with the edge version of Rails. Now that Rails is in Git instead of Subversion, Windows users especially have to jump through a hoop or two to get the code down.

For those new to Rails, “edge Rails” referes to the latest bleeding edge of the Rails code (the “nightly builds”, if you will, though edge rails is built continuously as patches are submitted, not just nightly). Creating a new Rails project with edge rails, instead of a released version like 2.0.2, allows you to start learning about the features that will be in the next version. If you (like me) like to do that, Brian’s article is a great resource.

Better messages for ActiveRecord validation errors 19

Posted by jeff Wednesday, April 23, 2008 02:14:00 GMT

So you’ve been working with Rails for some time and you think you know what you’re doing, right? And you know to remove all your scaffolding before you ship, right?

Suppose you’re writing an app to let two people play a game over them thar tubes.

Would you still do this?

<%= error_messages_for :game %>

Do you realize that your users will see something like this?

lame_error

error_messages_for is scaffolding in disguise. It will help you get going but it’s meant to be thrown away. Only computer geeks like to know about “fields” and things being “saved.”

Your users just want to start a game. Treat them like humans and specify your own header and subtitle messages. (Rails documentation does exist despite everyone’s complaints that there’s “no documentation”).

So how about something like this instead:

<%= error_messages_for :game, :header_message => "Please Try Again!", :message => "We had some problems starting the game:" %>

which will produce something like:

nice_error

Absolute Moron's Guide to Forms in Rails, Part 5 15

Posted by jeff Thursday, April 10, 2008 13:05:00 GMT

List Boxes in Rails

Today we will tackle the more thornier controls: list boxes and combo boxes. Let’s add the ability to select the origin and destination airports for our flight from a list of available airports.

Airports

We need a small list of airports to choose from, so let’s create a lookup table.

script/generate scaffold Airport code:string
rake db:migrate

Use the generated scaffold UI at /airports to add a few airports. Here’s what I chose:

We also need add two columns to our flight model:

script/generate migration AddAirportsToFlight origin_id:integer destination_id:integer
rake db:migrate

Notice that we’re storing integers, not strings this time. This is so we can hold foreign keys to the appropriate rows in the Airports table. We also need to open up the flight.rb model file and specify the relationship between flights and airports.

class Flight < ActiveRecord::Base

  belongs_to :origin, :class_name => "Airport" 
  belongs_to :destination, :class_name => "Airport" 

end

If you’re not very familiar with ActiveRecord yet, the code here uses a slightly more advanced syntax of the belongs_to method since the lookup table class name is not related to the attribute names we want in our Flight class. (If Rails knew that “origin_id” and “destination_id” are intended to be foreign keys to the Airports table, that would be magic indeed).

Similarly, let’s hook things up on the Airport side of the equation:

class Airport < ActiveRecord::Base

  has_many :departures, :class_name => "Flight", :foreign_key => "origin_id" 
  has_many :arrivals, :class_name => "Flight", :foreign_key => "destination_id" 

end

Before we use any GUI sugar, open up your console and hook up a flight to two airports (I’ve omitted a bunch of the console output for clarity):

>> f = Flight.find(1)
=> #<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-05 20:05:24", meal: false, equipment: nil, origin_id: nil, destination_id: nil>

>> ord = Airport.find_by_code 'ORD'
>> pdx = Airport.find_by_code 'PDX'

>> f.origin = ord
>> f.destination = pdx
>> f.save!
=> true

And now, behold the beauty and power of ActiveRecord associations that give us a natural-language vocabulary for our models.


>> ord.departures
=> [#<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-06 10:22:23", meal: false, equipment: nil, origin_id: 1, destination_id: 2>]
>> pdx.arrivals
=> [#<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-06 10:22:23", meal: false, equipment: nil, origin_id: 1, destination_id: 2>]

Awesome!

Now let’s see how we can make those associations in our form instead of through the console.

Selecting Airports

In HTML, listboxes and combobox (drop-down list) controls are both known as selection lists, and are implemented by the <select> tag. Nested inside the <select> tag are one or more option tags. Each option describes one list item.

The form_for builder gives us two waysto construct selection lists without having to manually construct each <option> tag individually. One is the select helper, which provides full control over how the <select> and <option> tags are generated. Another is collection_select, which provides a simpler syntax if:

  • You can provide the list of items as a Ruby collection
  • Each item in the collection has a method that can be called to provide a displayable string for that item
  • Each item also has a method that can be called to supply the value that should be sent to the application when the item is selected by the user.

These three conditions are the 80% case when you’re constructing the list from an ActiveRecord table. Some code will make it clearer. Open up new.html.erb again and add this code into the form:


<% airports = Airport.find(:all, :order => :code) %>
<p>Depart from: 
  <%= f.collection_select :origin_id, airports, :id, :code %>
</p>

<p>Arrive at: 
  <%= f.collection_select :destination_id, airports, :id, :code %>
</p>

We start by capturing all of the airports into the airports variable. Note that we use the <% %> syntax, without the equal sign, whenever we don’t want the result to be sent back to the browser.

We then use the collection_select helper to construct our combo box. Here are the parameters we had to pass:

  • First is the attribute of our model we’re trying to assign to (origin_id or destination_id)
  • Second is the Ruby collection. We will get one <option> tag – one item in the list – for each object in the collection.
  • Third is the method on the collection items that should be called on the selected item when it’s time to transmit the selection to the application.
  • Last is the method on the collection items that should be called to generate that item’s displayed string in the list.

Navigate to the form in your browser:

Finally, let’s make sure we understand how this ends up in our new flight model. Select some values and click Create.

Back To That Bridge Again

In my case, I created flight #444 from Chicago to Portland on a Boeing 777. Take a look at your log file (log/development.log) and find where the create action was called. Mine looks like this:

Processing FlightsController#create (for 127.0.0.1 at 2008-04-06 14:31:39) [POST]
  Session ID: BAh7BzoMY3NyZl9pZCIlZmRhM2FmODI0ZjA1MjYwMzgxMGJiMjcyYjBiOTNi%0AMTUiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--273a533fb4cee21ea44cb1ccdc6d70db908d7525
  Parameters: {"commit"=>"Create", "authenticity_token"=>"e9e1c0b0d744f33db348cd70a69cf70d6126c66b", "action"=>"create", "controller"=>"flights", "flight"=>{"number"=>"444", "origin_id"=>"1", "meal"=>"0", "destination_id"=>"2", "equipment"=>"747"}}
  Flight Create (0.000350)   INSERT INTO flights ("updated_at", "number", "origin_id", "meal", "destination_id", "equipment", "created_at") VALUES('2008-04-06 14:31:39', '444', 1, 'f', 2, '747', '2008-04-06 14:31:39')
Redirected to http://localhost:3000/flights/4
Completed in 0.00958 (104 reqs/sec) | DB: 0.00035 (3%) | 302 Found [http://localhost/flights]

Zooming in on just the parameters and reformatting for clarity:

Parameters: { "commit"=>"Create",
                   "authenticity_token"=>"e9e1c0b0d744f33db348cd70a69cf70d6126c66b",
                   "action"=>"create", 
                   "controller"=>"flights", 
                   "flight"=> { "number"=>"444", 
                                "origin_id"=>"1", 
                                "meal"=>"0", 
                                "destination_id"=>"2", 
                                "equipment"=>"747"}
                              }

See how all of our form data is in the tidy flight hash? We can easily access all the form values with params[:flight]. The important thing to notice are the values that came across for the selected airports.

Here’s another way to confirm to ourselves that it worked. Open up the Rails console again and find the flight:

>> f = Flight.find_by_number '444'
=> #<Flight id: 4, number: "444", created_at: "2008-04-06 14:31:39", updated_at: "2008-04-06 14:31:39", meal: false, equipment: "747", origin_id: 1, destination_id: 2>
>> f.origin.code
=> "ORD"

And for fun, check out the arrivals for the Portland airport:

>> Airport.find_by_code("PDX").arrivals
=> [#<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-06 10:22:23", meal: false, equipment: nil, origin_id: 1, destination_id: 2>, #<Flight id: 4, number: "444", created_at: "2008-04-06 14:31:39", updated_at: "2008-04-06 14:31:39", meal: false, equipment: "747", origin_id: 1, destination_id: 2>]

List Box instead of Drop-down Box

If you’d prefer to show a listbox instead of a combobox, we need to tell the browser to show more than one item at a time. To do that we need to pass two more parameters to the collection_select method:

  • An empty hash. See the Rails API docs for more info here – this hash can contain optional elements like whether to include a blank item at the top of the list, etc.
  • A hash of attributes that should be glued into the HTML <select> tag. We need to insert a size attribute with the value 3. Anything larger than 1 will end up looking like a listbox instead of a combobox.

Adding these options and playing with our <p> tags a bit means we have code that now looks like this:

  <p>Depart from:</p> 
    <%= f.collection_select :origin_id, airports, :id, :code, {}, {:size => 3}  %>

  <p>Arrive at:</p>
    <%= f.collection_select :destination_id, airports, :id, :code, {}, {:size => 3} %>
  </p>

and the form now looks like this:

You Are Now Free To Create Your Own Forms

We’ve gotten a few questions regarding the best way to handle several different models on the same form, multiples of the same model on the same form, and how to use advanced options of how to use form_for when your controller is a namespace (if your controller is within an “admin” namespace, for example). We’ll try to address those issues in subsequent postings in the very near future.

But for now, that’s it for our whirlwind tour of forms in Rails. Remember:

  • Ruby templates generate HTML. So learn HTML.
  • The params has is your bridge between your user and your application. Learn to use the log files well.
  • Follow Rails conventions to make life easy.

Questions? Drop us a comment.

Absolute Moron's Guide to Forms in Rails, Part 4 3

Posted by jeff Tuesday, April 08, 2008 02:05:00 GMT

Photo credit

Radio Buttons in Rails

Today we continue our tour of forms in Rails. Let’s continue by learning how to add radio buttons onto our form.

In Part 1 our form contained a textbox so that we could create a new flight given a flight number, and in Part 2 we added a checkbox for meal service. Let’s add a few radio buttons so that we can associate the flight with the kind of airplane that should be assigned to the flight.

Adding another column to the Flights table

Airlines often refer to the model of airplane as the “equipment,” so I’m going to use that term instead of the potentially confusing term “model”. Create another migration file, but this time we’ll use some command-line options to make our job easier:

script/generate migration AddEquipmentToFlight equipment:string
rake db:migrate

or for those on Windows

ruby script\generate migration AddEquipmentToFlight equipment:string
rake db:migrate

Presto, we’ve added a string column named equipment to the Flights table.

Adding Radio Buttons

Now that we have our new column, let’s add some radio buttons. Open up the new.html.erb template again, and use the form builder object to add three radio buttons:

<p>
  <p>
    <%= f.radio_button :equipment, '747' %><b>Boeing 777 (Jet, Nice)</b>
  </p>
  <p>
    <%= f.radio_button :equipment, 'MD80' %><b>MD-80 (Jet, Old)</b>
  </p>
  <p>
    <%= f.radio_button :equipment, 'Cessna' %><b>Cessna (Propeller)</b>
  </p>

Navigate to the form in your browser:

As always, look at the actual HTML that got generated. It’s important to understand the HTML that your embedded Ruby generates. Here’s the HTML for the first radio button:

<input id="flight_equipment_747" name="flight[equipment]" type="radio" value="747" /><b>Boeing 777 (Jet, Nice)</b>

As you know by now, the name attribute follows the conventions necessary to make good things happen in our params hash. Notice that all of our radio buttons have the same name attribute, but that the browser will only submit the name/value pair for the button that the user selects. Radio buttons with the same name are considered part of the same radio button group, so that the user can only select one choice per group.

Go ahead and choose an airplane, and then click the Create button. Open up script/console to convince yourself that the equipment value was saved:

>> f = Flight.find_by_number '567'
=> #<Flight id: 3, number: "567", created_at: "2008-04-06 09:55:14", updated_at: "2008-04-06 09:55:14", meal: false, equipment: "Cessna">
>> f.equipment
=> "Cessna" 

By now, using form_for and the form builder methods should be feeling more natural. Next time, we’ll be adding listboxes and combo boxes (drop-down lists). In the meantime, if you have comments or questions on what we’ve done so far, leave us a comment below.

Absolute Moron's Guide to Forms in Rails, Part 2 5

Posted by jeff Tuesday, April 01, 2008 16:25:00 GMT

Photo Credit

Last time, we created a Flight resource and took a peek at the generated scaffolding code. In particular, we opened up the new.html.erb template. Today we’re going to take a closer look at the code in that template to understand how it really works.

Recall one of the core concepts we established last time: requests from the browser travel over a bridge and into your controller code. That bridge is the params hash, generated for you by Rails itself, and is your code’s window to the outside world.

With that concept in mind, and without further ado…

The “New” form submits to the “Create” action

Rails views that end with the suffix .html.erb are designed to generate html, which in turn will be sent back to the browser. Let’s look at the new.html.erb file again:

<% form_for(@flight) do |f| %>
  <p>
    <b>Number</b><br />
    <%= f.text_field :number %>
  </p>

  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

The .erb extension indicates “embedded Ruby”, and indeed we can see Ruby code that’s been embedded into the surrounding HTML landscape. The Rails pipeline executes the Ruby code, and then sends the entire resulting HTML back to the browser. To see what got generated, use the View Source feature of your browser and locate the <form>:

<form action="/flights" class="new_flight" id="new_flight" method="post">
    <div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="025e8a2b92e325ea0a34ecf040693c0f74262a41" /></div>
<p>
  <b>Number</b><br />
  <input id="flight_number" name="flight[number]" size="30" type="text" />
</p>

<p>
  <input id="flight_submit" name="commit" type="submit" value="Create" />

</p>
</form>

Let’s zoom in on the form element itself:

<form action="/flights" class="new_flight" id="new_flight" method="post">

That’s the HTML tag for creating a form. In HTML, the action parameter is required and tells the browser what to do when the user clicks the submit button.

  • We want the browser to call our create action, so we use the url /flights
  • Similarly, method tells the browser which HTTP verb to use. In this case, we’re using POST

Those of you who paid attention during our REST series will recall that for a resource named Flight, the url /flights with a method of POST will trigger the create action in a controller named FlightsController in our Rails application.

form_for and More

Now, how did we generate this form tag with these attributes? We did it with the form_for method in Rails:

form_for(@flight) do |f|

form_for is only useful for creating a form to act upon an ActiveRecord-derived model, but that’s the 80% case when developing Rails applications. Suffice to say, you’ll be using form_for a lot.

form_for knew how to generate the <form> tag and all of its attributes by examining the flight variable. Recall how this variable was created by the controller’s new action:

@flight = Flight.new

Since it’s a new Flight instance, and has not yet been saved to the database, form_for knows that we want to make a form for creating a new flight in the database, not for updating an existing flight record. Therefore, our HTML form will want call the create action; and as a result, the url must be /flights and the method must be set to POST.

(Yes, if you’re not following RESTful controller conventions, you can override the url and method yourself; but don’t expect me to help you with that: you can go look it up yourself, you non-conformist, you.)

Back in Part 1, we explained that between the browser and your controller code is a bridge. The bridge is built mostly of your routes.rb file, which enables Rails to transform incoming HTTP requests into a single Ruby hash called params.

Let’s zoom in on that textbox in our form. First, look at the Ruby code in our template:

<%= f.text_field :number %>

which became this HTML

<input id="flight_number" name="flight[number]" size="30" type="text" />

How did it do that and why?

  • We are using the block variable, f.
  • f is given to us by form_for and knows how to generate form elements for us.
  • f is called a form builder.
  • We are calling the text_field method, which specifically knows how to generate an input tag with type="text".
  • The first parameter, :number, helps it generate a the id and name attributes of the input tag.

The id attribute is helpful for CSS and DOM access, but has no bearing on what happens when the form is submitted. The name attribute, on the other hand, is very important. Rails will automatically populate the params hash with data coming in from the form. It uses the name attribute of the input element as the key in our hash.

If this doesn’t make sense right away, let’s take a step back for a moment. Suppose our HTML looked like this instead:

<input id ="my_id" name="sport" type="text">

This creates a text field named sport. Suppose further that we typed the value “hockey” into this text field and submitted the form. What would happen? Rails would add a key of “sport” with the value “hockey” into the params hash, and we could easily discover what was typed into the text field like this:

def create
    favorite_sport = params[:sport]
    # favorite_sport is now 'hockey'
    ...
end

Now for the cool part. You can isolate all of your form’s data into your very own hash inside the params hash by following a simple convention. In the name attribute of your input tag, use square brackets. The part before the brackets will be name of your hash, and the part inside will be the key into your hash. That’s what f.text_field :number did for us:

<input id="flight_number" name="flight[number]" size="30" type="text" />

Here, the name is flight[number]. When Rails creates the parameter values for us, it will create a nested hash called flight, and inside that hash, it will create a key called number, and its associated value will be whatever was typed into the form as the flight number. The big payoff for all of this? params[:flight] will give us a hash of all of the key/value pairs submitted by our form.

We can now understand why this code that was generated in our create action:

@flight = Flight.new(params[:flight])

This creates a new Flight object, instantly initialized with all of the data coming from our form.

The Catch

In order for this magic to work, you have to be careful how you use the form builder object. The first parameter must be the name of an attribute on your model (that is, a column in your table). Rails not only uses that attribute to pre-fill the form for you based on the model object’s initial state, but the Flight.new(params[:flight]) code is expecting attribute/value pairs that correspond only to your model’s attributes.

What About Other Controls?

We’ve traced the lifecycle of a textbox from Ruby template, to HTML, to params hash, to controller code. In the next article we’ll learn how to use the same form builder object to easily bind checkboxes, radio buttons, and list boxes to our model as well.

Absolute Moron's Guide to Forms in Rails, Part 1 5

Posted by jeff Friday, March 21, 2008 16:52:00 GMT

Photo credit

Today we’re starting a new series that we hope will help those who are new to Rails, and especially those who might be new to web development (helloooo, all you graphic designers out there!). We’re going to begin today with a very small form with just one text field, to demonstrate the important connection between a form and a controller. Subsequent articles will demonstrate how to use HTML controls like combo boxes, list boxes, and radio buttons.

Forms in Rails

We’re presuming you know Ruby, have done a little work with Rails, and have read our introduction to REST or are already familiar with how to write RESTful controllers.

We’re also presuming you’re using Rails 2.0.2 or higher. Do a rails -v to make sure, and a gem install rails if you need to upgrade.

Let’s get started. For this series, we’ll be using an example based on a brand new Rails application, and you can code along with it if you want.

Start a new Rails application:

rails forms101
cd forms101

If you’re on Mac OS X 10.5, or Windows with SQLite3 installed, you’re ready to go. Any other case, you’ll need to create your development and test databases manually, or do this:

rake db:create:all

Em Vee Cee

Say it with me now: “M-V-C.” Everything you learn in Rails must always be put into the context of its architecture, and learning how to create web forms in Rails is no different.

To demonstrate, we’ll need a model to play with. Heck, let’s just generate a controller and some views while we’re at it. We’ll continue our theme from the REST series and use airline flights as our example.

Generate a full MVC scaffold for a flight, with a flight number:

script/generate scaffold flight number:string
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/flights
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  app/views/flights/index.html.erb
      create  app/views/flights/show.html.erb
      create  app/views/flights/new.html.erb
      create  app/views/flights/edit.html.erb
      create  app/views/layouts/flights.html.erb
   identical  public/stylesheets/scaffold.css
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      create    app/models/flight.rb
      create    test/unit/flight_test.rb
      create    test/fixtures/flights.yml
      exists    db/migrate
      create    db/migrate/003_create_flights.rb
      create  app/controllers/flights_controller.rb
      create  test/functional/flights_controller_test.rb
      create  app/helpers/flights_helper.rb
       route  map.resources :flights

On Windows, your command line would be:

ruby script\generate scaffold flight number:string

Next, update the database:

rake db:migrate

And now we can instantly play with our new Flight model:

script/console
>> Flight.count
=> 0
>> Flight.create :number => "123" 
=> #<Flight id: 1, number: "123", created_at: "2008-03-20 16:39:33", updated_at: "2008-03-20 16:39:33">
>> Flight.count
=> 1
>> Flight.find :first
=> #<Flight id: 1, number: "123", created_at: "2008-03-20 16:39:33", updated_at: "2008-03-20 16:39:33">
>> 

We’re off the ground, so to speak.

Params Are Your Friends

We now have one, glorious flight record in our database. Let’s see it in a browser. Start up a local web server on port 3000:

script/server

(or ruby script\server on Windows).

and now go to localhost:3000/flights/1 in your browser:

Not very exciting, since our model has only one attribute.

Now for the important part: look at your development.log file. You should have something like this:

Processing FlightsController#show (for 127.0.0.1 at 2008-03-20 16:58:14) [GET]
  Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--3336be1e6bd3c65be2845c8980bf23a200d5b678
  Parameters: {"action"=>"show", "id"=>"1", "controller"=>"flights"}
  Flight Load (0.000268)   SELECT * FROM flights WHERE (flights."id" = 1) 
Rendering template within layouts/flights
Rendering flights/show
Completed in 0.05683 (17 reqs/sec) | Rendering: 0.00181 (3%) | DB: 0.00027 (0%) | 200 OK [http://localhost/flights/1]

Smack in the middle is the line we need to look at:

Parameters: {"action"=>"show", "id"=>"1", "controller"=>"flights"}

This part of the log is important, because it shows the how the routes.rb file worked to map the incoming request header into parameters your application can work with. In the case of a GET request, this really just means looking at the url and splitting into pieces. For POST, PUT, and DELETE, it includes slurping up any encoded data in the request body as well, which we’ll see later on.

Now that the params have been determined, Rails can call an action in one of your controllers. Here’s our code for the show action:

def show
  @flight = Flight.find(params[:id])
  ...
end

We use the params hash to access the parameters. This hash your controller’s window to the outside world, and how it finds out what it’s been asked to do. Rails has already done some heavy lifting for you, by creating the right controller as specified by params[:controller] and called the method specified by params[:action].

Note that although the log file showed the hash keys as strings ("id") we can access them using the more efficient symbol syntax once we’re inside our controller action (params[:id]).

Where’s My GOO-EEE

That was fun, but we can’t have our employees at SoftiesAir firing up script/console whenever they need to add a flight to the database. Forms allow us to create models in a browser.

Take a look at this generated code for the new action in app/views/flights/new.html.erb:

<h1>New flight</h1>

<%= error_messages_for :flight %>

<% form_for(@flight) do |f| %>
  <p>
    <b>Number</b><br />
    <%= f.text_field :number %>
  </p>

  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

<%= link_to 'Back', flights_path %>

In the browser it looks something like this:

Not every exciting. Just a text field and a submit button. We can use it to create a new flight. Enter a flight number like 12, click Submit, and the point your browser to /flights:

Congratulations, we created a new flight in our database! But what really happened make that work?

Over Troubled Water

Remember, in order for our controllers to “see” what happened in the browser, it has to get its information from the params hash. This hash is the bridge between our HTML form and your code.

When we clicked “Submit” on the New Flight form, we sent a bunch of data scurrying over that bridge as it made its way into the create action of the Flights controller. Look at the log again, and you’ll see something like this:

Processing FlightsController#create (for 127.0.0.1 at 2008-03-21 09:32:08) [POST]
  Session ID: BAh7BzoMY3NyZl9pZCIlZDk1ODM5ZmY0MGVmZGI0NDgyNDU4YTM5MDIwY2Ri%0ANDkiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--6c4d1d7f05c3076adfc4815439a12abd895715a1
  Parameters: {"commit"=>"Create", "authenticity_token"=>"dda0b1eb89899c0d8bdeee1e4e0adddda5ad7cb4", "action"=>"create", "controller"=>"flights", "flight"=>{"number"=>"12"}}
  Flight Create (0.000505)   INSERT INTO flights ("updated_at", "number", "created_at") VALUES('2008-03-21 09:32:08', '12', '2008-03-21 09:32:08')
Redirected to http://localhost:3000/flights/2
Completed in 0.00950 (105 reqs/sec) | DB: 0.00050 (5%) | 302 Found [http://localhost/flights]

Once again, let’s zoom in on the parameter data that came over the bridge:

Parameters: {"commit"=>"Create", "authenticity_token"=>"dda0b1eb89899c0d8bdeee1e4e0adddda5ad7cb4", "action"=>"create", "controller"=>"flights", "flight"=>{"number"=>"12"}}

We still have the action and controller values as before. But now we also have a flight hash key, and it is itself a Ruby hash:

"flight" => { "number" => 12 }

If we look at the generated code for the create action, we see this:

def create
  @flight = Flight.new(params[:flight])
  ...

which is the same as saying:

@flight = Flight.new("number" => 12)

which is recognizable now as the normal way we create a Flight object initialized with the value 12 for the number attribute.

Connecting The Last Dot

We’ve seen how HTTP request data goes through our routes, and becomes a hash of parameters that in turn get handed to our controller. In order for the Submit button on our form to result in a new Flight object in our database, we have to make sure that the HTTP request data is sent in the manner that Rails expects and can understand. That takes us right back to where we started – the new.html.erb code – which we will explore in depth next time.

Comments, questions, complaints? Drop us a comment.

Freezing Your Rails Application 11

Posted by jeff Thursday, January 03, 2008 18:07:00 GMT

Ice cubes

Photo credit

Thanks to all who asked for an article about “freezing” your Rails application. I’ll try to cover all the basics, but feel free to follow up with more questions by leaving a comment below.

You on Gems

Talking about “freezing” your Rails app won’t make sense if you don’t first understand what Ruby Gems are all about. Even if you’re new to Rails, you already know at least a little bit about gems, because you can’t install Rails without learning this command:

gem install rails

(or sudo gem install rails if you’re on Mac/Linux).

Gems are the official way to share Ruby code with your friends, coworkers, and enemies. The RubyGems system is accessed with the gem utility, like we just did when we installed the Rails gems. In fact, for us Windows users, RubyGems is kind of like your new version of the Add/Remove Programs applet that comes with Windows. Using the gem command, you can install, upgrade, and uninstall Ruby code that you don’t write yourself.

Imagine a world without RubyGems. You write some cool Ruby class and want to send it to me. You could just email it, I guess. I’d have to copy it onto my machine, and then require it from within my code, using the full path to wherever I copied it to. Not a big deal, I suppose. If many people sent my their Ruby code, I’d probably create a folder for all of “everyone else’s stuff” and put it all in there.

Could Be Raining

But it could get worse. Instead of one .rb file, you want to send me your whole Ruby code library, spread across 9 different files. Or you’d probably zip it up into one file. Now this is starting to become a hassle. Because not only do I need to remember where I’m keeping all this code, I’m sure that the very next day you’re going to say you fixed something and you’re going to want me to upgrade to the newest stuff. Oh and it depends on this other zip file from this other guy, so I have to make sure I have that on my computer, too.

A Gem is like a zip file of Ruby code, except it also gets stamped with metadata about the code inside. Like who wrote it, a version number, and if it depends on any other gems. Instead of .zip, it gets an extension of .gem, and it’s the standard package format for third-party Ruby code. It makes it easy for us to all share code.

The gem utility can automatically find gem files in well-known locations on the internet, download them for us, upgrade them, and remove them. If you’ve never done it, do a gem list right now to see what gems you have installed.

Your Application on Gems

Like it or not, every Rails application you write depends upon the presence of certain gems having been installed on the same computer that’s running the application. Those gems are the Rails gems of course. Rails is a gem that depends upon other gems like activerecord, actionpack, activeresource, activesupport, actionmailer, and rake. (The gems that you must have to run every Rails app are: rails, activerecord, actionpack, and activesupport. Actionmailer and activeresource can be omitted if you’re not using those features.)

This means that when you publish your application on the internet – that is, you’ve deployed it to some server – that server also better have those gems installed, or your application will fail pretty quickly. This is why, if you’re looking at shared hosting providers, you have to find out if they support Rails, which really means do they have the Ruby interpreter and the necessary gems installed.

So if you’re with me so far, you know that you have some gems installed on your development box that make your Rails app work, and there are gems on the server that make your app work there, too. Good, right?

No. Not good.

Very, very bad.

Versions, Versions, And More Versions

Consider:

  • You’ve upgraded to Rails 2.0 and are using all the new stuff in it. But your server still has 1.2.3 on it.
  • You’re writing apps for Rails 1.2.5 and your hosting provider just upgraded all of their gems to 2.0.
  • You have an app written for 1.2.4, deployed and doing just fine. Now you want to deploy a 2.0 app on the same server.

It turns out that you can have multiple versions of a gem installed on the same machine. There’s a line in your environment.rb file that connects your application to a specific version of the Rails gems, and it looks something like this:

# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '1.2.3'

In this example, the application will look for the 1.2.3 version of Rails. If your server has it, all is good. If not, ouch.

So theoretically, all you have to do is always keep all versions of Rails that you need for all of your applications.

On every server.

All the time.

Forever.

Not good. And probably not even possible if you’re under a shared hosting plan (or if you’re not the boss of your IT department).

We’re Getting Warmer – I Mean, Colder

By now, I hope you now understand the problem.

Here’s the solution. Instead of having your application search the system-wide gems for the appropriate gems, you can “override” that search by placing copies of the desired gems right into a subfolder of your Rails app. If you re-read that line from your environment.rb file, you’ll notice the comment above it, which implies that Rails will first search a subfolder in your Rails app named “vendor/rails” for the Rails gems, before it goes looking elsewhere for them.

All you need to do, then, is copy the gems into your vendor/rails subfolder, right?

Well, no, unfortunately. Technically speaking you have to “unpack” them, not just copy them. Unpacking a gem is kind of like unzipping a zip file. The gem utility provides an unpack command to do just that. So you have to do unpack each Rails gem that your app needs into the a subdirectory heirarchy under vendor/rails.

You can do all that by hand, if you want to. Fortunately, Rails comes with a prewritten rake task to it all for you.

Freeze That App!

Freezing your application will make a copy of your system-wide gems and unpack them into your Rails app. As always, run rake tasks from the “root” directory of your Rails application:

c:\dev\myapp> rake rails:freeze:gems

You’ll now find c:\dev\myapp\vendor\rails has been created and populated with the latest version of your Rails gems. You can now deploy your Rails app anywhere regardless the version of Rails has been installed on the server. Your application will always use the gems you’ve frozen to it.

Thaw That App!

You can reverse the process:

c:\dev\myapp> rake rails:unfreeze

This toasts the vendor/rails directory. Now when your application starts up, it won’t find anything there, and will resort to looking for Rails in the system-wide gem repository instead. Or, you can now re-freeze to your latest and greatest version. (If all you want to do is re-freeze a frozen app, no need to thaw first, just run rake rails:freeze:gems again.)

Freezing To Edge

You maybe have heard the term “freeze to edge rails.” Turns out, in addition to freezing your application to a released version of Rails, you can download the latest bleeding-edge Rails bits and unpack them into vendor/rails. That means that you’ll be able to get the latest bug fixes and newest features (and newest bugs, too). Since you’re doing this to your application only, it’s not touching your stable Rails gems that you’ve installed system wide. This is a great way to try out the latest features in Rails.

Don’t know how to download the latest Rails code by hand? Yet again, another rake task comes to the rescue (actaully, two):

c:\dev\myapp> rake rails:freeze:edge
c:\dev\myapp> rake rails:update

That second rake task makes sure that your javascript files and other configuration files that were originally generated when you issued the rails command get updated with the latest versions from edge. (Our patch has made this second step obsolete, so in Rails 2.0.3 you won’t have to do the rails:update step anymore).

I always have an “edge” app on my box, that I just keep re-freezing to edge so I can play with the latest stuff going into Rails.

Warnings

Rails 1.x doesn’t necessarily freeze well with RubyGems version 1.0 and higher. If get a strange-looking “GemRunner”-related error when you try to freeze, that’s what’s happening. There’s a workaround – google for the solution, or let me know and I’ll try to post an article about that, too.

Also, the same version hassles can occur with any other gems you might be using in addition to your Rails app. Read this for how to solve that, too.

So have fun with edge, and always freeze your apps before deployment.

Cool?

Questions? Comments? Let us know.

Rails 2.0.2 16

Posted by jeff Monday, December 17, 2007 17:42:00 GMT

If you haven’t seen the official announcement please do so. At first I thought this was sort of an optional kind of update, and there would be no reason for me to repeat this old news.

But at the very end of DHH’s list of bug fixes, this one caught my eye: Jeremy Kemper fixed an issue with rake rails:freeze:gems. If you’re using RubyGems 0.9.5 (and you should be), you won’t be able to freeze your application to Rails 2.0 unless you get this fix.

So I believe that for anyone reading this blog, Rails 2.0.2 is a must-have upgrade. Just watch out for the new default to SQLite3 instead of MySQL for new applications.

It occurs to me now to ask, does everyone understand what “freezing” your Rails application does, and why you should always do it? If not, let us know and we’ll endeavor to write up a quick article on the topic.

Simulating XML-formatted requests with a check box 11

Posted by jeff Thursday, November 29, 2007 20:21:00 GMT

http://localhost:3000/

I was working on the search form shown above in the usual RESTful manner. This form posts to a create action of my SearchesController, and the code basically looks like this:

def create

    @search_results = find_matching_rows # code to search the db

    respond_to do |format|

        format.html  # default behavior, show results in html page
        format.xml { render :xml => @search_results.to_xml }

    end

end    
My routes file looks like this:
map.resources :searches
map.resources :customers

If you've been doing any REST lately, the above code should look familiar.

The part I want to talk about today, though, is the checkbox you see at the bottom of the form.

If the user fills in some search criteria and leaves the box unchecked, they should get a normal search results view. But if they check the box, I want to return the results as XML. Why would I do this? Well, in my case, it's just so I have an easier way to check my respond_to logic (yes, yes, I already have functional tests for it, but ignore that for the moment) since it's nice to be able to see the XML come back in the browser (in Firefox, anyway... Safari doesn't seem to do this for me, I have to view source afterwards.)

Rails (1.2 and higher) support the notion of a "format" parameter, which can be automatically recognized if you're using map.resources or something like map.connect ':controller/:action/:id.:format'. You can go directly to an address like localhost:3000/customers/1.xml and Rails will set params[:format] to xml. This, in turn, is what activates the format.xml code in the respond_to block above.

However, in my case, I have a a form that I'm using to search the database. So I added this code to my form to display the checkbox:

<p><%= check_box_tag 'format', 'xml' %>XML</p>

check_box_tag is useful when you have a checkbox that can't be mapped to any attribute of your ActiveRecord model. The first argument will become the key in the params hash in your controller; and the second will become the value (there are more parameters and options you can specify, I always have to look them up every time).

The trick here is, by specifically using "format" and "xml" as the values to for the checkbox tag, we will simulate the exact values the params hash needs to make the respond_to block to work. The user checks the box, clicks Submit, and presto - XML in their browser (well, not in Safari unless you view source, but Firefox seems to know what to do).

Cool, no?

ASP.NET to get MVC? 3

Posted by jeff Wednesday, October 10, 2007 00:15:00 GMT

Martin Fowler posted an interesting summary of the alt.net conference, in which he mentions that the ASP.NET framework is getting an MVC makeover.

At our Rails classes, we introduce the MVC concept right away, because it's the central nervous system of the Rails framework. Fortunately for me, when I started learning Rails, it wasn't the first time I had encountered an MVC frameworkd. I think I first learned about MVC back when Microsoft used a slighly absurd form of it they called the "Document-View Architecture" back when I was writing Windows applicationsin C++ with MFC. I was perplexed when I switched from C++ to C#, and found that Microsoft had neither provided (nor even recommended) any particular architectural pattern for either Winforms or Webforms apps.

It looks like they've decided that was a bad idea, and are now finding ways to glue MVC back onto ASP.NET (which I applaud).

Script/console tip for the lazy 12

Posted by jeff Thursday, July 26, 2007 15:19:00 GMT

I use script/console a lot. It's a great way to learn about the Rails API, inspect objects, and get really good with ActiveRecord subtleties.

Yesterday I was doing this in script/console:


>> Product.find :all, :order => 'title asc'
=> [#<Product:0x342ee58 @attributes={"title"=>"Another", "id"=>"2", "released"=>nil, "version_number"=>"1.0"}>, 
#<Product:0x342ee1c @attributes={"title"=>"My Product", "id"=>"1", "released"=>nil, "version_number"=>"1.5"}>]

Good, there's a couple of products in my development database to play with. I wanted to get all of the titles in an array, sorted in ascending order. So I need to take the results of my previous statement and call collect on it - so I was about to do this:


>> Product.find(:all, :order => 'title asc').collect { |product| product.title }

With irb history installed, I can just hit my up-arrow to recall the previous statement. But I'd still have to edit it, to insert the parentheses, before I could append the collect iteration code. So what does a lazy person do when confronted with a task that might take a second or two? Why, spend a lot more time googling for a better solution, of course.

Sure enough, after a few minutes with my good friend Google, I found it:


>> _.collect { |product| product.title }

Yes, that's an underscore character in front of the .collect. In irb (and hence, script/console), an underscore is a kind of global variable that holds the last result. So in my case, it represents the collection that I had just found. You can also capture it in a variable if you want:


>> products = _
>> products.size
=> 2

Just a tip for anyone out there that's just as lazy as me.


Ready to learn more? Sign up now for Essential Rails