Splitting ruby content with in_groups_of - ruby

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"

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 :-).

Sorting in rails app

I created an app that gives points to students. I want to sort the points in descending order. This is my code.
<% #users.each do |user| %>
<li> <%= user.points%></li>
<%end%>
This code outputs the points from each student, but when I try to add sort! after user.points, it gives me an error. I am not sure where I should do this.
You can go for:
<% #users.sort_by{|u| u.points}.each do |user| %>
<li> <%= user.points%></li>
<%end%>
Or you can go for:
#users=User.order(:points)
Try this:
<% #users.sort_by{|u| u.points}.reverse.each do |user| %>
<li> <%= user.points%></li>
<% end %>
Note: I have used reverese as you have mentioned I want to sort the points in descending order. But It's always good practise to assign data to object in controller as per your requirement.
Do it in controller like:
#users = User.order(points: :desc) # this will fetch users in descending order on basis of points
then in view simply use:
<% #users.each do |user| %>
<li> <%= user.points%></li>
<%end%>
for more info regarding Order
The documentation for Active Record .order is what you need.
http://apidock.com/rails/ActiveRecord/QueryMethods/order
For example:
User.order(:points)
would provide you with users ordered by points. Do the ordering from your controller rather in your view.

Divide loop iteration in an html.erb file over different sections for enhancing performance

This is part of my code in an html.erb file
<div class="list_carousel">
<ul id="products-carousel" class="carousel">
<% #posts.select{ |post| post.categories.include?(#categories.find_by_name("Productos")) }.each do |post|%>
[...]
<% end %>
</ul>
</div>
[...]
<div class="modals">
<% #posts.select{ |post| post.categories.include?(#categories.find_by_name("Productos")) }.each do |post|%>
[...]
<% end %>
</div>
The problem here is that I'm using several database queries to perform this loop and I would like to re-use the first loop in other parts of this files in order to enhance performance.
For example I would like to do something like each do |post| do something in this first section, don't do nothing in this second section and continue in the third one. This way I could re-use the instance of the selected postd over which I'm iterating.
In your controller, you can do this:
#posts = Post.all
#productos_category = #categories.where(name: "Productos").first
#productos = #posts.where(category_id: #productos_category.id)
Note that I changed the way you find the associated #productos to use the ActiveRecord query interface, as opposed to using pure ruby select and include? methods. This will allow for the query to be much more efficient. I implied some things, such as the child key for the categories being category_id. Change this where appropriate.
EDIT 1
I believe this will work, although my experience with many-to-many relationships is somewhat limited:
#productos_category = #categories.where(name: "Productos").first
#productos_posts = Post.joins(:join_table).where(join_table: {category_id: #productos_category.id})
Where :join_table is the name of the table that is used in the has_many association. If it is a HABTM relationship, this will probably be called categories_posts.
Then your view would change to this:
<div class="list_carousel">
<ul id="products-carousel" class="carousel">
<% #productos_posts.each do |post|%>
[...]
<% end %>
</ul>
</div>
[...]
<div class="modals">
<% #productos_posts.each do |post|%>
[...]
<% end %>
</div>

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| %>

Rails Template Rendering ERB if Statement is rendered not in order

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>

Resources