Рельсы: конфликт вложенных ресурсов, как охватить действие индекса в зависимости от вызываемого маршрута
Представьте, что у вас есть два определенных маршрута:
map.resources articles
map.resources categories, :has_many => :articles
оба доступны с помощью помощников/путей
articles_path # /articles
category_articles_path(1) # /category/1/articles
если вы посетили /articles
, index
выполнено действие ArticlesController
.
если вы посетили /category/1/articles
, index
действие из ArticlesController
выполнено тоже.
Итак, каков наилучший подход для условного выбора только статей с областью действия в зависимости от вызывающего маршрута?
#if coming from the nested resource route
@articles = Articles.find_by_category_id(params[:category_id])
#else
@articles = Articles.all
Ответы
Ответ 1
Здесь у вас есть два варианта, в зависимости от того, насколько ваша логика и ваш взгляд привязаны к сфере.
Позвольте мне объяснить далее.
Первый выбор - определить область действия вашего контроллера, как уже объяснялось другими ответами. Обычно я устанавливаю переменную @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_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
.
Это, как правило, мой любимый выбор, потому что при дополнительном действии вам не нужно загромождать свои взгляды и контроллеры множеством условных проверок.
Ответ 2
Мне часто нравится разделять эти действия. Когда результирующие действия очень похожи, вы можете легко разделить области внутри контроллера, увидев, имеются ли параметры [: 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
Ответ 3
Имея только один вложенный ресурс, использование условного выражения, основанного на параметрах для определения его области, будет самым простым. Это, скорее всего, способ пойти в вашем случае.
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
.
Ответ 4
if params[:category_id].blank?
# all
else
# find by category_id
end
Мне нравится рассматривать действие, независимое от маршрута. Независимо от того, как они туда попадут, сделайте разумное решение относительно того, что делать.