Rails - link_to,路由和嵌套资源

29

据我理解,嵌套资源在边缘版的Rails中不应该出现

link_to 'User posts', @user.posts
指向
/users/:id/posts

routes.rb 文件包含路由信息

map.resources :users, :has_many => :posts

如果这不是默认行为,那么可以通过其他方式实现吗?

4个回答

63

在Rishav的思路基础上:

link_to "User Posts", [@user, :posts]

以下是我在我的博客中的解释。

在 Rails 早期,你会这样编写路由:

redirect_to :controller => "posts", :action => "show", :id => @post.id

这将会有效地重定向到PostsController内的show动作,并将id参数与@post.id返回的任何值一起传递。通常会得到302响应。

然后,Rails 1.2引入了路由辅助函数,使您可以像这样使用:

redirect_to post_path(@post)

人们欣喜若狂。

这将有效地完成相同的操作。这里的post_path将使用@post对象构建路由,类似于/posts/1,然后redirect_to会向该路由发送302响应,浏览器会跟随它。

后来的版本(我记不得是哪个版本了)允许像这样使用语法:

redirect_to @post

人们第二次欢呼雀跃。

神奇,但并非真正的魔法

任何足够先进的技术都无法与魔法区分开来。

虽然这看起来像魔法,但实际上不是。它所做的事情非常巧妙。像 redirect_to 方法一样,它的兄弟方法 link_toform_for 都使用一个通用方法来构建URL,叫做 url_forurl_for 方法接受许多不同类型的对象,例如字符串、哈希表甚至是模型的实例,就像上面的例子那样。

然后它对这些对象做了很多事情。对于上面的 redirect_to @post 调用,它检查 @post 对象,看到它是 Post 类的对象(我们假设的),并检查是否调用 persisted? 方法在某个数据库中保留了该对象。

“保留”意味着 Ruby 对象在数据库中有一个匹配的记录。在Active Record中,persisted? 方法的实现如下:

def persisted?
  !(new_record? || destroyed?)
end
如果对象不是通过如Model.new的调用创建的,则不会是一个新记录,如果它没有调用destroy方法,它也不会被销毁。如果这两种情况都成立,那么该对象很可能以记录的形式被保存到数据库中。
如果已经保存,则url_for知道可以在哪里找到此对象,而且最有可能在一个名为post_path的方法下找到它。因此,它调用此方法,并传递此对象的to_param值,通常是id
简而言之,实际上它在执行以下操作:
#{@post.class.downcase}_path(@post.to_param)

这就是最终结果:

post_path(1)

当调用该方法时,您将获得此小字符串:

"/posts/1"

太好了!

这被称为多态路由(polymorphic routing)。你可以将一个对象传递给像redirect_tolink_toform_for这样的方法,它会试图找出正确的URL以使用。

form_for的形式

现在,当你编写Rails时,你可能很久以前就像这样使用过form_for

<% form_for @post, :url => { :controller => "posts", :action => "create" } do |f| %>

当然,随着Rails技术的进步,你可以将其简化为:

<% form_for @post, :url => posts_path do |f| %>
因为表单默认使用POST HTTP方法,所以对posts_path的请求将进入PostsControllercreate操作,而不是GET请求下的index操作。
但是,为什么止步于此?为什么不直接这样写呢?
<%= form_for @post do |f| %>

就我个人而言,如果这只是一些简单的东西,我认为没有理由不……使用。form_for方法在底层使用url_for,就像redirect_to一样,确定表单应该去哪里。它知道@post对象是Post类(我们再次假设),并检查对象是否持久化。如果是,则使用post_path(@post)。如果不是,则使用posts_path

form_for方法本身也会检查传入的对象是否持久化,如果是,则默认使用PUT HTTP方法,否则使用POST

因此,这就是为什么form_for足够灵活,以在newedit视图上具有相同的语法。现在越来越普遍的是,人们甚至将整个form_for标记放入单个部分中,并将其包含在newedit页面中。

更复杂的表单

对于传递普通对象的情况,form_for相当简单,但如果您传递了对象数组会发生什么?例如:

<%= form_for [@post, @comment] do |f| %>
url_for方法会将数组中的每个部分分离出来并逐个检查。在这种情况下,@post是一个已经存在于数据库中,id为1的Post实例,@comment是一个尚未存在于数据库中的Comment实例。url_for方法会将这两部分连接起来,形成URL,并返回相应的路由方法。调用该路由方法时,仅需传入已经存在于数据库中的对象即可。
post_comments_path(@post)

调用该方法会导致以下结果:

"/posts/1/comments"

最好的部分?form_for将根据@comment对象是否为持久化对象,自动选择使用POSTPUT。需要记住的一件好事是,form_for始终是针对数组中指定的最后一个对象。其前面的对象只是嵌套它的,没有其他作用。

添加的对象越多,url_for将会越多地执行艰苦的工作并构建路径...尽管我建议您将其保留为仅两个部分。

一个符号表单

既然我们已经介绍了使用包含对象的数组来使用form_for,现在让我们再看看另一个常见用法。包含至少一个符号对象的数组,如下所示:

<%= form_for [:admin, @post, @comment] do |f| %>
url_for方法在这里的作用非常简单。它看到有一个Symbol并将其直接使用。URL的第一部分将只是与该标志相同:admin。此时url_for所知道的URL仅为[:admin]
然后,url_for遍历数组的其余部分。在这种情况下,假设@post@comment都已持久化,并且它们的ID分别为1和2。与之前相同的类。url_for然后将post添加到正在构建的URL中,也将comment添加到其中,从而产生了[:admin, :post, :comment]
然后进行组合,生成admin_post_comment_path方法,并且因为这里都已经持久化了@post@comment,它们被传递进去,形成了这个方法调用:
admin_post_comment_path(@post, @comment)

通常会转换成这个路径:

/admin/posts/1/comments/2
您可以使用redirect_tolink_toform_for方法中的多态路由数组形式。可能还有其他方法我现在没有想起来,通常是Rails中任何通常需要URL的东西都可以。

在任何大于2的Rails版本中,都不需要使用哈希构建URL,那太老派了。

相反,尝试利用您新学到的多态路由知识并充分利用它。


这在最新的Rails中似乎无效。没有接受数组的方法签名。http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to - Chloe
@Chloe 在最新版本的Rails中,我非常确定这仍然有效。在对我进行负面评价之前,请尝试一下。 - Ryan Bigg
@Chloe:API文档有时并不能真实反映现实情况。我已经使用Rails很长时间了,知道[comment.post, comment]可以正确生成你所要求的post_comment_path辅助函数。我甚至写了一篇文章介绍这个问题:http://ryanbigg.com/2012/03/polymorphic-routes/ - Ryan Bigg
1
你的 post_comment_path 方法是错误的。你应该先传递 post 对象,然后再传递 comment。 - Ryan Bigg
是的,你说得对。应该使用link_to 'Destroy Comment', post_comment_path(comment.post, comment)来生成http://127.0.0.1:3000/posts/1/comments/2,其中http://127.0.0.1:3000/posts/1是帖子链接。 - Chloe
当使用_new_时,您还可以在嵌套资源中使用两个符号,例如url_for([:new, @post, :comment]),因为您还没有嵌套对象;这将生成正确的路径! - carlosayam

16

这对于 link_to 'Destroy Comment', post_comment_path(comment)(在 _partial 中)也不起作用。 - Chloe

3

link_to使用url_for,而polymorphic_urlurl_for所使用的方法之一。

polymorphic_url

因此,像其他人所说的那样,您应该使用:

link_to 'User Posts', [@user, :posts]

路径为:

user_posts_path(@user)
^^^^ ^^^^^      ^^^^^
1    2          3
  1. 因为它是一个活动记录,所以@user的类
  2. 转换为字符串,因为它是一个符号
  3. 作为调用参数添加,因为它是一个活动记录

这样就构建了一个好的辅助方法。


1
这是如何在最新的Rails中链接到嵌套资源的方法:

link_to '删除评论', post_comment_path(comment.post, comment)

注意:这是在一个局部视图文件中,所以没有使用 @

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接