Rails:嵌套资源冲突,如何根据被调用的路由确定索引操作的范围

想象一下,你有两个定义的路线:

map.resources articles map.resources categories, :has_many => :articles 

都可以通过助手/路径访问

 articles_path # /articles category_articles_path(1) # /category/1/articles 

如果你访问/articles ,则执行ArticlesController index操作。

如果你访问/category/1/articles ,那么也会执行ArticlesController index操作。

那么,根据呼叫路由有条件地仅选择范围文章的最佳方法是什么?

 #if coming from the nested resource route @articles = Articles.find_by_category_id(params[:category_id]) #else @articles = Articles.all 

这里有两个选择,具体取决于您的逻辑和视图与范围的关联程度。 让我进一步解释。

第一种选择是确定控制器内的范围,正如其他响应所解释的那样。 我通常设置@scope变量以在模板中获得一些额外的好处。

 class Articles before_filter :determine_scope def index @articles = @scope.all # ... end protected def determine_scope @scope = if params[:category_id] Category.find(params[:category_id]).articles else Article end end end 

@scope变量的原因是您可能需要在单个操作之外知道请求的范围。 假设您要显示视图中的记录数。 您需要知道是否按类别过滤。 在这种情况下,您只需要调用@scope.count@scope.my_named_scope.count而不是每次检查params[:category_id]时重复。

如果您的视图(具有类别的视图和没有类别的视图)非常相似,则此方法很有效。 但是,当按类别过滤的列表与没有类别的列表完全不同时会发生什么? 这种情况经常发生:您的类别部分提供了一些以类别为重点的小部件,而您的文章部分提供了一些与文章相关的小部件和filter。 此外,您的文章控制器有一些您可能想要使用的特殊before_filters,但是当文章列表属于某个类别时,您不必使用它们。

在这种情况下,您可能希望分离操作。

 map.resources articles map.resources categories, :collection => { :articles => :get } articles_path # /articles and ArticlesController#index category_articles_path(1) # /category/1/articles and CategoriesController#articles 

现在,按类别过滤的列表由CategoriesController管理,它inheritance了所有控制器filter,布局,设置……而未过滤的列表由ArticlesController管理。

这通常是我最喜欢的选择,因为通过额外的操作,您不必通过大量的条件检查来混淆您的视图和控制器。

我经常喜欢将这些行为分开。 当结果动作非常相似时,您可以通过查看params [:category_id]是否存在等来轻松分离控制器内的范围(请参阅@SimoneCarletti答案)。

通常使用自定义路径分离控制器中的操作可为您提供最大的灵活性和清晰的结果。 以下代码生成正常的路由帮助程序名称,但路由指向控制器中的特定操作。

routes.rb中

 resources categories do resources articles, :except => [:index] do get :index, :on => :collection, :action => 'index_articles' end end resources articles, :except => [:index] do get :index, :on => :collection, :action => 'index_all' end 

然后你可以在ArticlesController.rb中

 def index_all @articles = @articles = Articles.all render :index # or something else end def index_categories @articles = Articles.find_by_category_id(params[:category_id]) render :index # or something else end 

只有一个嵌套资源,使用基于参数的条件来确定它的范围将是最简单的方法。 这可能是您的理由。

 if params[:category_id] @articles = Category.find(params[:category_id]).articles else @articles = Article.all end 

但是,根据您对模型的其他嵌套资源,坚持使用这种方法可能会非常繁琐。 在这种情况下,使用像resource_controller或make_resourceful这样的插件会使这更加简单。

 class ArticlesController < ResourceController::Base belongs_to :category end 

这实际上会做你期望的一切。 它为您提供了所有标准的RESTful操作,并将自动设置/categories/1/articles

 if params[:category_id].blank? # all else # find by category_id end 

我喜欢考虑独立于路线的行动。 无论他们如何到达那里,都要做出合理的决定。