Rails:嵌套资源冲突,如何根据被调用的路由确定索引操作的范围
-
06-07-2019 - |
题
想象一下,您有两条已定义的路线:
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]时重复代码>
如果您的视图(具有类别的视图和没有类别的视图)非常相似,则此方法很有效。但是,按类别过滤的列表与没有类别的列表完全不同时会发生什么?这种情况经常发生:您的类别部分提供了一些以类别为中心的小部件,而您的文章部分提供了一些与文章相关的小部件和过滤器。此外,您的文章控制器有一些您可能想要使用的特殊before_filter,但是当文章列表属于某个类别时,您不必使用它们。
在这种情况下,您可能希望将操作分开。
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
管理,它继承了所有控制器过滤器,布局,设置......而未过滤的列表由 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
只有一个嵌套资源,使用基于params的条件来确定它的范围将是最简单的方法。这可能是您的理由。
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
我喜欢考虑独立于路线的行动。无论他们如何到达那里,都要做出合理的决定。