Rails: recursos aninhados conflito, como escopo a ação index dependendo da rota chamada
-
06-07-2019 - |
Pergunta
Imagine que você tem duas rotas definidas:
map.resources articles
map.resources categories, :has_many => :articles
ambos acessíveis por helpers / caminhos
articles_path # /articles
category_articles_path(1) # /category/1/articles
Se você visitar /articles
, ação index
de ArticlesController
é executado.
Se você visitar /category/1/articles
, ação index
de ArticlesController
é executado também.
Então, qual é a melhor abordagem para a seleção condicionalmente apenas os artigos escopo dependendo da rota ligando?
#if coming from the nested resource route
@articles = Articles.find_by_category_id(params[:category_id])
#else
@articles = Articles.all
Solução
Você tem duas opções aqui, dependendo de quanto a sua lógica e sua visão está ligada ao escopo. Deixe-me explicar melhor.
A primeira escolha é determinar o âmbito em seu controlador, como já explicado pelas outras respostas. Eu costumo definir uma variável @scope para obter alguns benefícios adicionais em meus modelos.
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
A razão para a variável @scope é que você pode precisar de saber o alcance do seu pedido fora da única ação. Vamos supor que você deseja exibir o número de registros na sua vista. Você precisa saber se você está filtrando por categoria ou não. Neste caso, você simplesmente precisa @scope.count
chamada ou @scope.my_named_scope.count
em vez de repetir cada vez que o controlo sobre params[:category_id]
.
Esta abordagem funciona bem se os seus pontos de vista, aquele com categoria e um sem categoria, são bastante semelhantes. Mas o que acontece quando a lista filtrada por categoria é completamente diferente em comparação com a outra sem uma categoria? Isto acontece muitas vezes: sua seção categoria fornece alguns widgets focado da categoria, enquanto a sua seção do artigo Alguns widgets artigo-relacionados e de filtro. Além disso, o artigo controlador tem alguns before_filters especiais que você pode querer usar, mas você não tem que usá-los quando o artigo listando pertence a uma categoria.
Neste caso, você pode querer separar as acções.
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
Agora, o anúncio filtrada por categoria é gerido pela CategoriesController
e herda todos os filtros controlador, layouts, configurações ... enquanto a lista não filtrada é gerido pela ArticlesController
.
Esta é normalmente a minha escolha favorita porque com uma ação adicional que você não tem que encher seus pontos de vista e controladores com toneladas de verificações condicionais.
Outras dicas
muitas vezes eu gosto de separar essas ações. Quando as ações resultantes são muito semelhantes você pode separar os escopos dentro do controlador fácil por ver se params. [: Category_id] está presente etc (ver resposta @SimoneCarletti)
Normalmente separando ações no controlador usando rotas personalizadas dá-lhe mais flexibilidade e resultados claros. Código a seguir resulta em nomes normais rota auxiliares, mas as rotas são direcionados para ações específicas no controlador.
Em 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
Em seguida, você pode ter em 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
Tendo apenas um único recurso aninhado, usando uma condicional com base nos parâmetros para determinar a sua extensão seria a abordagem mais fácil. Este é provavelmente o caminho a percorrer no seu caso.
if params[:category_id]
@articles = Category.find(params[:category_id]).articles
else
@articles = Article.all
end
No entanto, dependendo do que outros recursos aninhados que você tem para o modelo, furando com essa abordagem pode ficar muito tedioso. Nesse caso, usando um plugin como resource_controller ou make_resourceful vai fazer isso muito mais simples.
class ArticlesController < ResourceController::Base
belongs_to :category
end
Isso vai realmente fazer tudo o que você esperaria. Dá-lhe todas as suas ações RESTful padrão e será automaticamente configurar o escopo para /categories/1/articles
.
if params[:category_id].blank?
# all
else
# find by category_id
end
Eu gosto de considerar o independente ação da rota. Não importa como eles chegam lá, tomar uma decisão razoável sobre o que fazer.