Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

url_for of a custom RESTful resource (composite key; not just id)

Given the following resource definition:

map.resources :posts, :except => [:show]
map.post '/:year/:month/:slug, :controller => :posts, :action => :show

I can make url_for work for me, using this syntax:

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

But is there a way to make this work?

<%= link_to @post.title, @post %>

Currently it throws this error:

No route matches {:year=>#<Post id: 1, title: "test", (...)>, :controller=>"posts", :action=>"show"}

Apparently it passes the @post object to the first route parameter (seems like a Rails bug...). But can I make this work for me? I'll add that messing with default_url_options is a dead end.

Solution working only in Rails 3.x is ok, but I would prefer not to use any plugins.

like image 809
Paweł Gościcki Avatar asked Feb 15 '10 00:02

Paweł Gościcki


3 Answers

Override to_param. The default value for to_param is the record ID, but you can change that.

class Post
  def to_param
    "/#{year}/#{month}/#{slug}"
  end
end

(This assumes you have methods in the Post class for year, month and slug.)

Now when URLs are generated, your overridden to_param will be called.

<%= link_to @post.title, @post %> 
    => <a href="/2010/02/foobar">Foobar</a>

Obviously you must ensure your PostsController show action can find the record based on the parameters it is passed.

You may need to change your route definition and eliminate this route:

map.post '/:year/:month/:slug, :controller => :posts, :action => :show

However, with to_param overridden, the default RESTful post_path will work just fine for the URLs you want to generate.

like image 163
Luke Francl Avatar answered Nov 14 '22 02:11

Luke Francl


Unfortunately, when you pass an ActiveRecord to link_to, it tries to use Polymorphic URL helpers to generate the URL for the link.

The Polymorphic URL helpers are only designed to generate RESTful URLs by using the record identifier of your ActiveRecord.

Since your route uses multiple attributes of the Post object, the Polymorphic URL helpers are not equipped to generate the correct URL... not so much a bug as a limitation :)

Delving into link_to, when you pass it a Hash, it doesn't use Polymorphic Routing, so you avoid the whole problem.

I suppose a hacky approach would be to define a method on Post called routing_hash which returns

(:year => post.year, :month => post.month, :slug => post.slug)

I appreciate that it's not a DRY approach, but it's the best I can come up with at the moment

like image 3
Dancrumb Avatar answered Nov 14 '22 02:11

Dancrumb


How about fixing url_for for the PostsController? May not be very elegant but it is DRY and things should just work.

# app/controllers/posts_controller.rb 
class PostsController < ApplicationController

  protected

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

end
like image 7
anshul Avatar answered Nov 14 '22 01:11

anshul