Rails 4 [最佳实践] 嵌套资源和浅层:true

Posted

技术标签:

【中文标题】Rails 4 [最佳实践] 嵌套资源和浅层:true【英文标题】:Rails 4 [Best practices] Nested resources and shallow: true 【发布时间】:2014-03-19 01:53:02 【问题描述】:

以下帖子基于 Rails 4。

我目前正在寻找关于多个嵌套资源(超过 1 个)的最佳实践,以及选项 shallow:true。

最初在我的路线中,有这样的:

resources :projects do 
  resources :collections
end

相关的路线是:

    project_collections GET    /projects/:project_id/collections(.:format)          collections#index
                        POST   /projects/:project_id/collections(.:format)          collections#create
 new_project_collection GET    /projects/:project_id/collections/new(.:format)      collections#new
edit_project_collection GET    /projects/:project_id/collections/:id/edit(.:format) collections#edit
     project_collection GET    /projects/:project_id/collections/:id(.:format)      collections#show
                        PATCH  /projects/:project_id/collections/:id(.:format)      collections#update
                        PUT    /projects/:project_id/collections/:id(.:format)      collections#update
                        DELETE /projects/:project_id/collections/:id(.:format)      collections#destroy
               projects GET    /projects(.:format)                                  projects#index
                        POST   /projects(.:format)                                  projects#create
            new_project GET    /projects/new(.:format)                              projects#new
           edit_project GET    /projects/:id/edit(.:format)                         projects#edit
                project GET    /projects/:id(.:format)                              projects#show
                        PATCH  /projects/:id(.:format)                              projects#update
                        PUT    /projects/:id(.:format)                              projects#update
                        DELETE /projects/:id(.:format)                              projects#destroy

我在documentation 中看到关于嵌套资源的限制:

资源的嵌套深度不得超过 1 级。

好的。然后,就像文档说的那样,我将在我的路线中使用“浅”。

shallow do
  resources :projects do 
    resources :collections
  end
end

相关的路线是:

   project_collections GET    /projects/:project_id/collections(.:format)     collections#index
                       POST   /projects/:project_id/collections(.:format)     collections#create
new_project_collection GET    /projects/:project_id/collections/new(.:format) collections#new
       edit_collection GET    /collections/:id/edit(.:format)                 collections#edit
            collection GET    /collections/:id(.:format)                      collections#show
                       PATCH  /collections/:id(.:format)                      collections#update
                       PUT    /collections/:id(.:format)                      collections#update
                       DELETE /collections/:id(.:format)                      collections#destroy
              projects GET    /projects(.:format)                             projects#index
                       POST   /projects(.:format)                             projects#create
           new_project GET    /projects/new(.:format)                         projects#new
          edit_project GET    /projects/:id/edit(.:format)                    projects#edit
               project GET    /projects/:id(.:format)                         projects#show
                       PATCH  /projects/:id(.:format)                         projects#update
                       PUT    /projects/:id(.:format)                         projects#update
                       DELETE /projects/:id(.:format)                         projects#destroy

我看到的主要区别是集合的“展示”,这个特定的:

collection GET    /collections/:id(.:format)                      collections#show

所以如果我是正确的,集合的显示操作的链接是:

<%= link_to 'Show", collection_path(collection)%>

并且应该返回如下内容:“http://example.com/collections/1”

但是!两件事:

这不起作用。我得到的是“http://example.com/projects/1”。 即使它工作正常,但实际上 IMO 很糟糕,因为我松散了 REST 基本原则,即“集合是项目的子项,那么 url 应该是“localhost/project/1/collections/1”

如果我失去了休息动作的巨大优势,我不明白浅薄的好处是什么。放开“表演”动作又有什么好处呢?我已经将此发布到 SO,但我得到的唯一评论是“这很正常”。我不认为这是从其余 API 中“删除”操作的正常行为?

是的,帮助者使用 shallow 可能很方便,但对其他人来说一点也不方便,你失去了“一个集合嵌套到一个项目,所以这反映在 URL 中”的所有兴趣.

我不知道是否还有其他方法可以做到这一点,shallow 确实允许帮助器具有更大的灵活性,但它符合 REST 是错误的。那么,有没有机会让“助手”工作(使用“nested3_path(collection)”而不是“nested1_nested2_nested3([nested1.nested2.nested3,nested1.nested2,nested1])”,并保持“网址部分“nested1/123/nested2/456/nested3/789”?

【问题讨论】:

你试过重启服务器让路由生效吗?根据文档resources :posts, shallow: true do resources :comments end 将产生resources :posts do resources :comments, except: [:show, :edit, :update, :destroy] end resources :comments, only: [:show, :edit, :update, :destroy] 这听起来像你在做 确实需要重启服务器才能让路由生效。 【参考方案1】:

From this answer 似乎浅层路线在某种程度上违反了 IMO 的 Rails 惯例。

我认为您不需要显示路径的显式路径助手。 link_to 助手应该能够从对象的 to_param 方法中推断出它。

#your helper becomes 
link_to "show", collection

如果您按照上述方式使用助手,您可能还需要将父资源的嵌套 ID 传递给助手。

link_to "show", collection_path([project, collection])

【讨论】:

【参考方案2】:

虽然如果您仅对某些型号需要此功能会使事情复杂化,但最好查看Inherited Resources (IR)。它支持资源嵌套、多态所属,并能自动生成你要找的短路径和url辅助方法。您不再听说 IR 的原因是它的原作者和其他一些开发人员已经放弃了它,因为尝试扩展控制器时会出现复杂情况。但是,它仍然有一个社区,我们已经尝试对其进行更多扩展,并更多地关注Irie 的控制器扩展的易用性。

Rails 中的“最佳实践”取决于您与谁交谈。

Rails 传统上主要针对(非嵌套)资源的基本 CRUD。是的,它允许检索和更新嵌套资源,但假设这种情况不会经常发生。

然而,Rails 社区中出现的是ActiveModel::Serializers/json-api 方法。在这种情况下,通常不会发生超过一级的资源嵌套,嵌套资源是链接列表或子资源的侧载小版本,然后您可以在该资源上查询以获取更多数据。 Ember/Ember Data也接受了这一点。

还有roar 和许多其他项目旨在实现更接近他们对接近 Roy Fielding 最初的 REST 愿景的理解的东西。

我认为这取决于您的设计是什么以及您需要什么。如果效率是一个目标,那么额外的时间来明确和嵌套更多可能会得到回报。例如,我们目前使用AngularJS 和Irie。但是,各有各的。

如最后一点,请务必通过在查询中使用 includes(...)(或类似名称)来避免 n+1 查找,否则所有嵌套可能会影响性能。

【讨论】:

【参考方案3】:

因为Collection 有一个id,所以除了indexcreate 操作之外,在项目下嵌套路由是多余的。

有一个关于 URL 的规则,其中应该只有一个 URL 来获取(200)给定的资源,如果有其他 URL,你应该重定向到它。所以你可能有一个路由/projects/:id/collections/:collection_id 重定向到/collections/:collection_id

在您的情况下,集合与项目相关联,但这并不一定适用于所有关系。拥有:collection_id 后,您无需引用Project 的上下文即可访问它。

【讨论】:

是的!谢谢!我一直在谷歌搜索寻找浅层路线目的的基本解释!文档中的假设是:您已经知道这一点。从这里的其他一些答案来看,很多其他人也不知道!您的意思是,例如,如果您通过 :id 编辑评论,则将其嵌套在项目下是没有意义的,因为评论 :id 独立于项目 id。浅层只是清理路线以反映这一点。【参考方案4】:

我不相信 Rails 提供任何内置方式让 URL 使用完整的层次结构(例如 /projects/1/collections/2),但也有快捷帮助器(例如 collection_path 而不是 project_collection_path)。

如果您真的想这样做,您可以推出自己的自定义助手,如下所示:

def collection_path(collection)
  # every collection record should have a reference to its parent project
  project_collection_path(collection.project, collection)
end

但是手动为每个资源执行此操作会非常麻烦。


我认为使用shallow 路由背后的想法最好通过文档来总结:

避免深度嵌套的一种方法(如上所述)是生成 在父级范围内的集合动作,以便了解 的层次结构,但不嵌套成员操作。换一种说法, 只用最少的信息构建路线 唯一标识资源

来源:http://guides.rubyonrails.org/routing.html#shallow-nesting

因此,虽然这可能不符合 REST(如您所说),但您不会丢失任何信息,因为每个资源都可以被唯一标识,并且假设您的关联设置正确,您可以返回层次结构。

【讨论】:

虽然它没有内置在 Rails 中,但 Inherited Resources (IR) 有嵌套和帮助器可能会有所帮助。如果只是在少数情况下使用 IR,那么明确而不使用 IR 可能会更好,并且它还可以限制或复杂化控制器实现/调试,具体取决于您的需要。【参考方案5】:

级别

您必须在嵌套资源中仅使用 1 级的概念仅适用于系统设计:

对应的路由助手是 publisher_magazine_photo_url, 要求您在所有三个级别上指定对象。的确,这 情况令人困惑,以至于 Jamis Buck 的一篇热门文章 提出了一个好的 Rails 设计的经验法则:

我相信 Rails 仍然可以处理多个级别,尽管从可用性的角度不建议这样做


虽然之前见过sallow用过,但我自己没用过

从documentation 看来,它似乎有一个相当模糊的目的(我实际上不知道它为什么在那里)。问题是您没有公开将post_id 参数传递给您的控制器,让您在没有重要参数的情况下加载collection

我推测(这只是推测),目的是在幕后传递你需要的参数,所以你只剩下一条公共的“浅”路线:

#config/routes.rb
resources :projects do 
   resources :collections, shallow: true
end

我想你会得到一个这样的 URL 助手:

collection_path(project.id, collection.id)

这会显示为domain.com/collection/2

【讨论】:

是的,您在路线上是正确的,但这对我的问题没有多大帮助 =/

以上是关于Rails 4 [最佳实践] 嵌套资源和浅层:true的主要内容,如果未能解决你的问题,请参考以下文章

Rails URL 的嵌套路由和参数(最佳实践)

为浅层路由寻求最佳 RestKit/CoreData 映射和 JSON 结构的建议

如何重用 Rails 中嵌套控制器的渲染动作?

Rails 4+ 最佳实践:删除父项同时保留子项

不使用支持库的 Android 4.0、4.1 (<4.2) 中嵌套片段的最佳实践

将用户与 Rails 中的嵌套模型相关联