Rails: The First Story
What's Going On
It’s becoming clear that this series of articles will have a lot less code in it than previous articles, and may, unfortunately, have a lot more HTML, which is no fun to read. Nature of the beast, we think, when you’re working with a system generator like Rails. Perhaps as our pages get more complex there will be something to look at, but frankly this little app is so simple that that may not happen at all.
What we hope will be interesting will be our overall experience with RoR, and we certainly hope to draw some interesting and valuable conclusions about using it. In any case, we’ll strive to be as interesting as possible.
Not Exactly Interesting
As you’ll read, we have been having trouble with my computer, in that it seems to run Ruby 15 times slower than Chet’s, despite the fact that the machine is basically faster, with more memory. This is slowing us down, and reducing the value of the software, since if it needs to be maintained, I’ll be unable to maintain it on this computer. On the other hand, it could mean I’ll have an excuse to get another computer.
Ideas on what is making Ruby so slow should be sent along. It might be ideal to post them to my thread on the Ruby newsgroup. But send them until we report that it’s fixed, by all means.
What's Not Going On ...
People may be wondering about the shotgun app. The issue there is that the customer (Chet) has kind of run out of steam on stories. My guess is that we’ll get back to it, but that we’re going to enjoy working with Ruby enough that we’ll want to rewrite it.
Speaking of Rewrite ...
Kelly Anderson writes to ask why we don’t just use a wiki for the software page, and wonders whether we just do these things so we’ll have something to write about. Well:
- The app isn't much like a wiki. Each contributor can only edit a small part of the page, unlike a wiki, where every contributor can edit any number of pages. It needs security, and we think it needs to guide the contributors to mention just the things that they "should" mention.
- Yes, of course we do these things to write about them. The idea is to share, with interested people, our approach to doing software, and our thoughts on that and on the business of life. So we will certainly write things that any rational person would just acquire.
We hope that answers Kelly’s question.
OK, The Story is ...
- Empty List with Unchecked Entry Create a new Software page, with an empty list; link to an entry page that can add a new record; do not check password or require approval.
After brief discussion, we decide to begin completely from scratch, including creation of the database. We’ll name it xprogramming. We’ll add a dataset called software or something like that, guess a few fields, create a scratch list page showing the (empty) list, then create a page for adding entries and add one.
Getting Started
Chet reads and reminds us that the project name and the database name are the same in Rails, e.g. software and software_development. So if we call the database xprogramming, that would suggest that all future uses of the DB would be in the same project as this one. We don’t think we care. This is the only project we have in mind, but if we do more things on the site, putting them together with these new pages will make sense. We’ll use Instant Rails to create a new Rails App and call it xprogramming. Well, Instant Rails opens a console window into which you’re supposed to type rails xprogramming. So we do.
After a discreet interval, rails creates a few thousand folders. What’s next? Create the database. Instant Rails throws us to a PHP page, we create the xprogramming_development database after a bit of discussion about names, and then we go to the xprogramming folder and type rake db:migrate. If that doesn’t explode or format the hard drive, it means everything is OK. Tum te tum. Whistle … this is taking a while. We go shopping for books, listen to a few CDs. Finally it’s back. All is good.
Now we generate the model. ruby script/generate model tool. Or at least we hope so. Take a break, smoke ‘em if ya got ‘em. (We were going to call the model software but rails would then give us something called softwares, which seemed off to us.)
There is something weird here. On Chet’s machine, the process above took 20 seconds. On mine, it took several minutes. I have more and faster processors, more and faster RAM … something must be misconfigured. But what? If you have an idea, drop us a note.
Now we are supposed to update the migration code. The input file is:
class CreateTools < ActiveRecord::Migration def self.up create_table :tools do |t| end end def self.down drop_table :tools end end
We need to add the columns. A quick look at the current software page tells us that a good starting point would be fields Category, Tool, and Author. Here goes:
class CreateTools < ActiveRecord::Migration
def self.up
create_table :tools do |t|
t.column :category, :string
t.column :tool, :string
t.column :author, :string
end
end
def self.down
drop_table :tools
end
end
Then do rake db:migrate. This, too, runs forever. Something’s up. The delays for these tools are sufficient to derail our thought processes. I’m tempted to move development back to Chet’s machine until we understand what is going on. When it terminates, it says that it took 0.094 seconds. It seemed to take more like four minutes. Is Ruby compiling the world first or something?
Now we need to create a controller. ruby script/generate controller admin. We debated the name a bit and decided to follow the book for now.
Time Passes ...
In the course of doing the setup on my machine, we discovered that it was very slow. We diverted for a day into figuring out what was going on. We have narrowed it down to roughly these points:
- Rails is incredibly slow: Once a page is defined, it takes 30 minutes to get the Mongrel server fully running and the page to come up on the screen.
- Ruby's "ruby" test suite runs 15 times slower on my machine than on Chet's. My machine is overall slightly faster than his.
- Executing ruby -rsocket -e 'p Socket.gethostbyname("127.0.0.1")' ["localhost", ["test"], 2, "\177\000\000\001"] on Chet's machine takes under one second. On mine it takes six or eight.
- C# programs run at essentially the same speed.
We’ve spent more than a session on this now and it is both frustrating and gets in our way. Doing the work on Chet’s machine means that we can’t both see both the code and the growing article. Longer term, if I can’ update this code on my own machine, it will make future changes really difficult.
We would welcome ideas on how to isolate this problem and fix it.
The Software Page
Nonetheless, after we create the admin controller, we have the standard listing page much as shown in the book. We also create our page named software, which looks like this:
This is close enough to carry us forward. We need to add links for the tool and for the author’s mailing address. We get Rails to generate a new migration named AddLinks, and we edit it as shown:
class AddLinks < ActiveRecord::Migration def self.up add_column :tools, :tool_link, :string add_column :tools, :author_link, :string end def self.down remove_column :tools, :tool_link remove_column :tools, :author_link end end
We run the migration, with rake db:migrate. It claims to have worked. We use our admin page to add some fake links, with the result:
We have run the scaffold builder in Rails, so our software.rhtml file is fixed. It looks like this:
<h1>Listing tools</h1> <P>This is list.rhtml</P> <table> <tr> <% for column in Tool.content_columns %> <th><%= column.human_name %></th> <% end %> </tr> <% for tool in @tools %> <tr> <% for column in Tool.content_columns %> <td><%=h tool.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => tool %></td> <td><%= link_to 'Edit', :action => 'edit', :id => tool %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => tool }, :confirm => 'Are you sure?', :method => :post %></td> </tr> <% end %> </table> <%= link_to 'Previous page', { :page => @tool_pages.current.previous } if @tool_pages.current.previous %> <%= link_to 'Next page', { :page => @tool_pages.current.next } if @tool_pages.current.next %> <br /> <%= link_to 'New tool', :action => 'new' %>
As generated by Rails, this page actually lists all the columns, and includes edit buttons for each item. We don’t want that. We want just the tool and author column, and we want to use tool_link and author_link as links wrapped around tool and author.
We modify the list.rhtml as follows:
<h1>Listing tools</h1> <P>This is list.rhtml</P> <table width="50%" border="2"> <tr> <th width="25%">Tool</th> <th width="25%">Author</th> </tr> <% for tool in @tools %> <tr> <td width="25%"><a href="https://<%=tool.tool_link%>" target="_blank"><%=tool.tool%></td> <td width="25%"><a href="<%=tool.author_link%>"><%=tool.author%></a></td> </tr> <% end %> </table> <%= link_to 'Previous page', { :page => @tool_pages.current.previous } if @tool_pages.current.previous %> <%= link_to 'Next page', { :page => @tool_pages.current.next } if @tool_pages.current.next %> <br /> <%= link_to 'New tool', :action => 'new' %>
We also found and changed the place where we had set the item count for pagination to 2, and set it to 1000, because my page is supposed to display all the items at once. We might change that in future. With the changes above, the resulting page looks like this:
That brings us to the end of our programming day. We have the first part of our story done, a rudimentary page showing the software tools, with links to the tool and to the author’s email. We have not done the second half of the story, which is to produce a page where an author can add a tool to the system, without security.
We are definitely behind schedule, since we spent Wednesday trying to figure why my machine runs Ruby and Rails so slowly. Today, the story appears to be more than one session’s worth, but it is probably too soon to panic.
On the other hand, I just got a gig in San Francisco the week of Decemtnber 3, so assuming that sticks, shipping by December 7 is almost certainly out. Best guess right now, not discussed with Chet: December 14.
Discovered Stories
In the course of the work, we’ve discovered a few stories that may need to be identified. As yet, they don’t feel like they are going to be a problem, especially if we are shooting for December 14 rather than the 7th, and assuming neither of us gets additional work in the period.
- We need to deal with the author providing "https://" or "mailto:" or not, and with the possibility that some authors, rather than an email address, want just to link to some web page.
- We need to break the table into categories, such as unit testing, acceptance testing, and so on.
- We did not finish the first story in one session. Should we split it? We might do so with a larger story, to show the customer progress. But a session is only two hours, so it's hardly necessary here.
Rails: How Do We Like It So Far?
So far, Rails is easy enough. The enforced MVC, the dynamic generation vs fixed generation of classes and methods, plus the odd command-line generation of things, makes it seem a bit awkward to us so far.
We are also suspicious of the database connection. For example, there will be catagories of tool, as I’ve mentioned before. We know exactly what they are and plan to put them in a pull down of some kind. But we have a database. So it would be possible to make a separate data set containing the valid catagories, from which the user can select one, and then if we ever wanted to change the categories we could just add a new one to the set et cetera et cetera.
Then of course we would want to control the order in which they are displayed, but all we would have to do is to add a sort key to the category set, then script the page to sort the categories, loop over them in sort order, join to the database for each one, select the items, display them, rinse, repeat.
YAGNI. We know what the categories are. The system will be simpler without the category set. Maybe someday we’ll need it … but it is oh so tempting to exercise our relational muscles right now, adding complexity and time to the project, because it’s so simple. Thing is, it’s not as simple as not doing it. YAGNI. We’ll do it when we need it, but we were tempted. Lesser men could not have resisted.
Stay tuned, we’ll be back!