Rails: conflit de ressources imbriquées, comment définir l'action d'index en fonction de la route appelée

StackOverflow https://stackoverflow.com/questions/1811163

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
Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top