Rails: conflit de ressources imbriquées, comment définir l'action d'index en fonction de la route appelée
-
06-07-2019 - |
Question
Imaginez que vous ayez deux itinéraires définis:
map.resources articles
map.resources categories, :has_many => :articles
tous deux accessibles par les helpers / path
articles_path # /articles
category_articles_path(1) # /category/1/articles
si vous visitez / articles
, l'action index
de ArticlesController
est exécutée.
si vous visitez / category / 1 / articles
, l'action index
de ArticlesController
est également exécutée.
Alors, quelle est la meilleure approche pour ne sélectionner sous condition que les articles délimités en fonction de la route d'appel?
#if coming from the nested resource route
@articles = Articles.find_by_category_id(params[:category_id])
#else
@articles = Articles.all
La solution
Vous avez le choix entre deux options, en fonction du degré de votre logique et de votre point de vue. Laissez-moi vous expliquer plus loin.
Le premier choix est de déterminer la portée de votre contrôleur, comme expliqué précédemment par les autres réponses. Je règle généralement une variable @scope pour obtenir des avantages supplémentaires dans mes modèles.
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
La raison de la variable @scope est qu'il peut être nécessaire de connaître l'étendue de votre demande en dehors de l'action unique. Supposons que vous souhaitiez afficher le nombre d'enregistrements dans votre vue. Vous devez savoir si vous filtrez par catégorie ou non. Dans ce cas, vous devez simplement appeler @ scope.count
ou @ scope.my_named_scope.count
au lieu de répéter à chaque fois la vérification de params [: category_id]
.
Cette approche fonctionne bien si vos points de vue, celui avec catégorie et celui sans catégorie, sont assez similaires. Mais que se passe-t-il lorsque la liste filtrée par catégorie est complètement différente de celle sans catégorie? Cela se produit assez souvent: votre section category fournit des widgets axés sur les catégories, tandis que votre section article contient des widgets et des filtres liés à l'article. De plus, votre contrôleur Article dispose d’avant_filters spéciaux que vous pouvez utiliser, mais vous n’avez pas à les utiliser lorsque la liste d’articles appartient à une catégorie.
Dans ce cas, vous pouvez séparer les actions.
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
La liste filtrée par catégorie est maintenant gérée par le CategoriesController
et hérite de tous les filtres, présentations, paramètres, etc. du contrôleur, tandis que la liste non filtrée est gérée par le ArticlesController
.
C’est généralement mon choix préféré, car avec une action supplémentaire, vous n’aurez pas à encombrer vos vues et vos contrôleurs de tonnes de vérifications conditionnelles.
Autres conseils
J'aime souvent séparer ces actions. Lorsque les actions résultantes sont très similaires, vous pouvez facilement séparer les étendues à l’intérieur du contrôleur en vérifiant si params [: category_id] & nbsp; est présent, etc. (voir la réponse @SimoneCarletti).
Normalement, la séparation des actions dans le contrôleur à l'aide de routes personnalisées vous offre une flexibilité maximale et des résultats clairs. Le code suivant donne des noms d’assistant d’itinéraire normaux, mais les itinéraires sont dirigés vers des actions spécifiques dans le contrôleur.
Dans 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
Vous pouvez ensuite avoir dans 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
N'avoir qu'une seule ressource imbriquée, utiliser une conditionnelle basée sur les paramètres pour déterminer sa portée serait l'approche la plus simple. C’est probablement la voie à suivre dans votre cas.
if params[:category_id]
@articles = Category.find(params[:category_id]).articles
else
@articles = Article.all
end
Toutefois, en fonction des autres ressources imbriquées dont vous disposez pour le modèle, le maintien de cette approche peut s'avérer fastidieux. Dans ce cas, utilisez un plugin tel que resource_controller ou make_resourceful simplifiera beaucoup les choses.
class ArticlesController < ResourceController::Base
belongs_to :category
end
Cela fera tout ce que vous attendez. Il vous donne toutes vos actions RESTful standard et configurera automatiquement la portée de / categories / 1 / articles
.
if params[:category_id].blank?
# all
else
# find by category_id
end
J'aime considérer l'action comme indépendante de la route. Peu importe la façon dont ils y parviennent, prenez une décision raisonnable quant à la marche à suivre.