I've got three (relevant) models, specified like this:

class User < ActiveRecord::Base
  has_many :posts
  has_many :comments
  has_many :comments_received, :through => :posts, :source => :comments

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :comments

class Comment < ActiveRecord::Base
  belongs_to :user
  belongs_to :post

I'd like to be able to reference all the comments_received for a user with a route - let's say it's for batch approval of comments on all posts. (note that you can also get comments made by the user, but users can't comment on their own posts, so the comments through a post are different and mutually exclusive). Logically, this should work with:

map.resources :users, :has_many => [:posts, :comments, :comments_received]

This should give me routes of


The first two work, the last one doesn't. I've tried it without the _ in comments_received to no avail. I'm looking to get a URL like

I've also tried nesting it, but maybe I'm doing that wrong. In that case, I'd think the map would be:

map.resources :users do |user|
  user.resources :comments
  user.resources :posts, :has_many => :comments

and then the url might be:

Maybe this is the right way to do it, but I have the syntax wrong?

Am I thinking about this the wrong way? It seems reasonable to me that I should be able to get a page of all the comments added to all of a user's posts.

Thanks for your help!


I have to confess, I'm a little confused by the variable names, but first off, I'm surprised your has_many :through works at all the way it's defined. Do the models behave as you expect, setting aside the routes for a second?

Second, and this is where the variable names really come into play, the routes have some dependencies on pluralization, so your foos bars and bazs might either be a cause of the problem, or might be hiding the problem. In any event, you can definitely write something like this:

map.resources :users do |user|
  user.resources :awards
  user.resources :contest_entries do |contest_entry|
    contest_entry.resources :awards

which I believe would give you:

user_path, user_awards_path, user_contest_entry_path, and user_contest_entry_awards_path.

I'm not sure if this really answers your question, and it might help to get a clearer picture of what's going on here if you changed foo, bar, and baz to something closer to the real situation.

Cameron Price
OK, it's not exactly what I'm doing, but it's close enough to call Foo a User, Bar a Post, Baz a Comment, and xxx is "received." The goal is to get a route that shows all comments on all posts by a given user, noting that users cannot comment on their own posts.

A quick-n-dirty solution would be to add a custom method (e.g. getusercomments) to your users-controller that would return all the comments:

def getusercomments
@user = User.find(params[:id])
@comments = @user.posts.comments

Then add this method to your users-route:

map.resources :users, :member => { :getusercomments => :get }

Afterwards you should be able to call the following to get all comments of a user:
+2  A: 

Although the syntax to deinfe resource and model relationship is similar, you shouldn't be fooled into thinking that a resource maps to a model. Read what David Black has to say.

The problem you're having is with the routes you're generating. Using the nested syntax like so:

map.resources :users do |user|
  user.resources :posts
  user.resources :comments
  user.resources :comments_received

And then running 'rake routes', gives me (amongst loads of other stuff!):

                       users GET /users                              {:action=>"index", :controller=>"users"}
                  user_posts GET /users/:user_id/posts               {:action=>"index", :controller=>"posts"}
               user_comments GET /users/:user_id/comments            {:action=>"index", :controller=>"comments"}
user_comments_received_index GET /users/:user_id/comments_received   {:action=>"index", :controller=>"comments_received"}

So it appears that rails is adding _index to the end of the comments_received route. I'll admit I don't know why (something to do with clashing with the other comments route?) but it explains your problem.

An nicer alternative might be to define a collection action on your comments resource, like so:

map.resources :users do |user|
  user.resources :posts
  user.resources :comments, :collection => {:received => :get}

This will give you the following routes:

                 users GET /users                             {:action=>"index", :controller=>"users"}
            user_posts GET /users/:user_id/posts              {:action=>"index", :controller=>"posts"}
         user_comments GET /users/:user_id/comments           {:action=>"index", :controller=>"comments"} 
received_user_comments GET /users/:user_id/comments/received  {:action=>"received", :controller=>"comments"}

Note: the received action is now on the comments controller