Search code examples
ruby-on-railsurlrestslug

Pretty (dated) RESTful URLs in Rails


I'd like my website to have URLs looking like this:

example.com/2010/02/my-first-post

I have my Post model with slug field ('my-first-post') and published_on field (from which we will deduct the year and month parts in the url).

I want my Post model to be RESTful, so things like url_for(@post) work like they should, ie: it should generate the aforementioned url.

Is there a way to do this? I know you need to override to_param and have map.resources :posts with :requirements option set, but I cannot get it all to work.


I have it almost done, I'm 90% there. Using resource_hacks plugin I can achieve this:

map.resources :posts, :member_path => '/:year/:month/:slug',
  :member_path_requirements => {:year => /[\d]{4}/, :month => /[\d]{2}/, :slug => /[a-z0-9\-]+/}

rake routes
(...)
post GET    /:year/:month/:slug(.:format)      {:controller=>"posts", :action=>"show"}

and in the view:

<%= link_to 'post', post_path(:slug => @post.slug, :year => '2010', :month => '02') %>

generates proper example.com/2010/02/my-first-post link.

I would like this to work too:

<%= link_to 'post', post_path(@post) %>

But it needs overriding the to_param method in the model. Should be fairly easy, except for the fact, that to_param must return String, not Hash as I'd like it.

class Post < ActiveRecord::Base
  def to_param
   {:slug => 'my-first-post', :year => '2010', :month => '02'}
  end
end

Results in can't convert Hash into String error.

This seems to be ignored:

def to_param
  '2010/02/my-first-post'
end

as it results in error: post_url failed to generate from {:action=>"show", :year=>#<Post id: 1, title: (...) (it wrongly assigns @post object to the :year key). I'm kind of clueless at how to hack it.


Solution

  • It's still a hack, but the following works:

    In application_controller.rb:

    def url_for(options = {})
      if options[:year].class.to_s == 'Post'
        post = options[:year]
        options[:year] = post.year
        options[:month] = post.month
        options[:slug] = post.slug
      end
      super(options)
    end
    

    And the following will work (both in Rails 2.3.x and 3.0.0):

    url_for(@post)
    post_path(@post)
    link_to @post.title, @post
    etc.
    

    This is the answer from some nice soul for a similar question of mine, url_for of a custom RESTful resource (composite key; not just id).