Tuesday, January 31, 2012

Exercise 51: Getting Input From A Browser: Learn Ruby the Hard Way: Practicum

All right, so we have a simple Sinatra framework up and running. Now let's see if we can do something just a little more exciting, shall we :)?

This time around, we'll make some interactive elements. We will submit text to our application by using that well known method, the form. Additionally, we'll look at ways that we can do automated testing for forms.

How The Web Works (The Boring Bits)

Zed's got this pretty well nailed down, so I'll let him tell this part :):

- You type in the url http://learnpythonthehardway.org/ into your browser and it sends the request out on line (A) to your computer's network interface.

- Your request goes out over the internet on line (B) and then to the remote computer on line (C) where my server accepts the request.

- Once my computer accepts it, my web application gets it on line (D), and my web application code runs the / (index) handler.

- The response comes out of my web server when I return it, and goes back to your browser over line (D) again.

- The server running this site takes the response off line (D) then sends it back over the internet on line (C).

- The response from the server then comes off the internet on line (B), and your computer's network interface hands it to your browser on line (A).

- Finally, your browser then displays the response.

Rather than go through a full breakdown of what the web terms mean, I'll let the reader take a look at Zed's very well done crash course explanation over at http://ruby.learncodethehardway.org/book/ex51.html (chances are, if you've gotten this far with me, you already know the links and site very well.

How Forms Work

Let's take the lib/gothonweb.rb file make some changes to it:

- Restart Sinatra (hit CTRL-C and then run it again) to make sure it loads again

- With your browser go to http://localhost:4567/hello which should display, "I just wanted to say Hello, Nobody."

- Next, change the URL in your browser to http://localhost:4567/hello?name=Frank and you'll see it say "Hello, Frank."

Finally, change the name=Frank part to be your name. Now it's saying hello to you.

So what have we done here?

- We're now using the "params" hash with a value of ":name" to get data from the browser. Sinatra takes all of the key/value pairs after the ? part of the URL and adds them to the params hash for you to work with.

- The greeting is then constructed from the value of ":name". By default we set this to "Nobody". If we give it a name value on the command line, it changes it based on the "name=EnteredName" that we put in the URL after the "?"

We can add more than one param on the command line, too if we modify the script to accept the changes.

We can change the code to get params[:name] and params[:greet] as well like this:

By default, it looks like this:

And with the url values, it looks like this:


Creating HTML Forms

OK, so we can pass the values through the URL, but let's face it, that's a pain. Most people expect to enter information in the browser directly and hit a button. this uses the time honored web feature called a form (or a POST form, to be more specific). A form is just an HTML file with a "form" tag in it. This form will collect information from the user, then send it to your web application just like you did above.

Let's make a quick form to see how this works (this is being done in  lib/views/hello_form.erb):

Now make some changes to gothonweb to be able to accept the form:

Once you've got those written up, simply restart the web application again and hit it with your browser like before.

The part of the hello_form.erb file that makes this work is the line with
. This tells your browser to:

- Collect data from the user using the form fields inside the form.

- Send them to the server using a POST type of request, which is just another browser request that "hides" the form fields.

- Send that to the /hello URL (as shown in the action="/hello" part).

Creating A Layout Template

For the final exercise, we'll be making a bunch of small HTML pages. Having to always code up a page each time will soon become tedious, so we'll create a simple "layout" template to wrap all of our pages with common headers and footers.

So here's the change to lib/views/index.erb:

Change lib/views/hello_form.erb to be like this:

This removes all of the general "boilerplate stuff" that every page will have at the top and the bottom, and now we'll make a layout page that contains all of that:

Here's lib/views/layout.erb file that handles it for us from now on.

Once you have those changes, create a lib/views/layout.erb file with this in it:

Writing Automated Tests For Forms

So now that we have made these changes, we could keep loading up the web page each time to see if we have made the correct changes... or we could use our unit tests to see if what we have put in works, too. The tester in me would like to see us do both, so lets do that :).

Create the file test/test_gothonweb.rb with these contents:

Finally, run test/test_gothonweb.rb to test your web application:

$ ruby test/test_gothonweb.rb
Loaded suite test/test_gothonweb
Finished in 0.023839 seconds.

1 tests, 9 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 57414

Note: what's being seen here is not what I am seeing. Maybe there is a step missing, but the framework appears to be running as expected. Again, it's a problem I'l tweak a bit more later.

Zed explains that what is happening here is that we're importing the whole application from the lib/gothonweb.rb library, then running it manually.

The rack/test library we have included has a very simple API for processing requests. Its get, put, post, delete, and head methods simulate the respective type of request on the application.

- All mock request methods have the same argument signature:

get '/path', params={}, rack_env={}

- /path is the request path and may optionally include a query string.

- params is a Hash of query/post parameters, a String request body, or nil.

- rack_env is a Hash of Rack environment values. This can be used to set request headers and other request related information, such as session data.

This works without running an actual web server so you can do tests with automated tests and also use your browser to test a running server.

To validate responses from this function, use the assert_response function from test/test_gothonweb.rb which has:

assert_response(resp, contains=nil, matches=nil, headers=nil, status=200)


This is pretty cool, again, this helps make a lot more sense out of what I've been seeing in the code and the rails applications that I use (the boilerplate loading up the header and the footer that's the same, and then the specific code elements being loaded as needed and only when needed, it's a beautiful thing. The Unit tests are not behaving the way that I expect them, to, but again, it's possible I'm missing something or I configured something wrong. Even without this specific aspect, I think the point has been made. Sinatra makes for an elegant little framework for running a server.

No comments: