Content-type: text/html Downes.ca ~ Stephen's Web ~ Ruby on Rails (3)

Stephen Downes

Knowledge, Learning, Community

Oct 22, 2005

Continued from Part 2

Redoing ToDo

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:

   SyntaxError in #

   /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

'. Sort of like using CGI in perl. Rails has a bunch of these helpers; presumably there's a list somewhere. The partials are the same theme yet again so I can breeze by them.

Following the guide, I begin tailoring the scaffolded code, beginning with :order_by => 'category' in categories_controller.rb and the removal of 'show' (since everything displays fine in the list). The guide doesn't say this yet, but I also need to remve the 'show' command from the list output - I guess that would be in a template.

Next I look at the 'flash' messages. Bad name - there's a whole product called 'Flash'. I call then 'status messages'. Anyhow, you can display a flash message in your page with '<%=h @flash["notice"] %>' and in your script (the guide doesn't say this yet but I saw it in the scripts above) you can define the content of a flash message with "flash['notice'] = 'Category was successfully updated.'."

I make the rest of the changes outlined in the guide, then try to view the list. One error: "undefined method `strftime' for nil:NilClass". At first i think that the method 'strftime' no longer exists (once burned...) but then I think that 'nil:NilClass' looks too odd to ignore. So... what? I check the code but it looks OK. So I comment out the strftime references (actually, I just delete the '%' so it isn't interpreted as a command) and try it again. It works.

OK, so strftime is the problem. Check the database, just in case, and yes, the fields are there, with values. OK, must be the method. But a search on 'strftime' returns loads of documents - the method probably exists. Search for the error? Well here's a case where somebody has hit it and let the script in this state long enough to be Googled (and it's still in that state as I look today). Heh. Here's another explanation: "your db wasn't populated or the schema was modified somehow. eg. you WERE getting back a nil object for your created_on field because that field was empty for some reason." But no - because then I wouldn't be getting category names, and I am getting those. This discussion transcript gives me a clue about how to explore more:

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?



Stephen Downes Stephen Downes, Casselman, Canada
stephen@downes.ca

Copyright 2024
Last Updated: Nov 21, 2024 11:35 a.m.

Canadian Flag Creative Commons License.

Force:yes