开发者

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

开发者 https://www.devze.com 2022-12-19 23:50 出处:网络
Given the following resource definition: map.resources :posts, :except => [:show] map.post \'/:year/:month/:slug, :controller => :posts, :action => :show

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, ti开发者_运维百科tle: "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.


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.


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


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


I was trying out TempoDB and extending their class with the below worked for me. I had the route "resources :series" and then could use url_for with no problem.

class TempoDB::Series
  def self.model_name
    ActiveModel::Name.new(TempoDB::Series,nil,'series')
  end
  def to_param
    self.key
  end
end
0

精彩评论

暂无评论...
验证码 换一张
取 消