One thing most web applications need is a static page template. Now, whilst the page content might be static, the template itself might need to be dynamic – either because it’s going to change in future, or because you’ve got dynamic user information that appears in the tempalte.

So the most obvious way around this is the static controller. Dead easy, this: generate a new controller called static (or whatever you want). Then just write views for it named after pages you want. For instance: your about.rhtml file can contain all your “about” information. Then, when you hit up /static/about (to use the default routing), you get your static content, without having to make a whole page from scratch in public_html. You could even write a new 404 page on this controller.

All that remains, once it’s working, is to write some dedicated routes, and then the “static” controller can be hidden from existence – just route /about to :controller => "static", :action => "about" and you’re done. No need to write any controller logic at all!

Going on from there: one view I’ll always add to that template is the “foo” action.

So: when you’re mocking up a page, you’ll probably use lots of dummy links. Everyone expects this, because it’s obviously just a flat mock. But when you mock up an application, and show it to stakeholders in a working state, they click on things, and wonder why they get ActionController exceptions when it breaks. Also, they wonder why the link that breaks stuff is always /foo.

Obviously, it’s because I’ve left link_to "/foo" all over the shop. Have no fear: the easy way around this is to route /foo to :controller => "static", :action => "foo", and then write a static page called foo.

When you do this, the page should explain that it represents functionality that hasn’t been added yet, but will be added soon, and that the developers haven’t forgotten it.

This (from experience) reassures stakeholders that the thing that is not working will be working soon. It also means that when they do find “grey screen” errors, it either means that something’s genuinely broken, or it means that the developers really have forgotten something. Time to update that link to point to foo.

It sounds trivial, but it turned out to be an effective communication of diligence on the developer’s part, and saved much time in meetings explaining that “yes, we’re working on that”. Consider it, next time you’re developing for external stakeholders.

Sometimes, when you’re building a Rails application, you want to generate different relationships to the same model – or, rather, multiple relationships to multiple perspectives on a model. You can tell ActiveRecord to override the expected class name, but you can also use this technique to create “subsets” of classes for relationships, too. It’s worth remembering that ActiveRecord can generate these for you. By doing the query in the database, rather than filtering afterwards, your application will run faster.

As you can see from that explanation, I’m not exactly the greatest developer, so this is probably best explained with an example from life.

I’m currently working on a CMS – yawn yawn, I know. Anyhow, in this CMS, we have articles, and an Article has_many :comments. Obvious one-to-many relationship, fine. However, comments may or may not be spam, marked by the attribute is_spam. So whilst article.comments will return all the comments on a particular article, it’d be nice to have a class method that returns all the clean comments; article.clean_comments, for instance.

My first stab at this was to add the following method to the Article class:

def clean_comments
  output = []
  self.comments.each do |c|
    output << c unless c.is_spam
  end
  output
end

which is, I suppose, passable idiomatic Ruby, and which does the job; we build an output hash of objects unless the comment is spam. The problem is that if you have, say, an article with a thousand comments - of which 990 are spam, we end up having to generate 1000 objects from the database, and then filter them with Ruby. It'd be faster to select only 10 from the database in the first place, right?

We can do that by defining a new relationship. In our Article model, under has_many :comments, we can add this:

has_many :clean_comments,
         :class_name => 'Comment',
         :conditions => 'is_spam is null',
         :order => 'created_at'

Bingo. That extra has_many allows us to refer to article.clean_comments, and it'll do so directly from the database via SQL. This is a fairly simple example, and I do recommend looking up has_many and has_one (which has similar capabilities) in the Rails API documentation. There always seems to be more depth with Rails than you initially expect, so it's worth digging a little deeper - and you'll make your code leaner, quicker, and better as a result.

So I’ve been fiddling with Rio recently. I’m trying to write a little script to find and replace throughout a directory – and all its subdirectories, recursively. Given the end result of this might end up being an application based upon Rails, it seemed best to write this in Ruby. And I remembered someone (Tiest, I think) mentioning Rio at one of the LRUG meetings. So I gave it a shot.

Rio is, in short, lovely. It acts as a convenience wrapper around a whole host of modules – including Kernel, File, Dir, and others – and basically makes reading and writing files a doddle. It’s also quite powerful, and makes batch operations across directories really easy, once you’ve figured it out.

So my global find/replace script comes down to this:

rio('dirname').all.files.skip(/^\./).do |eachfile|
   eachfile < eachfile.contents.gsub("search", "replace") end

And that's it. Obviously, this being gsub, "search" can be replaced with any regular expression of your choosing. I'm sure I could do it faster in sed/awk/grep, but I'm not as familiar with them as many - and this way around, I can patch this snippet into a larger Ruby application. The skip clause forces Rio to ignore files beginning with a dot, as .DS_Store and friends cause it problems (it seems).

Anyhow, I'm very impressed with Rio - saves a lot of faffing with read and write modes - and can highly recommend it if you need to faff with the file system in Ruby.

Rails and Hypercard

22 December 2005

Gavin mentions the idea that Ruby on Rails might just be the new Hypercard – something I may or may not have discussed with him.

I think he’s right. After one of the London Ruby User Group meets, someone commented that all Rails really needs now is a killer easy-front-end for page layout, or an IDE for apps of some form, and it could really hit the jackpot. I immediately thought of Hypercard; it had the requisite simplicity, grace, and convention, and would be nicely suited to Ruby (just as it was to Applescript).

I’ve mentioned Hypercard before on this site. It was pretty formative in me finding a way I could program computers that wasn’t necessarily reams of code, of first making me aware of UI design, and of making programming fun. Rails has had a similar effect, properly kick-starting me into OO programming, and finally making me understanding and appreciate scripting languages.

The speed of gettings things working, that’s what matters. Not finished – finished might be a long way off – but you’ve always got something to show for your labours. That’s why I like it.

Game over

18 November 2005

Interesting post to the london.pm list, following London Web Frameworks Night. It addresses the image problem Perl has, and the marketing it needs to do. In short, Perl itself is fine, and it has some a few advantages (eg CPAN, DBI) to Ruby/Python, but it sure needs to sell itself better. [As spooled earlier today.]

OK, so maybe I was a little hasty with my earlier post. We are emphatically not all developers now. The hyperbole ran away with me, and I apologise for that. But there was a nugget of truth in there.

If the Rails-revolution (and that includes all those things which perform similar functions to Rails, even if they themselves are not rails) has a real-world analogue, it’s that of the far eastern bespoke tailors. You see their ads in the papers; they fly over to London for fittings in hotels, and then make a bespoke suit for you in their workshops back in Hong Kong. You end up paying the price of a decent off-the-peg suit (about £200) for something tailored to you. Of course, it’s not quite Saville Row, but it’s still a darn sight better than a generic off-the-peg model.

And this is what the rapid-development-frameworks give to small businesses.

A small business wanting to serve the global market might consider, say, an online shop as part of its web presence. A small, local web-development firm are contracted to build one. A long while ago, said web shop built a fairly substantial (and customizable) online store. They now use this work as a basis for future stores – pull another copy off the shelf, add a few tweaks, and serve to client. To create something uniquely tailored to the client’s needs would take a lot of time – and the client, being a small business, can’t afford the time a bespoke product takes.

So they receive delivery of a store that works, but has quirks that they could do without, features they don’t need, and might even lack features that could save them time; instead, they implement workflow-based workarounds to those missing features.

What the new frameworks do is put bespoke products (especially small bespoke products) within reach of small businesses. A small web-development firm isn’t going to be overworked, or take too long, to start from scratch and create a product that is 100% suitable for the client. The client will be more productive as a result. It’ll probably be a better product, too, as the product will always be built to current standards (instead of being last year’s model, dolled up).

Of course, in this new bespoke-economy, there will still be successful web-development firms who continue to serve up the old, 90%-suitable, off-the-shelf product. They might get away with this for a while, but not forever, because the bespoke economy serves everyone better – developers get the chance to constantly be creating new things; clients get the product they’ve always wanted.

So we’re not all developers, we’re just all in a position to get bespoke development at off-the-peg prices (or less). I mentioned ‘software scaling down to a userbase of one’. It’s not that this is possible – it always has been – that makes these new development frameworks exciting, but that it’s suddenly become very, very viable.

A few days I linked to Dema’s tagging mixin for Rails. In, well, about half an hour over the past two days I implemented it into a project I was working on – first into the models, then into visualisation. The interface will come last (though of course, that doesn’t mean it’s not getting a lot of thought right now).

One problem I ran into was that whilst I could tag away with new tags, adding an already-extant tag to a data object didn’t work – it threw an exception error. In the end, I found this was down to my join table – the tags_things table that assigns tags to thing objects. The thing was, as with most of my tables, I stuck an auto-incrementing id column into it. This was really a stupid idea and not in any way necessary (though in all the other tables, it is fairly appropriate). The moment I just left it with two columns, tag_id and thing_id, it all worked fine.

It’s a nice mixin, by the way – makes searching by tag dead easy and it’s fairly lean. Saved me reinventing the wheel, that’s for sure.

Fun with Ruby

19 August 2005

Gosh, I’ve been busy. Still, in between evenings out and work, I’ve been starting work on a fairly large personal project, which (unless I encounter a colossal brick wall) be written in Ruby, with the Rails framework playing a large part. So far, despite some hiccups, it’s been very pleasant; a fair amount of headscratching (in part down to my unfamiliarity with the langauge and its at-times-bonkers syntax, and in part down to working out just what I wanted to do), but every breakthrough has been delightful. A bit of trial-and-error, and then, suddenly, boom: I’ve got a whole new piece of functionality working with minimal code. I’m enjoying the langauge a lot – it’s reminding me of what I’d learned of Python a fair bit, but with some interesting twists. Particularly a fan of the idea that it’s a language that’s very understandable when spoken aloud, to the point of suggesting that methods with boolean outcomes should end in a questionmark, and that destructive methods should end with an exclamation mark. That’s fun.

More on this mystery project later, perhaps; I’m hoping it could be moderately big.

Bah

07 August 2005

So much for the success with Ruby on Rails detailed in the last post. Everything was going fine until I tried to create a scaffold. At which point I get a nasty little MySQL error:

Access denied for user ''@'localhost' (using password: NO)

So basically, even though I’ve defined all the database settings correctly in the database.yml file for the application, it’s trying to connect without a username or password. And as such, quite rightly, is failing. I’ve reinstalled the mysql gem, I’ve tried configuring the database.yml file to connect as root; none of it works. Bit frustrated – I can’t see that I’ve done anything wrong, but it’s just not working. I’ve also tried users with both old and new password hashes, and that hasn’t helped either. Anyone got any ideas? Anyone? Lazyweb?

Hear the Tiger RoR

07 August 2005

Well, there’s a thing. I managed (with relatively no hassle) to get a local install of Ruby on Rails up and running on my 10.4.0 Powerbook. Quite surprised – given the scattered documentation – that it went so well. For reference, I used these instructions, which worked exactly as specified. I did already have an install of MySQL, but that was dead easy when I did it a few months ago, too.

Update: of course, then I ran into problems. What I discovered is that those instructions are for an install of Tiger with XCode 2.0, which still only has gcc 3.3. I upgraded to XCode 2.1, and gcc upgraded to 4.0 – and there my troubles began. Of course, then I solved them.

Now to get to work in it. I have an application in mind that it would be ideally suitable for; whether or not I can get my head around the language (especially without making my forget the other ones I know) is another matter. If I was a real programmer, I’m sure this wouldn’t be a problem, but alas, I’m not. Ah well.