Rails: конфликт вложенных ресурсов, как настроить действие индекса в зависимости от вызываемого маршрута
-
06-07-2019 - |
Вопрос
Представьте, что у вас есть два определенных маршрута:
map.resources articles
map.resources categories, :has_many => :articles
оба доступны помощникам / путям
articles_path # /articles
category_articles_path(1) # /category/1/articles
если вы посещаете / article
, выполняется действие index
из ArticlesController
.
если вы посещаете / category / 1 / article
, index
из ArticlesController
также выполняется.
Итак, каков наилучший подход для условного выбора только статей с определенными областями в зависимости от маршрута вызова?
#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] код>.
Этот подход хорошо работает, если ваши взгляды, с категорией и без категории, очень похожи. Но что происходит, когда список, отфильтрованный по категории, полностью отличается от списка без категории? Это происходит довольно часто: в разделе вашей категории есть некоторые виджеты, ориентированные на категории, а в разделе статей - виджеты и фильтры, связанные со статьей. Кроме того, в вашем контроллере Article есть несколько специальных 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
, и он наследует все фильтры, макеты, настройки контроллера, а нефильтрованный список управляется ArticlesController
. р>
Обычно это мой любимый выбор, потому что с дополнительным действием вам не нужно загромождать свои представления и контроллеры множеством условных проверок.
Другие советы
Мне часто нравится отделять эти действия. Когда получающиеся действия очень похожи, вы можете легко разделить области внутри контроллера, посмотрев, есть ли params [: category_id] & nbsp; и т. Д. (См. Ответ @SimoneCarletti).
Обычно разделение действий в контроллере с помощью пользовательских маршрутов обеспечивает большую гибкость и четкие результаты. Следующий код приводит к обычным именам помощников маршрутов, но маршруты направлены на определенные действия в контроллере.
В rout.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 / article
.
if params[:category_id].blank?
# all
else
# find by category_id
end
Мне нравится считать действие независимым от маршрута. Независимо от того, как они туда доберутся, примите разумное решение относительно того, что делать.