自定义RESTful资源的url_for(复合键;不仅仅是id)

时间:2010-02-15 00:38:03

标签: ruby-on-rails ruby rest

给出以下资源定义:

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

我可以使用以下语法让url_for为我工作:

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

但有没有办法让这项工作成功?

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

目前它会抛出此错误:

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

显然它将@post对象传递给第一个路由参数(看起来像是一个Rails bug ...)。但我可以为我做这项工作吗?我要补充说,弄乱default_url_options是一个死胡同。

仅在Rails 3.x中工作的解决方案没问题,但我不想使用任何插件。

4 个答案:

答案 0 :(得分:7)

覆盖to_paramto_param的默认值是记录ID,但您可以更改它。

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

(这假设您在Post班级中有yearmonthslug的方法。)

现在生成网址时,系统会调用被覆盖的to_param

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

显然你必须确保你的PostsController show动作可以根据传递的参数找到记录。

您可能需要更改路线定义并消除此路线:

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

但是,如果to_param被覆盖,默认的RESTful post_path对于您要生成的网址就可以正常使用。

答案 1 :(得分:6)

如何为url_for修复PostsController?可能不是很优雅,但它是干的,事情应该有效。

# 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

答案 2 :(得分:3)

不幸的是,当您将ActiveRecord传递给link_to时,它会尝试使用Polymorphic URL帮助程序来生成链接的URL。

Polymorphic URL帮助程序仅用于通过使用ActiveRecord的记录标识符生成RESTful URL。

由于您的路由使用Post对象的多个属性,因此Polymorphic URL帮助程序无法生成正确的URL ...而不是作为限制的错误:)

深入研究link_to,当你传递哈希时,它不会使用多态路由,所以你可以避免整个问题。

我认为一种hacky方法是在Post上定义一个名为routing_hash的方法,该方法返回

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

我很欣赏它不是干眼的方法,但它是我现在能想到的最好的方法

答案 3 :(得分:0)

我正在尝试TempoDB并使用以下工作扩展他们的课程。我有路由“资源:系列”,然后可以使用url_for没有问题。

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