views:

32

answers:

2

I have users, posts and comments. User can post only one comment to each post.

class User < ActiveRecord::Base
  has_many :posts
  has_many :comments
end

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

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

On userpage (http://host/users/1 for example) I want to show all posts where the given user has commented. Each post then will have all other comments.

I can do something like this in my User controller:

def show
  @user = User.find(params[:user_id])
  @posts = []
  user.comments.each {|comment| @posts << comment.post}
end

This way I will find User, then all his comments, then corresponding post to each comment, and then (in my view) for each post I will render post.comments. I'm totally new in Rails, so I can do this =) But I think it's somehow bad and there is a better way to do this, maybe I should use scopes or named_scopes (don't know yet what this is, but looks scary).

So can you point me out to the right direction here?

Thanks in advance for any help.

Ilia

+3  A: 

You could define an association which retrieves all the posts with comments in a single query. Keeping it in the model reduces the complexity of your controllers, enables you to reuse the association and makes it easier to unit test.

class User < ActiveRecord::Base
  has_many :posts_with_comments, :through => :comments, :source => :post
  # ...
end

:through is an option for has_many to specify a join table through which to perform the query. We need to specify the :source as Rails wouldn't be able to infer the source from :post_with_comments.

Lastly, update your controller to use the association.

def show
  @user  = User.find(params[:user_id])
  @posts = @user.posts_with_comments
end

To understand more about :through and :source take a look at the documentation.

Tate Johnson
I have to admit, I got lost the first time I read this, because my mind was still stuck on a 'user -> post -> comment' relationship. But, after reviewing it a few times, I made the pivot to the 'user -> comment -> post' mental model. The comment looks like a classic many-to-many mapping model, and then this all seems so simple!
Edward M Smith
Wow! It works great. And it was easier, than I thought. Thank you.
ilzoff
@Edward You're on the nail. `has_many :through =>` is more or less the same as `has_and_belongs_to_many` except `has_many :through =>` lets you work on the join association. That's it's common use case, although it works perfectly for what @ilzoff is doing too.
Tate Johnson
A: 

When you got the user, you have the associations to his posts and each post has his comments. You could write: (I don't know the names of your table fields, so i named the text text)

# In Controller
@user = User.find(params[:user_id]).include([:posts, :comments])

# In View
@user.posts.each do |post|
  post.text
  # Comments to the Post
  post.comments.each do |comment|
    comment.text
  end
end

I haven't tested the code, so there could be some errors.

ipsum
It's not exactly what I wanted. I needed not all users posts, but all posts through users comments. You can see, the Tate Johnson's answer, it works for me.
ilzoff