Overriding url_for for Fun and Profit

| | Comments (2)

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

2 Comments

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

Leave a comment

About this Entry

This page contains a single entry by zenspider published on December 2, 2005 1:38 PM.

rubyholic v1.0 is live was the previous entry in this blog.

Splat is good for you is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Pages

Powered by Movable Type 4.1