Rails Template Rendering ERB if Statement is rendered not in order - ruby

When I'm using an if-statement in an erb- template file, the if statement is evaluated delayed, which is mixing up the html:
<small>
<% if #applause_count == 1 %>
The author has been cheered up once!
<% elsif #applause_count > 1%>
The author has been cheered up <%= #applause_count %> times! <br/>Be the next!
<% end if %>
</small>
produces:
<small>
</small>
The author has been cheered up 100 times! <br/>Be the next!
Can someone explain me this strange behaviour?

As noted, the problem is with the <% end if %>
Use <% end %>
This produces the desired html:
<small>
The author has been cheered up 2 times! <br/>Be the next!
</small>

Related

How to reuse ERB templating code in function?

I have this piece of code inside ERB template to render all articles in list.
<% #list.each do |a| %>
<article class="my-panel">
<header>
<h2><%= a[:name] %></h2>
<time datetime="<%= to_datetime(a[:time]) %>"><%= time_description(a[:time]) %></time>
</header>
... more stuff cut out
</article>
<% end %>
Now I am going to have to change it to something like this:
<% #list[content_splitter.before_ad_range].each do |a| %>
<%= render_article(a) %>
<% end %>
<%= AdCreator.between_content_ad %>
<% #list[content_splitter.after_ad_range].each do |a| %>
<%= render_article(a) %>
<% end %>
I thought it would be nice to have render_article defined in template as opposed to having html clutter my ruby code. But when I move that code inside function I get an error.
This is the function:
<% def render_article(a) %>
<article class="my-panel">
<header>
<h2><%= a[:name] %></h2>
<time datetime="<%= to_datetime(a[:time]) %>"><%= time_description(a[:time]) %></time>
</header>
<div class="image">
<img alt="" src="<%= rel_url_to a[:img_url_1x] %>" srcset="<%= rel_url_to a[:img_url_2x] %> 2x, <%= rel_url_to a[:img_url_3x] %> 3x">
</div>
<div class="text">
<%= a[:article_text] %>
</div>
</article>
<% end %>
This is the error:
undefined local variable or method `_erbout' for #<Html::FrontPage:0x0055fb94005c68>
Line of code producing this error is:
self.class.instance_variable_get(:#renderer).result(binding)
Why is this happening? How to find more informative error?
How to fix this? Can I avoid moving this obviously html dominant code into ruby helper file?
PS. I suspect problem is that functions to_datetime and time_description can't be accessed from inside ERB function.
I know that function render_article does get called because if I change it's signature to remove parameter I get an error
wrong number of arguments (given 1, expected 0)
# (erb):45:in `render_article'
What you are trying to do is not technically impossible, but rather difficult and not recommended as can be seen by reading comments and answers to this question.
As your error message points out your def won't quite happen in the context you "probably" expect and you certainly won't be able to "freely use erb features" while defining the method (since your context is completely different from what you expect).
The "railsy" way to do this is adding a helper or using a partial, but both come with their drawbacks. .erb files (as most templating-languages) do not "factor" well. If you want to factor things somewhat more freely you should look at the fortitude gem which provides what is basically a ruby-DSL for html which factors pretty easily. However, that is a rather drastic change from what you are probably used to.
If you really want to define a method inside an .erb-file then you would have to do it entirely within a single pair of <% ... %> brackets where you will have only access to your params, not your context. You would have to return what is basically a String in order to be able to use it in <%= ... %> and pay a hell of a lot of attention to escaping rules for everything to make it through. This is most probably more trouble than it is worth (but easy enough to do in fortitude :-).

Retrieve data using for loop in Ruby

I have a question about using foreach loops in Ruby.
I want to display documents and am using a foreach loop in order to display these documents. It returns an error with the i variable inside of data["response"]["docs"][i]["topic"] which is a JSON string I am iterating over.
I do not understand why that is. Can anyone tell me what I am doing wrong?
If I simply do data["response"]["docs"][0]["topic"] it works fine but not with the i. Why is that?
<%
(0..10).each do |i|
%>
<%= i %> <br/>
<%= data["response"]["docs"][i]["topic"] %>
<%
end
%>
My question is, how many items are there in data["response"]["docs"]? Are there exactly 11? Either way I would use the following code instead:
<% data["response"]["docs"].each_with_index do |item, index| %>
<%= index %>
<br/>
<%= item["topic"] %>
<% end %>
This iterates over the data["response"]["docs"] no matter how many there are (whether is is 1 doc or 20 docs) and stores the value in the variable named item. The each_with_index function gives you the index as well, stored in index, so you can display it later. If you only want the first 11 use:
<% data["response"]["docs"].first(11).each_with_index do |item, index| %>
This will grab a maximum of 11 doc items.
It's hard to tell what might be going wrong because you haven't posted the error, but if you're using a 10-element array, you want to do:
(0..9).each do |i|
With 0-based indexes, you should only use the range from 0-9, rather than 0-10. You may be getting an error because you're trying to access an element that isn't there (i.e. at index 10).
Even better is:
<% data["response"]["docs"].each do |document| %>
<%= document["topic"] %>
<% end %>
or if you need to print the index:
<% data["response"]["docs"].each_with_index do |document, index| %>
<%= index %> <br/>
<%= document["topic"] %>
<% end %>

Splitting ruby content with in_groups_of

I am using middleman's blog to output the summary of all blog posts on a page like so:
<ul>
<% page_articles.each_with_index do |article, i| %>
<% unless article.data['tags'].include?('featured') %>
<li class="Project">
<h3><%= link_to article.title, article.url %></h3>
</li>
<% end %>
<% end %>
</ul>
This works well, but I need to break after 3 entries like so:
<ul>
<li class="Project"><li>
<li class="Project"><li>
<li class="Project"><li>
</ul>
<div class="break"></div>
<ul>
<li class="Project"><li>
<li class="Project"><li>
<li class="Project"><li>
</ul>
From research, it seems that ruby's in_groups_of seems like the way to go here, but cannot get the syntax working properly. Here is what I have:
<ul>
<% page_articles.in_groups_of(3, false).each_with_index do |article, i| %>
<% unless article.data['tags'].include?('featured') %>
<li class="Project">
<h3><%= link_to article.title, article.url %></h3>
</li>
<% end %>
<% end %>
</ul>
It is returning an undefined method 'in_groups_of'. Also, it seems like the unless logic should be applied before it is split into groups of three. Any thoughts on ways to improve this?
Array#in_groups_of is an activesupport extension added by Rails to allow you to split and access equal groups (Array) with a fixed number of elements (by default nil will fill the uneven split. e.g.
require "active_support/core_ext/array"
a = (1..5).to_a
a.in_groups_of(2)
#=> [[1,2],[3,4],[5,nil]]
When you pass the second argument (fill_with) as false it no longer returns equal groups but rather splits the elements into groups of n elements and when the split is uneven the last group will be the remaining elements regardless of n.
require "active_support/core_ext/array"
a = (1..5).to_a
a.in_groups_of(2,false)
#=> [[1,2],[3,4],[5]]
This non-equal grouping functionality is actually available without the use of activesupport through Enumberable#each_slice
a = (1..5).to_a
a.each_slice(2).to_a
#=> [[1,2],[3,4],[5]]
require "active_support/core_ext/array"
a.in_groups_of(2,false) == a.each_slice(2).to_a
#=> true
Since you are specifying the second argument as false in this case instead of require-ing functionality you don't need I would recommend using Enumerable#each_slice as follows:
<ul>
<% page_articles.each_slice(3).with_index do |article, i| %>
<% unless article.data['tags'].include?('featured') %>
<li class="Project">
<h3><%= link_to article.title, article.url %></h3>
</li>
<% end %>
<% end %>
</ul>
This will function as requested without any additional overhead as Enumerable is part of the ruby core.
Array#in_groups_of is not part of core Ruby, but added by Rails as part of Active Support Core extensions.
You can use Active Support extensions by requiring it.
require "active_support/core_ext/array"

Use only the first image if there are multiples

I want to get an understanding of how to achieve the following. I have a simple CMS that will allow you to upload portfolio information/images. The user can upload one image or multiple images.
To show each image I use a .each and iterate through all the images for each portfolio...
<!-- Project Feed -->
<div class="project-feed clearfix">
<% #portfolios.each do |p| %>
<div class="one-third project-item"><!-- add data filter class here to enable selection of sectors-->
<% p.images.each do |i| %>
<%= image_tag(i.photo.url(:portfolio_square)) %>
<div class="overlay">
<!-- Small image bottom right of overlay, click and can navigate through all images
<%= image_tag(i.photo.url(:portfolio_large), :class => 'lightbox-link folio', :rel => 'gallery') %> -->
<%= link_to p do %>
<h5><%= p.title %> </h5>
<% end %>
<p>
<%= p.sectors.map(&:name).join(", ") %>
</p>
</div><!--/overlay-->
<h4><%= p.title %></h4>
</div>
<% end %>
<% end %>
My problem is that if there are multiple images assigned to the portfolio (let's say Portfolio 3 has 4 images associated with it) then they all show.
So I want to achieve this layout:
Portfolio 1 Portfolio 2 Portfolio 3
as opposed to
Portfolio 1 Portfolio 2 Portfolio 3
Portfolio 3 Portfolio 3 Portfolio 3
How can I grab only one image if there are multiples? Would this be .first? though I am unsure of the syntax and how it would fit in with my code. Or would I create a separate block and put it in an if statement if the image count is more than 1?
You can use first like this:
<%= image_tag(p.images.first.photo.url(:portfolio_square)) %>
If you do this you don't need:
<% p.images.each do |i| %>
If I understand correctly, you want to change the behavior of p.images.each to take only the first element, in that case, the minimal impact change on your code could be:
<% p.images.take(1).each do |i| %>

In a Ruby each block, how do I do something to the last record in the array within the block?

Say I have a block like this:
<% #help_sections.each do |section| %>
<li><%= section.name %></li>
<% end %>
But on the last record returned, I want to do something else, e.g. applying a class to the li that's there:
<li class="last"><%= section.name %></li>
How do I do that in the most DRY way?
Thanks.
Edit1:
I imagine I would simply use an if statement and the last ruby method, but not sure how to do that within the block? I know that if I just wanted the last element in that array, I could just do #help_sections.last, but that doesn't make sense within the confines of a Ruby block.
The most DRY way is to use CSS instead. Instead of e.g. this:
li.last { color: red; }
..and then cluttering up your markup with an extra CSS class, just use the :last-child pseudoselector, i.e.:
li:last-child { color: red; }
Then you don't have to change anything in your view. This is supported in all modern browsers including IE9.
Try each_with_index:
<% #help_sections.each do |section, index| %>
<li <%= "class='last'" if index == (#help_sections.length-1) %>><%= section.name %></li>
<% end %>
DRY is a good idea in general, but don't kill yourself to keep from repeating a li.
<% #help_sections[0...-1].each do |section| %>
<li><%= section.name %></li>
<% end %>
<li class="last"><%= #help_sections.last.name %></li>
You could either do something like
<% #help_sections.count.times do |i| %>
<%= #help_sections[i].name %>
<%= do_something if #help_sections.count == i - 1 %>
<% end %>
This old answer might help (Tell the end of a .each loop in ruby). Basically, you can use:
<% #help_sections.each_with_index do |section, index| %>
<% if index == #help_sections.size - 1 %>
<li class="last">
<% else %>
<li>
<% end %>
<%= section.name %></li>
<% end %>
If you use each_with_index instead of plain each, the block will also be passed the index of the current element within the collection. You can then compare that to #help_sections.length.
E.g.
<% #help_sections.each_with_index do |section, i| %>
<li<% concat " class='last'" if i == #help_sections.length - 1 %>><%= section.name %></li>
<% end %>
In general cases not covered by smart CSS selectors, you can define a convenient helper method, which would provide each iterated element with its context within the collection. Like this one:
def each_with_context enum
length = enum.count
enum.each_with_index do |elem, i|
context = {
:first => i == 0,
:last => i == length - 1,
:even => i.even?,
:odd => i.odd?,
:middle => i == length / 2
}
yield elem, context
end
end
And then use it within HAML view like this:
-each_with_context(#help_sections) do |section, context|
%li{:class => context[:last] ? 'last' : nil}
=section.name

Resources