opinions and rants on software and...things
December 07, 2008
A few posts ago, I promised to share my fail-proof instructions for installing and integrating the Ruby on Rails plugin called acts_as_urlnameable. Here is what I learned. Since I put this together, I have discovered some more issues that I need to find workarounds for, and I'll post about those later. For now, here's how you get it working for you in the vast majortiy of cases.
1. Install plugin:
- script/plugin install http://code.helicoid.net/svn/rails/plugins/acts_as_urlnameable/ will make a static copy of the source for you, or
- script/plugin install -x http://code.helicoid.net/svn/rails/plugins/acts_as_urlnameable/ will fetch a copy via SVN
2. Add acts_as_urlnameable to //environment.rb// if needed. If you define config.plugins, add urlnameable there.
3. For each model that will be urlnameable, add acts_as_urlnameable:
class Foo < ActiveRecord::Base acts_as_urlnameable :nameable_field end
4. Add to each Model an override of to_param. This implementation differs from the suggested ones by continuing to provide the default numeric id for records that haven't been urlnameified yet. This should help you to avoid breaking existing functionality when adding this to an existing website:
def to_param if urlname and urlname.length > 0 urlname else id end end
5. Add a new method, smart_find. Again, this approach allows you to have mixed numeric and urlnamed ids. This will save a good deal of time when converting an existing Rails application to use acts_as_urlnameable. There are plenty of places in code that you won't care about having pretty, legible ids, like in HIDDEN form fields. This reduces the total exposure to your code:
def self.smart_find(id) found_foo = nil if id.to_i > 0 # We got a regular, old int id, so look it up as usual found_foo = Foo.find(id) else # We got a string, a urlname id found_foo = Foo.find_by_urlname(id) end end
6. Add a migration to add the table and apply to existing rows:
class AddUrlnamesTable < ActiveRecord::Migration # :nodoc: def self.up create_table 'urlnames' do |t| t.column 'nameable_type', :string t.column 'nameable_id', :integer t.column 'name', :string end # For each Model to which acts_as_urlnameable will apply # and which has existing rows, add a loop like the following; # simply resaving each record will add the urlname data. for each_foo in Foo.find(:all) each_foo.save end end def self.down drop_table 'urlnames' end end
7. In each controller that references one of the now-acts_as_urlnameable Models, change references to Foo.find to Foo.smart_find
8. In all of your views, check for links that use the pattern '':id => @foo.id'' and replace them with '':id => @foo''. This will allow the to_param override to intelligently choose which id to provide.
That's the basic idea, and that should be enough to get anyone going with acts_as_urlnameable. During the course of integrating it into My Kids Library, I have discovered a number of circumstances that require some pretty sophisticated customization, and I will detail those in future posts. Until then, I am happy to answer any questions posted in the Comments section.
August 28, 2008
One of the many changes that Jessamyn suggested for MyKidsLibrary is that the URLs should be comprised of meaningful text rather than just numbers. In addition to being more human-friendly, it is, apparently, an important search engine optimization technique.
Ruby on Rails likes to construct URLs that end with a numeric identifier that is used to look up a specific record in the database. It is an efficient, effective solution, and the software engineer side of me never considered why you'd have it be otherwise. I have come to think of URLs as being things that are as effectively meaningless and worthless to my brain as printouts of UNIX coredumps. I click on links, I bookmark pages, I never pay the slightest attention to URLs. I use tools -- browsers, bookmarking services -- to work with URLs just as I use tools to write software.
Once I decided to go about making the change, I set out to find who else had already done this work. The Rails ecosystem is vast and densely populated; I knew that there was but a very tiny chance that I'd actually have to start from scratch. Sure enough, a little work on Google revealed that there were many candidate solutions. I picked one that looked solid and set about integrating it into my project.
I'm not new to this; I have been a working, salary-earning software engineer for nearly two decades, so I should have been prepared for the documentation to suck. The documentation always sucks. The last time that I read really good, comprehensive documentation was when I was writing code for a VMS system, and I sat right next to the big orange wall. At least, I remember it being good; it's all so long ago that I might be remembering it in a somewhat nostalgic light.
I had to figure out a lot of things that weren't mentioned in the documentation, and while that's not the worst thing, it is still frustrating to see a useful, well-put-together package that stops just short of being perfect. And, really, they all do.
Open Source is invaluable, but in many respects, it reminds me of a Roadside Picnic.
And just to prove that I'm not a hypocritical dick, my next post will include extensive, failproof instructions for configuring and using the wonderful Rails plugin acts_as_urlnameable.