Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

To nest or not to nest a resource

I have been playing around with Rails again for the last couple of days.

I'm unsure if I should nest a resource or not, and how to determine it.

Let's pretend I have an application where users can sign up and make a todo list. They can choose to make these lists public or private.

Where I'm running into trouble is if I should nest the lists under the user resource:

If I do that, I end up with something like:

/users/:id/lists /users/:id/lists/:list_id

But what if I want to say, have a view with all public lists under /lists? That would interfere with the restful-ness of the routes, no?

I could not nest the lists and have separate routes for users and lists, but then I run into trouble again, in my mind at least.

/users/:id # some profile page /lists # should this link to all lists or all the current user's lists?

But then, if I want to show a specific user's lists, I don't know what my route would be.

Is there a way to combine this somehow? Nest the lists under the user resource, but still have the /lists lists_path? Or would that just be a custom named route?

I'm interested in how people who are more proficient than me in Rails would tackle this.

like image 498
Joris Ooms Avatar asked Nov 12 '22 16:11

Joris Ooms


1 Answers

You can have both a nested and non-nested lists.

resources :lists will create all of the restful routes.

resources :users do
  resources :lists
end

will then create all of the nested routes and associated helpers. I wouldn't use nested routes in this scenario, because your controller is going to be a shitshow.

Nested routes (let's say users/7/lists) by default routes to ListsController#index, but you also have /lists that is going to ListsController#index. So you can add some logic to your controller:

ListsController < ApplicationController
  def index
    if params[:user_id].present?
      # I'm in a user_list
    else
      # I'm in a public list
    end
  end
end

Ugh.

Okay how about a before_filter?

ListsController < ApplicationController
  before_filter :unjankify_nested_routes

  private      

  def unjankify_nested_routes
    if params[:user_id].present?
      # I'm in a user_list
      render "users_#{@action}"
    else
      # I'm in a public list
    end
  end
end

Now we can basically scope the controller methods by saying users_show, users_index etc. But what is that @action variable? We need to capture that.

class ApplicationController     
  def process_action(name, *args)
    @action_name = name
    super rescue false #no super in tests
  end
end

We just overrode an internal Rails method to capture the action name. That's fun, but kind of seems like we're doing something wrong.

The Right Way (tm)

There is no 'right way' but scopes are a good option

scope 'privates' do
  resources :lists
end

Now we can have a Privates::ListsController in app/controllers/privates/lists_controller. you can add the user_id in the query params, or nest routes under the scope:

scope 'privates' do
  resources :users do  
    resources :lists
  end
end
like image 192
Chris Avatar answered Nov 15 '22 04:11

Chris