Search code examples
ruby-on-railsscopes

Best practices for displaying different data when using the same partials in Rails


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.


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