6

我希望我的网站具有如下所示的 URL:

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

我的Post模型带有slug字段('my-first-post')和published_on字段(我们将从中扣除 url 中的年份和月份部分)。

我希望我的Post模型是 RESTful 的,所以url_for(@post)像它们应该的那样工作,即:它应该生成上述 url。

有没有办法做到这一点?我知道您需要覆盖to_parammap.resources :posts设置:requirements选项,但我无法让它全部工作。


我几乎完成了,我已经完成了 90%。使用resource_hacks 插件我可以做到这一点:

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"}

在视图中:

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

生成正确的example.com/2010/02/my-first-post链接。

我也希望这个工作:

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

但它需要覆盖to_param模型中的方法。应该相当容易,除了to_param必须返回的事实,而String不是Hash我想要的。

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

结果can't convert Hash into String出错。

这似乎被忽略了:

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

因为它会导致错误:(post_url failed to generate from {:action=>"show", :year=>#<Post id: 1, title: (...)它错误地将 @post 对象分配给 :year 键)。我对如何破解它一无所知。

4

5 回答 5

4

Rails 3.x 和 Rails 2.x 的漂亮 URL,不需要任何外部插件,但不幸的是,需要一点 hack。

路线.rb

map.resources :posts, :except => [:show]
map.post '/:year/:month/:slug', :controller => :posts, :action => :show, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/

application_controller.rb

def default_url_options(options = {})
  # resource hack so that url_for(@post) works like it should
  if options[:controller] == 'posts' && options[:action] == 'show'
    options[:year] = @post.year
    options[:month] = @post.month
  end
  options
end

post.rb

def to_param # optional
  slug
end

def year
  published_on.year
end

def month
  published_on.strftime('%m')
end

看法

<%= link_to 'post', @post %>

请注意,对于 Rails 3.x,您可能希望使用此路由定义:

resources :posts
match '/:year/:month/:slug', :to => "posts#show", :as => :post, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/

是否有任何徽章可以回答您自己的问题?;)

顺便说一句:routing_test文件是查看 Rails 路由可以做什么的好地方。

更新:使用default_url_options是一个死胡同。仅当@post控制器中定义了变量时,发布的解决方案才有效。例如,如果有一个@posts带有帖子数组的变量,我们就不走运了(default_url_options因为无法访问视图变量,例如p@posts.each do |p|.

所以这仍然是一个悬而未决的问题。有人帮忙吗?

于 2010-02-14T18:06:26.887 回答
4

它仍然是一个黑客,但以下工作:

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

以下将起作用(在 Rails 2.3.x 和 3.0.0 中):

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

这是我的一个类似问题的一些好人的答案, url_for of a custom RESTful resource (composite key; not just id)

于 2010-02-24T17:23:07.170 回答
1

您可以减轻压力并使用friendly_id。它很棒,可以完成这项工作,您可以查看Ryan Bates的截屏视频以开始使用。

于 2012-01-12T00:50:07.533 回答
1

Ryan Bates 在他的屏幕截图中谈到了它“如何添加自定义路线,使某些参数可选,并为其他参数添加要求。” http://railscasts.com/episodes/70-custom-routes

于 2010-02-13T20:50:34.053 回答
1

这可能会有所帮助。您可以在您的方法中定义一个default_url_options方法,该方法ApplicationController接收传递给 url 帮助程序的选项哈希,并返回您要用于这些 url 的其他选项的哈希。

如果将帖子作为参数提供给post_path,它将被分配给路线的第一个(未分配的)参数。尚未对其进行测试,但它可能会起作用:

def default_url_options(options = {})
  if options[:controller] == "posts" && options[:year].is_a?Post
    post = options[:year]
    {
      :year  => post.created_at.year,
      :month => post.created_at.month,
      :slug  => post.slug
    }
  else
    {}
  end
end

我处于类似的情况,其中帖子具有语言参数和 slug 参数。写入post_path(@post)将此哈希发送到default_url_options方法:

{:language=>#<Post id: 1, ...>, :controller=>"posts", :action=>"show"}

更新:有一个问题是您无法从该方法覆盖 url 参数。传递给 url 助手的参数优先。因此,您可以执行以下操作:

post_path(:slug => @post)

和:

def default_url_options(options = {})
  if options[:controller] == "posts" && options[:slug].is_a?Post
    {
      :year  => options[:slug].created_at.year,
      :month => options[:slug].created_at.month
    }
  else
    {}
  end
end

如果Post.to_param返回蛞蝓,这将起作用。您只需要将年份和月份添加到哈希中。

于 2010-02-13T22:18:44.237 回答