referencing REST resources with something other than ids

Hello. I got a REST URL personalization issue... Here is an example to explain:

If have two nested resources set like this in "routes.rb":

map.resources :teams do |teams|   teams.resources :players end

So, some teams will be accessible with URL such that:

/teams/42 /teams/42/edit

and the players by some of the form:

/team/42/players /team/42/players/18 /team/42/players/new

etc...

Is there a convenient way do not use the "id" column of the "team" and "player" models but another one (e.g. "name", supposing it's unique) to have nicer URLs but keeping the ability to use *_path helpers? So, I dream of URLs such that, keeping the same example:

/teams/mylovelyteam /teams/mylovelyteam/players /teams/mylovelyteam/players/new /teams/mylovelyteam/players/theleader

I hope my explanation is understandable...

Thanks.

aurels, belgium

I know of one way. I can’'t say if it’s the best or not.

When a route is generated, the id method of the object is not actually called. It’s just the default. The magic method is to_param

So in your method if you put

def to_param self.name end

Then this should give you the routes that your after. You will need to escape it though so it doesn’t give you bad urls. However…

I believe that by doing this ( I haven’t tried it myself ) it will still call the parameter :id, but you will need to find_by_name in your controllers.

so was ==> Team.find( params[:id] ) Now ==> Team.find_by_name( params[:id] )

The way I have done it to avoid this is to include the id still. But I don’t know if I’ll keep it like this for ever. /team/1-my-lovely-team

This way ruby still interprets the id as an integer and I can use it as normal.

Here’s the code I’m using. I got the escaping code from Rick Olson’s permalink_fu plugin

cattr_reader :translation_to, :translation_from @@translation_to = ‘ascii//ignore//translit’

@@translation_from = ‘utf-8’

Ripped directly from permalink_fu by Rick Olson

def escape_slug(str) s = Iconv.iconv(self.class.translation_to, self.class.translation_from, str).to_s

    s.gsub!(/\W+/, ' ') # all non-word chars to spaces
    s.strip!            # ohh la la
    s.downcase!         #
    s.gsub!(/\ +/, '-') # spaces to dashes, preferred separator char everywhere

    s

end

def to_param escape_slug “#{self.id}-#{self.name}” end

HTH

Daniel

The first solution works good :wink: So thanks! Hope this will help some else!

Aurélien Malisart wrote:

Hello. I got a REST URL personalization issue... Here is an example to explain:

If have two nested resources set like this in "routes.rb":

map.resources :teams do |teams|   teams.resources :players end

So, some teams will be accessible with URL such that:

/teams/42 /teams/42/edit

and the players by some of the form:

/team/42/players /team/42/players/18 /team/42/players/new

etc...

Is there a convenient way do not use the "id" column of the "team" and "player" models but another one (e.g. "name", supposing it's unique) to have nicer URLs but keeping the ability to use *_path helpers? So, I dream of URLs such that, keeping the same example:

/teams/mylovelyteam /teams/mylovelyteam/players /teams/mylovelyteam/players/new /teams/mylovelyteam/players/theleader

I hope my explanation is understandable...

Thanks.

aurels, belgium   

Yes, sort of.

I've got a project that does this with one of it's controllers and it works great. What you need to understand is that the RESTful routes will just pass whatever comes after /teams/ in the url as the :id in the hash. So, what you would do in your controller is...

Team.find_by_name(params[:id])

This then assumes that you'll always, always, always get the team name in the url, and will never be trying to call /teams/42, as that would just look for a team named "42", which may or may not exist. One option is, if you're certain you'll NEVER have a team who's name is a number, then you could do a REGEX check on the params[:id] and if it purely numbers, then do a normal find by :id, otherwise lookup by name with the params[:id] that isn't really an :id.

Here's the code I'm using. I got the escaping code from Rick Olson's permalink_fu plugin

You can, of course, just install the plugin :slight_smile:

http://svn.techno-weenie.net/projects/plugins/permalink_fu/README

Rick,

Can this be used for the purpose outlined previously, just to provide a slug, or do I need to use it with the date prefix, and a seperate db column?

Cheers Daniel

Can this be used for the purpose outlined previously, just to provide a slug, or do I need to use it with the date prefix, and a seperate db column?

The db column is so you're not constantly escaping your titles. It also ensures that the permalink doesn't change each time you tweak the title, which can be bad for SEO. Still, you can use PermalinkFu.escape to manually escape in a #to_param method if you want.

Just so I understand, I don’t want to be a pain in the arse.

With the way that I’m doing it if the title changes it doesn’t matter to the app, since the id is at the start of the slug, is this still bad for SEO since the link that the SE has is still valid?

Do I need to have the date prefix /blah/2007/07/07/permalink_fu

Cheers Daniel

What you need to understand is that the RESTful routes will just pass whatever comes after /teams/ in the url as the :id in the hash

I do :wink:

So, what you would do in your controller is...

Team.find_by_name(params[:id])

That's what I'm doing actually.

Thanks.