Rails 中漂亮(过时)的 RESTful URL
Posted
技术标签:
【中文标题】Rails 中漂亮(过时)的 RESTful URL【英文标题】:Pretty (dated) RESTful URLs in Rails 【发布时间】:2011-01-16 12:44:05 【问题描述】:我希望我的网站具有如下所示的 URL:
example.com/2010/02/my-first-post
我的Post
模型带有slug
字段('my-first-post')和published_on
字段(我们将从中扣除网址中的年份和月份部分)。
我希望我的 Post
模型是 RESTful 的,所以像 url_for(@post)
这样的东西应该可以正常工作,即:它应该生成上述 url。
有没有办法做到这一点?我知道您需要覆盖 to_param
并设置 map.resources :posts
并设置 :requirements
选项,但我无法让它全部工作。
我几乎完成了,我已经完成了 90%。使用resource_hacks plugin 我可以做到这一点:
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 键)。我对如何破解它一无所知。
【问题讨论】:
【参考方案1】:Rails 3.x 和 Rails 2.x 的漂亮 URL,不需要任何外部插件,但不幸的是,有点 hack。
routes.rb
map.resources :posts, :except => [:show]
map.post '/:year/:month/:slug', :controller => :posts, :action => :show, :year => /\d4/, :month => /\d2/, :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 => /\d4/, :month => /\d2/, :slug => /[a-z0-9\-]+/
是否有任何徽章可以回答您自己的问题? ;)
顺便说一句:routing_test 文件是了解 Rails 路由可以做什么的好地方。
更新:使用default_url_options
是死胡同。仅当控制器中定义了@post
变量时,发布的解决方案才有效。例如,如果存在带有帖子数组的 @posts
变量,我们就不走运了(因为 default_url_options
无权访问视图变量,例如 @posts.each do |p|
中的 p
。
所以这仍然是一个悬而未决的问题。有人帮忙吗?
【讨论】:
徽章?有自学者(如果您回答了自己的问题并且获得了 3 次赞成),您可以接受自己的答案。 那只是个玩笑;)还有一个更大的问题。当我处理其他控制器中的帖子时,我的解决方案会中断。具体来说,当没有 @post 存在时(虽然那里可能有 @posts 变量)。【参考方案2】:它仍然是一个 hack,但以下工作:
在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.
这是一些nice soul 对我的一个类似问题url_for of a custom RESTful resource (composite key; not just id) 的回答。
【讨论】:
【参考方案3】:Ryan Bates 在他的屏幕截图中谈到了“如何添加自定义路线、使某些参数成为可选参数以及为其他参数添加要求”。 http://railscasts.com/episodes/70-custom-routes
【讨论】:
通用路由(Ryan Bates 谈到的 map.connect)、named_routes 和 RESTful 路由是三种不同的东西。我可以使用通用路由和命名路由轻松获得任何我想要的 URL。我想拥有使用 RESTful 路由的漂亮 url。【参考方案4】:这可能会有所帮助。您可以在 ApplicationController
中定义一个 default_url_options
方法,该方法接收传递给 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
返回了 slug,这将起作用。您只需将年份和月份添加到哈希中。
【讨论】:
有效!非常聪明,虽然也很hackish。现在有没有更清洁的方法来解决这个问题? 这很有趣,因为它对我不起作用。我只能看到 post 实例已通过。也许是因为你使用的插件。只有当我将帖子作为:slug => @post
传递时,我才能让它工作。
现在我明白了:如果你直接在 options
哈希中设置一个值,就像我在第一次修订中错误地做的那样,例如options[:year] = 2009
然后设置正确(因为哈希是通过引用传递的)。所以你实际上可以覆盖给定的键。
是的,它需要一个插件来确保路由正确(这是一个先决条件)。检查这个:gist.github.com/303710【参考方案5】:
您可以减轻压力并使用friendly_id。太棒了,可以完成这项工作,您可以查看 Ryan Bates 的 a screencast 以开始使用。
【讨论】:
我认为你错过了这个问题的重点。这不是关于 slug 生成(这本身是微不足道的),而是关于友好的日期 url(年/月部分在这里是困难的)。以上是关于Rails 中漂亮(过时)的 RESTful URL的主要内容,如果未能解决你的问题,请参考以下文章