Подтвердить что ты не робот

Рельсы: конфликт вложенных ресурсов, как охватить действие индекса в зависимости от вызываемого маршрута

Представьте, что у вас есть два определенных маршрута:

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
4b9b3361

Ответ 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

Мне нравится рассматривать действие, независимое от маршрута. Независимо от того, как они туда попадут, сделайте разумное решение относительно того, что делать.