Question

Just a question related to Rails best practices:

Say we have a Post and a Comment model. The same partial is used to render the post on both the index view and the show view. Inside that partial is a reference to another partial that renders the comments.

post_controller.rb

def index
  @posts = Post.all
end

def show
  @post = Post.find(params[:id])
end

_post.html.haml

.post
  = post.content 
  = render 'comments/list', :comments => post.comments

comments/_list.html.haml

- comments.each do |c|
    c.comment

Let's now say that for the post index view, we only want to display the last 3 comments for each post, yet on the show view we want to display all comments for the post. Because the same partial is used, we can't edit the call to limit the comments. What's the best way to achieve this? Currently I've abstracted this out to a helper, however it feels a little dodgy:

def limited_comments(comments)
  if params[:controller] == 'posts' and params[:action] == 'show'
    comments
  else
    comments.limit(3)
  end
end

Which means _post.html.haml is changed to read

= render 'comments/list', :comments => limited_comments(post.comments)

It works, but doesn't feel like the Rails way. I'm guessing there's a way with scopes, but I can't seem to figure it out.

Was it helpful?

Solution

I believe what @benchwarmer wanted to say is that it's better to pass a parameter to _post partial. Straightforward @comments doesn't work, but something like the code below might:

def index
  @posts = Post.all
  render :partial => @posts, :locals => { :max_comments => 3 }
end

def show
  @post = Post.find(params[:id])
  render :partial => @post, :locals => { :max_comments => false }
end

In post.html.haml:

= render 'comments/list', :comments => limited_comments(post.comments,max_comments)

Your helper:

def limited_comments(comments,max_comments)
  max_comments ? comments.limit(max_comments) : comments
end

I didn't compile, so you may need to further work on parameters that you pass to render :partial (may be you'll have to separately set :partial and :object/:collection in this case, or smth else, I don't remember and didn't try). But I hope, the idea is clear - keep the logical representation (all comments or last 3) separate from processing path (which controller/action). May be you'll later want to display posts with comments embedded in yet another list (last 3 posts for a list of users), then such separation will come handy.

If you don't want to expose all your internal logical details at controller level, you can also do smth like:

def index
  @posts = Post.all
  render :partial => @posts, :locals => { :comments_list_type => :short }
end

def show
  @post = Post.find(params[:id])
  render :partial => @post, :locals => { :comments_list_type => :full }
end

Then, in post.html.haml:

= render 'comments/list', :comments => build_comments_list(post.comments,comments_list_type)

Your helper:

def limited_comments(comments,comments_list_type)
  case comments_list_type
    when :short
      comments.limit(3) 
    when :long
      comments.limit(10) 
    when :full
      comments
  end
end

OTHER TIPS

You can last 3 comments on the index action and assign into @comments variable and for show action you can load all the comments for that post. So it becomes

def index
  @posts = Post.all
  @comments = Comment.order("created_at desc").limit(3)
end

def show
  @posts = Post.all
  @comments = @post.comments.order("created_at desc").limit(3)
end

In the view its simply

= render 'comments/list', :comments => @comments
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top