Oct 22, 2005
My previous articles on Ruby have captured the attention of the Ruby and Rails community, so now I am immersed in a wealth of knowledge, much of which was contributed here on my website. My thanks to those who wrote in offering advice.
It's 12:25 on another Saturday and I've decided to have another crack at it. I begin with this comment suggesting that I simply give up on the 'scaffold' command and go back to 'model' and 'controller'. Honestly, I hadn't noticed that I hadn't tried to use 'controller' the whole latter half of last Saturday. Putting this d'oh moment aside, I give it a go, removing (rm -r -f ToDo) the ToDo directory and starting from scratch.
[root@downes rails]# rm -r -f ToDo [root@downes rails]# rails ToDo ... [output clipped] [root@downes rails]# cd ToDo [root@downes ToDo]# vi config/database.yml [root@downes ToDo]# ruby script/generate model category ... [root@downes ToDo]# ruby script/generate controller category ... [root@downes ToDo]# cd app/controllers/ [root@downes controllers]# ls application.rb category_controller.rb [root@downes controllers]# vi category_controller.rb
Still no joy. http://www.downes.ca:3000/ToDo fails as usual. I recall another comment which suggested I try http://localhost:3001/category/new instead - so I attempt http://www.downes.ca:3000/category and http://www.downes.ca:3000/category/new with no happiness. Perhaps if I restarted the server.
Recall from last week that I could not shut down the server. Another commentator helpfully writes that "to 'really really' kill a process, use 'kill -9 [pid]' as root. kill without a strength indicator is liking asking someone politely to go unconscious, while kill with a '-9' is like hitting them with a steel pipe." This is one of these things about Linux that bugs me. I've used 'kill' a lot and it has always worked. And it has never been the case where I have typed 'kill' that I did not actually want the process to stop. So why would there be kill levels?
Anyhow, 'kill -9' works and the server - which has been running since last week - is now shut down. So I restart the server ('script/server -d') and try my links again. Aha! Something different:
# /app/controllers/category_controller.rb:3: syntax error scaffold: category ^ routing.rb:219:in `traverse_to_controller' generated/routing/recognition.rb:3:in `eval' generated/routing/recognition.rb:3:in `recognize_path' script/server:49 Show framework trace This error occured while loading the following files: ./script/../config/../app/controllers/category_controller.rb
OK, I had typed 'scaffold: category' instead of 'scaffold :category' in category_controller.rb. I know from reading Ruby documentation that this is not merely a trivial error - " The construct :artist is an expression that returns a Symbol object corresponding to artist. You can think of :artist as meaning the name of the variable artist, while plain artist is the value of the variable." (From here). So I fix the error and try again. 1:00 pm.
One thing I have learned, though: you have to restart the server. before I restarted the server this error didn't register at all. After I restarted the server, it did. Therefore the server was not even registering the completely new installation of ToDo.
So I try http://www.downes.ca:3000/category again and...
Listing categories Category Created on Updated on Sat Oct 15 16:33:24 ADT 2005 Show Edit Destroy Sat Oct 15 16:33:24 ADT 2005 Show Edit Destroy New category
Success! It is now working! Interestingly, it took the combined advice of three commentators to get me to this point. 1:05 pm. Amazing.
The big thing to note here is that the link is directly to the table name, and not the directory name. I had been typing http://www.downes.ca:3000/ToDo simply because the Four Days guide pointed to 'http://todo/category' without actually recognizing that in the AddressBook example I was pointing directly to the 'contact' table and not to the 'AddressBook' database. In retrospect it seems a pretty silly thing to have overlooked, but because I couldn't restart the server (and no indication that it was in fact necessary) I had no inkling of what was actually happening even when I made changes - I had no way to narrow in on it.
Coding is a very humbling business.
On With Four Days
So I've decided to continue with the Four Days guide even though it has already led me astray. Why? Well, because I want to get to that end point (helpfully underlined by this comment asserting that "you can ship a rails app to a system that has ruby installed, but not rails...").
So now I'm going to 'enhance the model'. First step: input validation. The Four Days guide illustrates how to add validation into the data model:
app\models\category.rb class Category < ActiveRecord::Base validates_length_of :category, :within => 1..20 validates_uniqueness_of :category, :message => "already exists" end
This is pretty straightforward. This code ensures that input is between 1 and 20 characters long (no blank categories, therefore), and ensures that the category being entered does not already exist. So I'll edit the file category.rb and input these constraints. That done, I go back to my (now working) ToDo application and enter the category 'test'. It loads fine, then I try entering 'test' again and get:
New category 1 error prohibited this category from being saved There were problems with the following fields: * Category already exists
It caught the error exactly as designed. It also provides an input form with the attempted 'test' category, so I can simply change the name and submit it again. I change the name to 'test1' and submit. Works like a charm. This is the sort of elegance I was hoping for. And with that, 'Day 1' in the Four Days guide comes to an end. Heh.
OK, here's what's going on at the beginning of Day 2. The actions listed inside category_controller.rb cause Ruby to generate some code on the fly. Of course, if you do this, you have to live with the default code. To actually view the code, and to (possibly) change the defaults, you need to run the actions as a script, which will generate some output we can look at. The Four Days guide, naturally, continues with the 'scaffold' action it described in Day 1. This was the action I changed back to 'model' and 'controller'. The examples from 'scaffold' won't be useful to me in my current configuration. But I wonder whether, with a proper server restart, 'scaffold' might not work after all. So I shut down the server and then edit /app/controllers/category_controllers.rb as follows
class CategoryController < ApplicationController scaffold :category end
Then I restart the server and try http://www.downes.ca:3000/category again. What do you know. It works! The problem therefore was not with the scaffold action, it was with my not being able to restart the server. This shoudl be put into the guide - restart the server!
OK, now I can go back to Day 2. As instructed, I will now run the scaffold as a script:
[root@downes ToDo]# ruby script/generate scaffold category dependency model exists app/models/ exists test/unit/ exists test/fixtures/ skip app/models/category.rb skip test/unit/category_test.rb skip test/fixtures/categories.yml exists app/controllers/ exists app/helpers/ create app/views/categories exists test/functional/ create app/controllers/categories_controller.rb create test/functional/categories_controller_test.rb create app/helpers/categories_helper.rb create app/views/layouts/categories.rhtml create public/stylesheets/scaffold.css create app/views/categories/list.rhtml create app/views/categories/show.rhtml create app/views/categories/new.rhtml create app/views/categories/edit.rhtml create app/views/categories/_form.rhtml
Works like a charm. Now we have actual scripts (indicated by the filenames listed). Now for some Ruby weirdness: "Note the slightly bizarre naming convention we've moved from the singular to the plural, so to use the new code you need to point your browser at http://todo/categories. In fact, to avoid confusion, it s best to delete app\controllers\category_controller.rb etc in case you run it accidentally." OK. So I remove the file:
[root@downes ToDo]# rm app/controllers/category_controller.rb rm: remove regular file `app/controllers/category_controller.rb'? y
Then I try my new URL. http://www.downes.ca:3000/categories. It works. I wonder what would have happened had I tried to run the URL first, or even the old URL, before deleting category_controller.rb. I don't want to think about it. 1:49 pm.
Now I use vi and look inside the new file, categories_controller.rb (and actually, some of that weirdness makes more sense - singular for actions, plural for genrated scripts?). Anyhow, this file is kind of like 'main menu'. It takes each of the input commands (eg., 'show' from ../categories/show) and associates it with an action (in this case, '@category = Category.find(params[:id])' ). At this point it may render a template or redirect to another action. For example, after 'create' it redirects you to 'list'. I always prefer to go straight to the editing screen after I create, because I make so many typos. So I change the word 'list' to 'edit' and test it - now when I create a new category, it directs me into the editing screen for that category. Well, not so simple:
Couldn't find Category without an ID
I guess it doesn't remember what I just created. Hm. I try changing it to "redirect_to :action => 'edit(params[:id])'". Nope. I guess I need to pick up the ID from the 'create' action, but I don't know how to do that yet. OK, oh well, I'll leave this for now.
Ah, what the heck... I look more closely at the 'edit' action, which ultimately redirects to 'show'. I see now. I try changing the link to "redirect_to :action => 'edit', :id => @category" and test that. It works fine - it creates the new catgeory and then takes me directly to the edit screen for that category. OK. Each parameter is specified separately, in a list separated by commas.
Next, Four Days looks at views - a view is a definition of the user interface. I've done a lot of work with views writing my own Perl scripts, so I'm pretty comfortable with this arrangement. There are three major components to a view:
- A Layout provides common code used by all actions, typically the start and end of the HTML sent to the browser. " - A Template provides code specific to an action, e.g. List code, Edit code, etc. " - A Partial provides common code - subroutines - which can be used in used in multiple actions e.g. code used to lay out tables for a form.
In my own system, I call them a 'template', a 'view' and a 'keyword' respectively. Not that this observation is useful to anyone but me.
I open app/views/categories.rhtml and have a look:
(So anyhow, I decided this would be a good time to save what I've written so far - I'm writing in kwrite, which is pretty stable but has crashed on me in the past. I save what I've written, then start editing again and... Wham! down goes kwrite. Weird. Open it again, all my text has in fact been saved, start typing again, and kwrite crashes again. Even though it's Linux, I decide this would be a good time to reboot. Reboot. Open kwrite. Another crash! I determine that I can crash kwrite consistently by using the up arrow several times in sequence. Sheesh. No idea what happened, but this is not a happy development... well, now here I am in OpenOffice 1.1 - I don't like it so much because it takes a long time to load. kwrite is down for for the count; even hitting the up arrow once kills it (now what kill level is that?). 2:55 pm.
Anyhow, looking at categories.rhtml it appears to be dead simple - just some ordinary HTML with Ruby actions enclosed between <% ... %> tags. It makes me think I'm coding in PHP again. Or for that matter, in ColdFusion. Well, whatever works, I guess.
The template is just as simple. One thing to note:
<%= start_form_tag :action => 'create' %>
Variables associated with the action follow the action (presumably separated by commas, as before). Also, 'start_form_tag is a Rails helper to start an HTML form - here it generates
I try the 'inspect' command and - oh! - I see two blank lines. Hm. The first two lines of the database are empty? Yes. I delete them in phpMyAdmin. I must have created them when I was messing around with 'new'. OK then. I insert the 'strftime' again and try reloading the page. This time it works fine. OK, I see. strftime generates an error on null data. That's not very graceful. And if null data is going to crash the whole program (sheesh) the error message should at least be a bit more informative. 4:20 pm.
A couple more minor edits and I am at the end of Day 2 in the Four Days guide. This seems like a good time to stop. Who knows what mysteries lie ahead?