Special characters and nested routes »

Created at: 14.11.2007 22:58, source: poocs.net - Home, tagged: rails

I might be the only crazy guy on the planet using strings as the primary record identifier in the URI and also allowing a bunch of special characters in it. And just in case I am, please move along. I’ll just keep this article around as a reference for myself.

Say you’ve got a Rails web app from the early days. Way back when REST and Rails haven’t been spending day and night together. Back when we just had named routes.

But actually, back when it was already popular to occasionally use something different from the numeric primary key to identify a resource in a URL. Example:

/profile/show/scoop

Well, as far as that would actually resemble a URI of my own profile, some people are way fancier with their nicknames and the punctuation therein. The most prominent punctuation character is probably the period (.).

Back in the days, that was all dandy. Well, that was then and this is now.

Meet the Resource

With restful routes, we don’t need no stinkin’ named routes (for the most part). A simple declaration like


# config/routes.rb
map.resources :users

and we get a boatload of useful helpers such as user_path and new_user_path generating the appropriate URIs for us. But I digress.

Rewiring our application to use restful routes for the User model (after having seen the light), all’s well until you try to access a URI like

/users/mr.piggy

Eek! Rails interprets the period as a designation of format, like in /users.xml to offer an XML representation of the requested resource.

Now, who wants a piggy representation of a user?

To allow a period in the params[:id] part of the URL (which, after all, is what the username ends up in after routing is done chopping apart the requested URI), Rails 2 will have a way to re-use the :requirements parameter as an argument to map.resources, like so:


# config/routes.rb
map.resources :users,
  :requirements => { :id => %r([^/;,?]+) }

(Note: This has been committed to the Rails trunk back in February of 2007 as changeset 6232. I backported it as a patch to Rails 1.2 residing locally in my application (so I didn’t have to directly modify the Rails code. If there’s an interest in that I’ll post it as a separate article in a few days.)

Boom, now /users/mr.piggy nicely ends up having a params[:id] value of mr.piggy (instead of a value of mr and a failing attempt to render a piggy format).

What about nesting?

But our journey doesn’t end here. Coincidentally, if a site has users, those users tend to interact with it (in a true Web 2.0 fashion, you know).

Adding further to routes.rb, we now have a routing configuration like this


# config/routes.rb
map.resources :users,
  :requirements => { :id => %r([^/;,?]+) } do |users|

  users.resources :interests
  users.resources :buddies
end

Easy, right? Trying to access a URL like /users/mr.piggy/buddies throws an exception though.

No route matches "/users/mr.piggy/buddies" with {:method=>:get}

The fix would be to add the :requirements constraint to all the nested routes as well.


# config/routes.rb
map.resources :users,
  :requirements => { :id => %r([^/;,?]+) } do |users|

  users.resources :interests,
    :requirements => { :id => %r([^/;,?]+) }
  users.resources :buddies,
    :requirements => { :id => %r([^/;,?]+) }
end

Such a nasty repetition. Especially considering we might add a bunch of additional resources in there. Object#with_options to the rescue. Here’s the refactored version:


# config/routes.rb
map.resources :users,
  :requirements => { :id => %r([^/;,?]+) } do |users|

  users.with_options :requirements => {
    :id => %r([^/;,?]+)
  } do |users_requirements|

    users_requirements.resources :interests
    users_requirements.resources :buddies
  end
end

Much better. Accessing /users/mr.piggy/buddies works as expected now, routing to BuddiesController#index with params[:user_id] having a value of mr.piggy, which you can use in a before filter to setup the parent resource.

Final words

It’s debatable whether or not it’s a good idea to allow punctuation characters in the URI. In my case, reworking parts of an existing application with an existing user base of several hundred thousand registered users I didn’t have an option but to comply.


more »

Slow queries: Rails habtm gotcha »

Created at: 09.11.2007 00:23, source: poocs.net - Home, tagged: rails

Back in the old days when some brave souls started with Rails, the framework would automatically supply helper methods to check the availability of associated records for every has_many or has_and_belongs_to_many association in the form of has_foos?.

For example, given a declaration like this:


class User
  has_and_belongs_to_many :interests
end

would get you User#has_interests? back in the days.

Fast forward to the present, these methods have long been removed from Rails core. There’s no 1:1 replacement, really. When you want to check a collection in a conditional for whether or not it’s empty, there are several things you can do.

Association Proxies, a quick review

Basically, every collection is a derivative of the Array class, as such most of Arrays methods have been (re-)implemented for the respective collection proxies.

First of all, there’s collection#length, which is the most brutal of them all because it simply loads the whole collection into memory and runs #size on it.

Next up is collection#count, which uses an SQL COUNT() statement to just fetch the number of associated records and is usually the quickest way at that.

Finally, there’s collection#size, which is basically a combination of both length and count since it behaves differently depending on whether or not the collection has already been loaded at the time it is being called. Given the collection has already been loaded (by a prior statement in your code, what have you) it’s identical to collection#length. If it hasn’t been loaded, its behavior is identical to collection#count.

Best practice

Judging from the rundown above, it’s usually best practice to simply use collection#size and trust it to do the right thing. In case you’re planning to use a collection as soon as you found out there are records associated with it, use collection#length to load the connection right away and then loop over it. Beware, however, that this is advisable only for those cases that don’t use pagination. (You’re using will_paginate for that, aren’t you?)

In a conditional, it makes most sense to use collection#empty?, which uses size#zero? internally.


unless user.interests.empty?
  # ...
end

The problem

99% of what I said above applies to both has_many and has_and_belongs_to_many associations. Admittedly, poor little habtm has had its momentum and seems to be on the way out of the cool kids’ block. Still, there are a few cases where it’s still the best fit (such as the User#interests example from first section of this article) and some people *do* have Rails code that’s been in existence for a few years. (Well, maybe only I do.)

So, the 1% that behaves differently is that size on a habtm collection is not as intelligent as its little brother from the has_many camp. In fact, it’s pretty dumb because it’s in every case behaving identical to length meaning it’s loading the whole collection. Ouch.

>> user.interests.size
  Join Table Columns (0.001284)   SHOW FIELDS FROM interests_users
  Interest Load (0.001263)   SELECT * FROM interests
    INNER JOIN interests_users
    ON interests.id = interests_users.interest_id
    WHERE (interests_users.user_id = 142841 )

The solution

You have to fill in the gaps of habtm#size’s lack of intelligence yourself. If you trap over habtm queries that take way too long for your taste, it might be about time to swap out that size call for an always speedy count call.

>> u.interests.count
  Interest Columns (0.001006)   SHOW FIELDS FROM interests
  SQL (0.009749)   SELECT count(*) AS count_all FROM interests
    INNER JOIN interests_users
    ON interests.id = interests_users.interest_id
    WHERE (interests_users.user_id = 142841 )

You would then also avoid using collection#empty? in your conditionals, since that would then resort back to using size internally. Instead, use the count#zero? form or define your own instance methods on the association.


if user.interests.count.zero?
  # ..
end

Postscript

To actually find out what Rails is transforming your method calls into (in terms of generated SQL) I prefer to use this technique by the always-wise Jamis Buck. Redirecting the ActiveRecord logger to the shell’s standard output will intermix the SQL statements with the output of your method calls, making everything relevant visible at first glance.


more »

Revisiting date localization for Ruby 1.8.6 »

Created at: 23.10.2007 17:45, source: poocs.net - Home, tagged: rails

Welcome to my yearly post about a revised version of a simple way to localize Ruby’s Time#strftime method (after my original post in ‘05 and the update in ‘06).

What’s changed?

In my original methodology, the arrays in the Date class containing the actual day and month names in clear text had to be replaced by strings wrapped in the infamous _() call of GetText in order to spit out their localized versions when requested from the modified Time#strftime call.

When Ruby 1.8.6 came along, it had its Date array constants frozen. While I agree it’s generally not a good idea to modify the contents of a constant (it’s a constant after all, mind you), the implementation I came up with 2 years ago worked fine up to and including today.

Since, also, most of the sites I’m working on require multiple languages (as opposed to, say, a single language set at application boot time), the contents of those Date arrays truly need to be modified at runtime and having those arrays frozen meant jumping through all sorts of hoops to address a supposedly simple issue.

What now?

Well, I had to come up with a way to actually execute the gettext functionality in the context of an object that has the current GetText locale bound to it. The most obvious object with that kind of property is ApplicationController.

But this still leaves us with the issue that we cannot modify the frozen Date constants and that everything we put into these constants is evaluated right away instead of at a point where we have the desired locale set.

What I came up with was a proxy object derived from the Array class that would basically just wrap the cleartext contents that are already in each of the Date arrays but not before they’re actually accessed/output.


    # lib/date_localization.rb
    class GetTextDateProxy < Array

      cattr_accessor :gettext_proxy

      def [](key)
        _(at(key))
      end

      def _(string)
        return string unless gettext_proxy
        gettext_proxy.instance_eval { gettext(string) }
      end

    end

As you can see, the proxy object is pretty simple after all. It just overwrites the default of Array#[] to look up the requested key and then wrap the output in _().

Since I couldn’t actually be bothered to include the whole of Ruby-GetText into my poor little proxy object, I then made room for it to hold a pointer to an instance of ApplicationController in a class attribute called gettext_proxy. The local underscore method of my proxy class then wraps around the actual gettext implementation available to the gettext proxy (read: ApplicationController), returning the string unchanged if there is no gettext proxy and if there is, run the string through the proxy’s gettext method.

Dealing with a state of not having a gettext proxy available is actually necessary in cases where there are calls to these arrays before ApplicationController gets to hook itself up to GetTextDateProxy.

But how does this actually hook into the Date class now? Let’s take a look:


    # lib/date_localization.rb
    class Date

      silence_warnings do
        %w(
          MONTHNAMES DAYNAMES ABBR_MONTHNAMES ABBR_DAYNAMES
        ).each do |array|
          class_eval %( #{array} = GetTextDateProxy.new(#{array}) )
        end
      end

    end

While this still looks a little tedious, it’s far less tedious than repeating the arrays’ contents simply wrapped in _() all over the place, like we had to do in previous incarnations of this methodology.

Basically, now every array is hooked up with their very own instance of GetTextDateProxy which then takes care of running the strings through gettext in the appropriate moments.

Wiring it all up

For the last part, we need to get ApplicationController to hook itself into GetTextDateProxy after having received the appropriate locale for the request it’s dealing with.

The best fit for this is the after_init_gettext hook provided by Ruby-GetText. Adding this code to ApplicationController will take care of the last required step.


    # app/controllers/application.rb
    after_init_gettext { GetTextDateProxy.gettext_proxy = self }    

Here you go, working (albeit hackish) localization for Ruby’s Time#strftime working up to and including the most recent version of Ruby, which is 1.8.6 as of this writing.

Enjoy.


more »

Ruby-Debug HOWTO and free book »

Created at: 02.10.2007 21:59, source: poocs.net - Home, tagged: rails

Somewhere between the release of my Rails book and now, several things happened. First of all, Ruby 1.8.6 was released in March and quickly became the preferred version to use with Rails application with the release of Rails 1.2.3.

This, however, meant the end to the era of the breakpointer library to debug your Rails application, which relied on a bug in Ruby that had been fixed in Ruby 1.8.5 onwards. The Rails Core group was quick to adopt ruby-debug, Kent Sibilev’s native implementation of a Ruby debugger which was also heavily inspired by more powerful debuggers from other programming languages such as C.

With the preview release of Rails 2.0, available since Sep 30, integration of ruby-debug has been fostered even more.

For this very reason, I recently sat down and wrote a replacement chapter for my Rails book that serves both as a gentle conceptual introduction to ruby-debug (which still somewhat lacks detailed hands-on articles, due to its still fairly young age) and a replacement for the example debugging sessions the printed book has (but relying on the slightly different operation of the breakpointer client).

The article is available through SitePoint.com. Please note, however, that you should have the source code for the sample application from the book available, to give yourself some context and the full scope of the code.

Rails book available for FREE

On a related note, SitePoint, the publisher of Build Your Own Ruby on Rails Web Applications, decided to offer the entire book as a free PDF download for a limited time period of 60 days starting today. Grab your copy of the 474 pages (including the updated chapter on debugging making use of ruby-debug) here.


more »

Loading all Rails test fixtures with fixtures :all »

Created at: 27.05.2007 19:39, source: Cody Fauser - Show all, tagged: rails rails testing

Are you as tired as we were of loading 20+ different fixtures in each of your Rails test classes? We were, and we even added a method all_fixtures() to test_helper.rb to do the loading of all our fixtures for us.

Thankfully though, we don't need our own helper method anymore, as the Rails fixtures() method will now accept a symbol :all, which will instruct the test helper to load all of your fixtures automatically.

1
2
3
4
5
6
7
8

require File.dirname(__FILE__) + '/../test_helper'

class ShopTest < Test::Unit::TestCase
  fixtures :all

  # Your tests here
end

As of Rails 1.2.3 this feature has not yet been merged from the trunk. This means that you'll either need to run Edge Rails from Subversion, or install the beta Rails gems as follows:


sudo gem install -s http://gems.rubyonrails.org rails -y

Happy testing!


more »