Search code examples
ruby-on-railshelper

How can I loop through an ActiveRecord_Relation in my helpers instead of views?


I'm trying to move a loop statement from my view that iterates with every element in a ActiveRecord_Relation and put it on my helpers.

This is the code I'm using in the views to generate a card for each post in the database:

<% @posts.each do |post|%>
  <div class="post-container">
    <div>
      <h2 class="m-0-p-0"><%= post.title %></h2><br/>
      <hr class="m-0-p-0" />
      <%= second_useless_helper(user_signed_in?, post) %>
    </div>
    <div>
      <h4><%= post.body %></h4>
    </div>
  </div>
<% end %>

But when I try to iterate through my helpers using this code:

def useless_loop
  @posts.each do |item|
    render inline: '<p>' + item.title + '</p>'.html_safe
  end
end

This is what I got instead: Code returned from the loop in helpers

I tried a lot of different ways of looping, but every single one returns the same thing.

The only thing that I could do was access the values of every item through bracket notation (Ex: @posts[0].title or @posts[4].body). But I failed when I tried to loop it.

I searched a lot about what is happening and why the loop does not work, but I couldn't figure it out.

The full code is available here: https://github.com/luisvinicius09/members-area/tree/app


Solution

  • Your useless_loop helper works fine. The reason it returns the posts collection is because Array#each returns self.

    def useless_loop
      @posts.each do |item|
        render inline: '<p>' + item.title + '</p>'.html_safe
      end
    end
    

    In the above the render call is made, but the return value of render is ignored since each does not use the return value of a block. You could use a combination of map and join to collect all render results, then join them together into a single string.

    def useless_loop
      @posts
        .map { |item| render inline: "<p>#{h item.title}</p>".html_safe }
        .join
        .html_safe
    end
    

    However there is no need for the render call at all.

    .map { |item| render inline: "<p>#{h item.title}</p>".html_safe }
    # can be replaced with
    .map { |item| "<p>#{h item.title}</p>".html_safe }
    

    You could also use the tag helper. This helper escapes special HTML characters in unsafe strings passed as content, which removes the need to use html_escape (aliased as h).

    def useless_loop
      @posts.map { |item| tag.p(item.title) }.join.html_safe
    end
    

    I'm not entirely sure if the final .html_safe call is needed. If all elements in an array are marked as HTML safe the resulting string might be marked as HTML safe automatically. I currently don't have a Rails environment on hand, so you'll have to test that one yourself.