Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Imagine you have two defined routes:

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

both accessible by helpers/paths

articles_path # /articles
category_articles_path(1) # /category/1/articles

if you visit /articles, index action from ArticlesController is executed.

if you visit /category/1/articles, index action from ArticlesController is executed too.

So, what is the best approach for conditionally selecting only the scoped articles depending on the calling route?

#if coming from the nested resource route
@articles = Articles.find_by_category_id(params[:category_id])
#else
@articles = Articles.all
like image 554
knoopx Avatar asked Nov 28 '09 00:11

knoopx


1 Answers

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.

like image 108
Simone Carletti Avatar answered Sep 18 '22 16:09

Simone Carletti