How to add "0," after each loop in Ruby - ruby-on-rails

How to add "0," after each loop in Ruby (At the data: part of the series)
So first loop would
show only the value
next loop 0,value
next loop 0,0,value
next loop 0,0,0,value
etc..
series: [
<% #prot = ProjectTask.where("project_id = ? AND taskType = ?" ,#project.id, "Pre-Sales")%>
<% #prot.each do |prt| %>
<% hoursSum = 0 %>
{
name: '<%= prt.task_name%>',
data: [(here after each loop of #prot i want to add "0," here)<% #taskHours = Effort.where(:project_task_id => prt.id) %>
<% #taskHours.each do |th| %>
<% hoursSum = hoursSum + th.hours %>
<% end %>
<%= hoursSum%>
]
},<% end %>

<% #prot.each_with_index do |index, prt| %>
...
<%= "0," * index %>
...
<% end %>

This logic seems too complex to be in a view - I would migrate it to a helper function that builds up the data array and call that instead.
I think you'll find it a lot easier to do what you want then without having to deal with all the clutter of the erb tags etc. There's lots of ways you could do it - Yossi's suggestion of using each_with_index is a perfectly good one.
Two little things though - I would advise against shortened names for stuff like #prot - just call it #project_task. It's more readable and you can guarantee you call it the same thing throughout your code (instead of having some places where you call it #prot, others where it's #ptask etc) which will save you more time than you lose typing a longer name, I promise you.
Also - you use camelCase for some of your variables - I would advise sticking with the Ruby convention of snake_case.

Related

How do I elegantly check for presence of both the object and associated objects?

I have an instance variable #tally_property, and if there are photos on that object I would like to cycle through the photos and show them.
So my code snippet looks like this:
<% if #tally_property.photos.present? %>
<% #tally_property.photos.each_with_index do |photo, index| %>
The issue is that based on the above, if #tally_property is nil, then the entire first line throws an error.
So is there a 'nil' check I can do that isn't bulky, i.e. I don't want to do if #tally_property.nil?, on both the primary object and the association, and is elegant and ruby & rails-esque?
I would use the safe navigation operator (&.) and write something like this:
<% #tally_property&.photos&.each_with_index do |photo, index| %>
...
<% end %>
In Ruby 2.3.0+ you can use the safe navigation operator:
#tally_property&.photos
ActiveSupport has a .try method that can be used to the same end in older versions of ruby:
#tally_property.try(:photos)
You can add a simple conditional to be able to safely iterate through the collection:
<% (#tally_property.try(:photos)||[]).each_with_index do |photo, index| %>
<% end %>
Rails 4 adds ActiveRecord::Relation#none and a change in behaviour so that associations always return a ActiveRecord::Relation. So its perfectly acceptable to write:
<% #tally_property.try(:photos).try(:each_with_index) do |photo, index| %>
<% end %>
After upgrading your app. Or you can use a partial and render:
<%= render partial: 'photos', collection: #tally_property.photos if #tally_property %>
Which removes the need for writing the iteration.
Use && (or and, they each have their sweetspot).
Taking it out of Erb for a moment, I would generally write something like this:
if #tally_property and #tally_property.photos.present?
Depending on photos I might use:
if #tally_property and #tally_property.photos
or perhaps:
if #tally_property and not #tally_property.photos.empty?
Sometimes I'll use a temporary variable:
if (photos = #tally_property && #tally_property.photos)
photos.each #…
That kind of thing.
I would recommend this episode of Ruby Tapas, And/Or for a longer (but still quick) look at it.
One more way, just select all photos connected to this tally_property:
example how it might be:
Photo.joins(:tally_property).each_with_index do |photo, index|

How to select 6 random records from table

My portfolio_controller.rb has an index method like this:
def index
#portfolio = PortfolioItem.all
end
How can I specify in the condition that the code in this block should be executed 6 times? In other words, how can I access exactly 6 values from the #portfolio object in my view, using a loop? This is what I have so far:
<% #portfolio.shuffle.each do |portfo| %>
Using all, followed by shuffle, is a bad solution for two reasons.
A slight improvement would be to use sample(6) instead of shuffle.first(6), as this removes a step from the process.
However, the bigger issue here is that Portfolio.all.<something> (where the <something> method requires converting the data into a ruby Array) will fetch all of the data into memory - which is a bad idea. As the table grows, this will become a bigger performance issue.
A better idea is to perform the "random selection" in SQL (with the order and limit methods), rather than in ruby. This avoids the need to fetch other data into memory.
The exact solution is database-specific, unfortunately. For PostgreSQL and SQLite, use:
Portfolio.order('RANDOM()').limit(6).each do |portfolio|
Or for MySQL, use:
Portfolio.order('RAND()').limit(6).each do |portfolio|
You could define this as a helper in the Portfolio model - for example:
class Portfolio < ApplicationRecord
# ...
scope :random_sample, ->(n) { order('RANDOM()').limit(n) }
# ...
end
And then in your view:
#portfolio.random_sample(6).each do |portfolio|
You can something like this :
<%(1..6).each do |i| %>
<% #your statements %>
<%end%>
<% #portfolio.shuffle.each_with_index do |portfo, index| %>
<%if index <= 6%>
<p><%= portfo.title %></p>
<%end%>
<% end %>
Or You can do it as
<% #portfolio.shuffle.take(6).each do |portfo| %>
<p><%= portfo.title %></p>
<% end %>

Splitting up an each block, into blocks depending on content

This is a tricky one to express, I'll give it a shot though:
I have defined #model = Model.all which includes both depart_at and arrive_at. I now need to run through these, in groups of date. (Each is a datetime in the database).
I've tried a few things, but I can't seem to get anything to work here.
<% #departure_times.each do |departure_time| %>
<% end %>
is the current code. I can't seem to find anything in the Rails API about, what I can do to chunk up by the date of depart_at.
Edit:
The expected outcome is something alike:
**date** - 10:15, 11:15, 20:30
**date** - 11:15, 12:30, 14:15
etc - The meaning for this, is to group the output by the date of the timestamp.
One might prepare the chunked array in the controller:
#dt_grouped = #departure_times.group_by do |departure_time|
Date.parse(departure_time.depart_at.to_s).to_s
end
This will produce a Hash instance like:
{ '2016-03-08' => [dt1, dt2, dt3,...], ...}
and then in view:
<% #dt_grouped.each do |date, times| %>
<%= date %> — <%= times.join ', ' %>
<% end %>

Rails output polymorphic associations

I want to implement a search functionality in my Rails app by using the pg_search gem. I've set up everything like it says in the documentation. Then I've set up a search controller with a show action:
def show
#pg_search_documents = PgSearch.multisearch(search_params)
end
The search itself works but I have a really annoying problem in my view. Whatever I do, it always outputs an array of PgSearch::Document objects. Even when I only write this in my view:
<%= #pg_search_documents.each do |document| %>
<% end %>
I get this (I've shortened it):
[#<PgSearch::Document id: 2, content: "…", searchable_id: 28, searchable_type: "Vessel">, #<PgSearch::Document id: 3, content: "…", searchable_id: 27, searchable_type: "Vessel">]
I know that pg_search sets up a polymorphic association which I've never dealt with before — could that be the problem?
Thanks in advance
<%= #pg_search_documents.each do |document| %>
<% end %>
This is a classic error, one I remember being puzzled over when I first started learning Rails. The mistake is using <%= %> with each. The return value of each is the array that you're iterating over (in this case, #pg_search_documents), and by using <%=, you're telling Rails to create a string from that array and insert it into your view. That generally isn't what you want: you want the view to be generated by the code inside the block you're passing to each.
Use <% #pg_search_documents.each do |document| %> instead (omitting the =) and you'll avoid the dump of the array's content.
You may also need to use .searchable as #blelump suggests, but I wanted to answer the other half of your question, as it's a common pitfall.
To get back to the original source model, searchable call is needed on these search result records, e.g:
<% #pg_search_documents.each do |document| %>
<%= document.searchable %>
<% end %>
You can also switch back to the source model within your controller, e.g:
#pg_search_documents = PgSearch.multisearch(search_params).collect(&:searchable)
Then, the #pg_search_documents will contain Vessel elements.

Multiple similar loop in a view, better practice?

Using Rails 3. I have 2 options for my view:
OPTION A:
<% #items.each do |item| %>
...
<% end %>
...
<script>
var items = [
<% #items.each do |item| %>
['<%= item.name %>', <%= item.lat %>, <%= item.lng %>],
<% end %>
];
</script>
OPTION B:
<% #items.each do |item| %>
...
<% content_for :items_array do %>
['<%= item.name %>', <%= item.lat %>, <%= item.lng %>],
<% end %>
<% end %>
...
<script>
var items = [<%= yield :items_array %>];
</script>
At the moment, I have chosen OPTION B, but I still think that it is not neat at all. Any better way to do this? I refused to choose OPTION A because I don't want it to loop twice in the same view because of performance and DRY issues, correct me if I am wrong.
Thanks.
If it were my project I would probably move this problem to the javascript side. Put the lat and long into data- attributes somewhere in the item tags and use unobtrusive scripting to read them. (If you want an example let me know.)
If that's not possible, I personally would prefer option A, first of all for readability (it's easier to understand, especially if you're not familiar with content_for) and second because it's easier to refactor, we could move collecting the data into a method on the model or maybe into a helper. DRY doesn't really apply here because even though you're looping through the same dataset, you're doing very different things with it.
That said, I wasn't quite sure about the performance implications so I ran a little test. I took 500 client records in one of my apps and used the option A to render a list of links followed by mapping their data into a content_for block and benchmarked it using Ruby's inbuilt Benchmark module (it's in haml instead of erb but I'm sure it's not too hard to read.)
- bench = Benchmark.measure do
%ul#client-list
- #clients.each do |client|
%li
= link_to client.company, client
- content_for :foobar do
= "['#{j client.name}', '#{j client.company}', '#{j client.email}'],"
= javascript_tag do
var nearbys = [#{yield :foobar}];
%p= bench.to_s
The result, about 370 miliseconds
0.370000 0.000000 0.370000 ( 0.397476)
Then I did the same with two loops (one each one map):
- bench = Benchmark.measure do
%ul#client-list
- #clients.each do |client|
%li
= link_to client.company, client
= javascript_tag do
= "var nearbys = [#{ #clients.map { |client| "['#{j client.name}', '#{j client.company}', '#{j client.email}']," }}]"
%p= bench.to_s
The results, about 80 miliseconds:
0.080000 0.000000 0.080000 ( 0.077940)
You may want to check the results yourself, but it seems like the content_for method is not a very efficient means to solve this problem.
edit: just for reference, this was tested using ree 1.8.7 and rails 3.2.6
edit2:
An example of how to rewrite this without using inline javascript. I'd first move all the required parameters into data- attributes, for example in the view:
<% #items.each do |item| %>
<div class="item" data-name="<%= item.name %>"
data-lat="<%= item.lat %>"
data-long="<%= item.long %>">
<%= item.name %>
</div>
<% end %>
(You could also take the name from inside the div, but putting everything in data-attributes is more consistent.)
Then in you javascript you could map the data as follows (assuming jquery):
var nearbys = $('.item').map(function() {
return [ $(this).data("name"), $(this).data("lat"), $(this).data("long") ];
});
This way you can keep all your javascript neatly in a separate file and collecting the data is done client-side.
it's very simple just use to_json it will do all the escaping for you ;)
So your code could look like this:
<script>
var items = <%= #items.map{|o| [o.name, o.lat, o.lng]}.to_json.html_safe %>;
</script>
(the html_safe marks the string as safe, avoiding double escaping)
If you have objects that are more complex than just arrays of strings and numbers you should use jQuery's $.parseJSON(json) so it would look like:
<script>
var items = $.parseJSON('<%= #items.map{|o| [o.name, o.lat, o.lng]}.to_json.html_safe %>');
</script>
Enjoy!
All of this code should go into a function in the helper file for the Model. That is what the helpers are for, right?
The views should have minimum Ruby code hard-wired into them. So, use helpers.
Here are some links to get you more insight into helpers:
using-helper-methods-in-ruby-on-rails
what-belongs-in-a-helper-method

Resources