A while back I was thinking about MVC and how URLs are built and came to the conclusion that every model should know what its URL is. I don't think it is the responsibility of the Controller to know this, and certainly not the view.
Since Rails uses a Hash to represent a URL, I thought it would be easy to just add a method to every Model that would return a Hash representation of the URL and override url_for to perform the expansion from a model into its URL Hash.
It turns out that it was easy. After some enhancements by Ryan that adds defaults we came up with the following:
class ApplicationController < ActionController::Base
def url_for(options = {}, *params)
if options.include? :model then
model = options.delete(:model)
defaults = {
:action => 'show',
:id => model.id,
:controller => model.class.name.downcase,
}
defaults = defaults.merge model.url_params if model.respond_to? :url_params
options.delete :only_path if defaults.include? :only_path
options = defaults.merge options
end
return super(options, *params)
end
end
You can use the modified url_for like this:
@model = Model.find some_id url_for :model => @model url_for :model => @model, :action => 'edit' redirect_to :model => @model link_to @model.name, :model => @model
Sometimes the defaults aren't good enough though. To override them you add a url_params method to your model. In Trackmap I store enough information about a flickr photo to generate its flickr URL in the database and my url_for modification (with a bit of cheating) makes this very clean:
class Photo < ActiveRecord::Base
#...
def url_params
return { :host => 'flickr.com', :controller => 'photos',
:action => flickr_nsid, :id => flickr_photo_id,
:only_path => false }
end
end

I found this extremely useful, but it did cause me some trouble, probably due to the rapid evolution of Rails. ActionView::Helpers::UrlHelper::url_for calls this twice via send in certain circumstances.
It seems to behave correctly once you wrap the "if options.include? :model" statement in "if options.is_a? Hash".
It seems that this code assumes options is indeed a Hash. After that it works great.
ryan-
check out
http://gist.github.com/34225
with this you can do class Model def link model = self helper{ linkto model } end end or class Model def link helper.linkto(...) end end the key, of course, is having ActionController.current set for each request. otherwise it's straightforward and very flexible.
btw - i couldn't agree with you more.
-a