UPDATE (again) | Ignore the module thing-o I put up here before. Better off just using to_param and making the routes work.
to_param
if slug
"#{id}-#{slug}"
else
super
end
end
match "/posts/:id(-:slug)" => "posts#show", :as => :post, :constraints => { :id => /\d+|\d+-.*/ }
The trick is in that route. It makes -:slug optional, and adds a constraint to :id. The constraint’s preference is to match just a numeric value (which is what gets used when a request comes in, so you have :id and :slug in your params), but the pipe allows it to also match a numeric value followed by the slug, for the purpose of route generation. It’s a mild hack, but this whole thing is really. Allowing resource to provide a hash of params would solve this. This could be applied to any fancy route you want (e.g. having the :id at the end instead of the start).
Rails 3.0.7.
Take a route like:
match "/posts/:id-:slug" => "posts#show", :as => :post
This provides a post_path(obj) helper for me, but of course, it only wants to generate a single argument from my model, and from what I can tell, there’s no way to return multiple values from my models’ to_param method. I know I can write to_param like this:
to_param
"#{id}-#{slug}"
end
But this is passed as a single argument to the route, which ultimately doesn’t match any routes. Similarly, I know I can remove the "-:slug" from my route, but then the :id parameter contains bogus input and is basically a bit of a hack (though apparently the done thing).
It’d be awesome if a future version of Rails let you return a hash from to_param, like:
to_param
{ :id => id, :slug => slug }
end
then used those when locating the correct route to use.
What I’m trying to figure out, is where I would go about overriding post_path() to provide the correct route. I can put it in a view helper, but then it’s not available in my controllers, so I’m guessing this is the wrong place to do it.
Just curious more than anything. I know it works fine if I just omit the :slug on the route, but in a more complex situation I can imagine overriding the default paths to be a useful thing to know. Being able to generate a route just by passing a resource is a pretty nice feature I’d like to adopts, but the marketing dept will always have us filling the URLs will all kinds of keywords mumbo jumbo, so some control would be excellent 🙂
There are two sides to routing: route generation and route matching.
The route generation, as you point out, is quite easy. Override
to_paramin any ActiveModel:The route matching is less obvious. It is possible to specify
:awesome_identifierin place of every occurrence of:idin the default resource routes. However, I have found that Rails will give you less resistance if you leave:idin your routes, and only change the logic in your controller. Note that this is a trade-off, because fundamentally the:idin your routes is not really correct.As you have noted, Rails has optimised a single use case: if you want the primary key to be used for quick record look-up, but still prefer a slug to be included in the URL for user-friendliness or search engine optimisation. In that case you can return a string of the form
"#{id}-#{whatever}"from theto_parammethod, and everything after the dash will be ignored when feeding the same string back into thefindmethod.