Rails: Nested resources conflict, how to scope the index action depending on the called route

前端 未结 4 1287
走了就别回头了
走了就别回头了 2021-02-13 19:50

Imagine you have two defined routes:

map.resources articles
map.resources categories, :has_many => :articles

both accessible by helpers/path

相关标签:
4条回答
  • 2021-02-13 20:28
    if params[:category_id].blank?
      # all
    else
      # find by category_id
    end
    

    I like to consider the action independent from the route. No matter how they get there, make a reasonable decision as to what to do.

    0 讨论(0)
  • 2021-02-13 20:30

    You have two choices here, depending on how much your logic and your view is tied to the scope. Let me explain further.

    The first choice is to determine the scope within your controller, as already explained by the other responses. I usually set a @scope variable to get some additional benefits in my templates.

    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
    

    The reason for the @scope variable is that you might need to know the scope of your request outside the single action. Let's assume you want to display the number of records in your view. You need to know whether you are filtering by category or not. In this case, you simply need to call @scope.count or @scope.my_named_scope.count instead of repeating each time the check on params[:category_id].

    This approach works well if your views, the one with category and the one without category, are quite similar. But what happens when the listing filtered by category is completely different compared to the one without a category? This happens quite often: your category section provides some category-focused widgets while your article section some article-related widgets and filter. Also, your Article controller has some special before_filters you might want to use, but you don't have to use them when the article listing belongs to a category.

    In this case, you might want to separate the 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
    

    Now the listing filtered by category is managed by the CategoriesController and it inherits all the controller filters, layouts, settings... while the unfiltered listing is managed by the ArticlesController.

    This is usually my favorite choice because with an additional action you don't have to clutter your views and controllers with tons of conditional checks.

    0 讨论(0)
  • Having only a single nested resource, using a conditional based on the params to determine it's scope would be the easiest approach. This is likely the way to go in your case.

    if params[:category_id]
      @articles = Category.find(params[:category_id]).articles
    else
      @articles = Article.all
    end
    

    However, depending on what other nested resources you have for the model, sticking with this approach can get quite tedious. In which case, using a plugin like resource_controller or make_resourceful will make this much simpler.

    class ArticlesController < ResourceController::Base
      belongs_to :category
    end
    

    This will actually do everything you'd expect. It gives you all your standard RESTful actions and will automatically setup the scope for /categories/1/articles.

    0 讨论(0)
  • 2021-02-13 20:34

    I often like to separate those actions. When the resulting actions are very similar you can separate the scopes inside the controller easy by seeing if params[:category_id] is present etc (see @SimoneCarletti answer).

    Normally separating actions in the controller by using custom routes gives you most flexibility and clear results. Following code results in normal route helper names but the routes are directed to specific actions in controller.

    In 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
    

    Then you can have in 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
    
    0 讨论(0)
提交回复
热议问题