The following query to the postgres database
#interventos_2 = Intervento.where(['previsto >= ?', start_date]).group("DATE_TRUNC('week', previsto)").count
generates a hash from which the view can extract the data as follows
<% #interventos_2.each do |w| %>
<%= w[0].strftime('%Y-%W') %> <%= w[1] %><br />
<% end %>
However if there is a blank (count = 0) in the range of weeks we are concerned with, the sequence of years and commercial weeks will look weird and or misleading.
What is an efficient way to declare the range and then fill in the blank weeks with zero?
Update the query is being run via specific sql for performance reasons as the data set is expected to be sufficiently large, frequently changing (cache may not help all that much) and frequently asked.
If I understand you correctly, you want to present a full range of YYYY-WW labels and counts, starting with start_date regardless of whether there's data for a given week. You didn't mention if previsto is a Date, a Time, or a DateTime; I'll assume it's a Time just for maximum inconvenience. =]
I think the main challenge you're struggling with is that you're conflating the dataset with the presentation. I like handling the two separately: first, get the data and put it into a year-week format; then present the range.
For getting the data, I like a more Railsy, less database-specific solution. Your mileage may vary, especially if you've got a large dataset and/or need to make the database do the heavy lifting. Here's a query that gets only the previsto field for each record while also forcing the database to evaluate the date range. Probably the most concise query without having to break out SQL:
#interventos_2 = Intervento.select(:previsto).
where(previsto: (start_date..Time.now)).
map {|iv| iv.previsto.strftime('%Y-%W')}
Note that this also maps the result down to a simple array of YYYY-WW. Speaking of which, let's map out that range of YYYY-WW now:
# make sure the end_date a clean multiple of 7 days from start_date
end_date = Date.today + (7 - (Date.today - start_date.to_date) % 7)
#timespan = (start_date.to_date..end_date).step(7).map {|date| date.strftime('%Y-%W')}
(There are probably much tidier ways to write that)
Given those bits, here's a version of your view code that presents the full range of weeks and the count for that week, even if it's 0:
<% #timespan.each do |yearweek| %>
<%= yearweek %> <%= #interventos_2.count(yearweek) %><br />
<% end %>
In bocca al lupo!
Update: Your update notes that your use case requires the direct SQL query, so here's the same general approach with that in mind:
#interventos_2 = Intervento.where(['previsto >= ?', start_date]).
group("DATE_TRUNC('week', previsto)").count.
map {|timestamp,count| [timestamp.strftime('%Y-%W'), count]}.to_h
# make sure the end_date a clean multiple of 7 days from start_date
end_date = Date.today + (7 - (Date.today - start_date.to_date) % 7)
#timespan = (start_date.to_date..end_date).step(7).map {|date| date.strftime('%Y-%W')}
<% #timespan.each do |yearweek| %>
<%= yearweek %> <%= #interventos_2[yearweek] || 0 %><br />
<% end %>
With the following controller method, the range of weeks and the data required for the view is generated (beginning_of_week for comparing with the has data, in addition to the data itself).
#weeks = []
while start_date < final_date
#weeks[start_date.year] = [] unless #weeks[start_date.year]
#weeks << [start_date.beginning_of_week, start_date.cweek]
start_date += 1.week
end
#weeks.reject!{|a| a.blank?}
Thus the view can generate a layout item for each week and compare it to the hash, and where there is nil, generate zero.
<% #weeks.each do |week| %>
<% if !week.nil? %>
<%= week[0] %>:
<% z = #interventos_2.detect {|f| f[0] == week[0] } %>
<% if !z.nil? %>
<%= z.to_a[1] %>
<% else %>
0
<% end %>
<% end %>
Related
I am working with a Calendar and I think that calendar deals only with date without a time.
Note: requested_date is a DateTime Attribute. I want to get all reservations with requested date and covert it to date only:
users_controller.rb
def myprofile
#reservation = Reservation.all
#resv_by_date = #reservation.group_by(&:requested_date) <-- make requested_date to date only
end
myprofile.html.erb
<div id="admin">
<%= calendar do |date| %>
<%= date.day %><br>
<% #resv_by_date.each do |reservation| %>
<%= reservation.requested_date.to_date %><br>
<% end %>
<% end %>
</div>
(this month 'May') requested_date is existing on my database
image output after the solution of the 1st answer
I don't think you want to use a group on your ActiveRecord relation not group_by which is a Ruby method.
Also, the method to get a Date from a DateTime object is datetime.to_date:
#reservations_by_date = Reservation.select(:requested_date).group(:requested_date)
#reservations_by_date.each { |reservation| puts reservation.requested_date.to_date }
Normally I'd advice you to try to group the data in the DB already (using group) for performance reasons instead of ruby code (group_by). But in your specific case it seems that you indeed need to retrieve all reservations in the given time period (e.g. month) and just group them by the date so that you can display all of them in the calendar view. In that case, you indeed have to group in ruby code.
You can do that simply by updating the block in group_by:
#reservations_by_date = Reservation.all.group_by { |res| res.requested_date.to_date }
Note that most probably you'll want to narrow down the select only to the given time period (e.g. the displayed month) first, using where conditions, I skipped that part from this answer.
The grouping above will create a hash of the reservations where the keys will be the dates (not datetimes) and the values will be arrays of reservations in the given date. In the calendar view, you can simply browse once you've accessed them using the date key:
<% calendar do |date| %>
<%= date.day %><br>
<% #reservations_by_date[date].each do |reservation| %>
<%= reservation.name ... or whatever %><br>
<% end %>
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 %>
I am building a quiz application. Let's say I have four quizzes (that should run at 5 PM, 6 PM, 7 PM, and 8 PM), I represent it by setting a .datetime called game_start in the Quiz model.
Next, I want to show the user when the next 3 quizzes are coming up by datetime. I know I can do that by comparing game_start to Time.now like so:
<p>Upcoming Game #1: <%= Quiz.where("game_start >= ?", Time.now).order(game_start: :asc).first.topic %></p>
<p>Upcoming Game #2: <%= Quiz.where("game_start >= ?", Time.now).order(game_start: :asc).second.topic %></p>
I found someone approaching a similar problem in this answer, but they were not trying to return multiple matches.
So it sort of works, but that does not follow DRY. Is it possible to specify that I want to return the first three results closest to Time.now instead of doing them individually?
You can use limit to retrieve only 3. In your controller:
#quizzes = Quiz.where("game_start >= ?", Time.now).order(game_start: :asc).limit(3)
Then in your view iterate over #quizzes to display each quiz:
<% #quizzes.each_with_index do |quiz, index| %>
<p>Upcoming Game #<%= index + 1 %>: <%= quiz.topic %></p>
<% end %>
I have an app where I receive & ingest daily sales reports from multiple sources. All are structured differently so I store down into my Postgres db in separate tables.
I'm currently doing something like this to iterate over the last 30 days sales for one report source and it seems to work quite nicely. My concern is how efficient & scaleable this will be when I add additional report sources as the way I currently have it structured means I'd have to add and repeat large amounts of the code for each new source.
<% from = Date.today - 30 %> #30 days ago
<% to = Date.today %> #Today
<% step_date = from %>
<% source_one_chart_data = [] %> #Initialise empty array to later pass to JS Chart library
<% begin %>
<% count = #product.sales_source_one.total.where(:report_date => step_date).count %> #check if there are any sales for this product on the current step date
<% if count != 0 %>
<% sale = #product.sum_total_net_by_day(step_date) %>
<% source_one_chart_data.push(sale.to_s) %> #Push sales total to array if sales exist on that date
<% else %>
<% source_one_chart_data.push("0") %> #Otherwise push a zero into array so all 30 days map to a value
<% end %>
<% step_date += 1.day %> #Increase step_date by 1 on each iteration of the loop
<% end while step_date <= to %> #Stop loop when we reach to date
Can anyone offer any guidance on how efficiently bring in additional sales sources without having to repeat code? Also, it would be good if I could change the step from day to week/month/year and have it calculate sales accordingly; so if the sales source is reported daily and the step is week it would sum all values that occur in the step week.
Why do you have all of that code in your view? You should move most of it to your model / controller.
def process_chart_data
from = 1.month.ago.to_date
to = Date.today
step_date = from
chart_data = []
while step_date <= to
sales_total = sales_source_one.total.where(report_date: step_date).count
if sales_total.zero?
chart_data.push(0)
else
sale = sum_total_net_by_day(step_date)
chart_data.push(sale.to_s)
end
step_date += 1
end
return chart_data
end
The above could probably be refactored further, but now if you place it in your Product model then in your controller you can just do:
#product = Product.find(params[:id]) # or whatever
#chart_data = #product.process_chart_data
Then in your view you can use #chart_data.
Moving that code into a method also allows you to update it quicker, lets say I want the user to control how far back records are retrieved:
def process_chart_data(start_date)
from = start_date
...
end
In the controller:
#product = Product.find(params[:id])
#chart_data = #product.process_chart_data(params[:start_date])
I have a books model with a date type column named publish_date. On my views I'm iterating through the books and I want to group the books by year such that I have a heading for every year and books that were published on that year to be listed below the year heading.
So by starting with "2010" all books published on 2010 would be listed, then another heading "2009" with all books published in 2009 listed below it and so forth.
<% #all_books.each do |book| %>
<%=link_to book.title + ", (PDF, " + get_file_size(book.size) + ")" %>
<% end %>
By doing a book.publish_date.strftime("%Y") I am able to get the year but I do not know how to group the entries by year. Any help on this would be appreciated.
You can use group_by (see API) like (of the top of my head
<% #all_books.group_by(&:year).each do |year, book| %>
...
<% end %>
def year
self.created_at.strftime('%Y')
end
< % #all_books.group_by(&:year).each do |year, book| %>
Year < %= year %>
# render books here
< % end %>
What say?
You can use group_by for convenience, but your need can be better served by relying on DB for sorting and a each loop. This avoids the cost of client side sorting and hash manipulations for grouping.
Somewhere in your controller
#all_books = Book.all(:order => "publish_date DESC")
In your view
<%year = nil
#all_books.each do |book|
if year.nil? or year > book.publish_date.year
year = book.publish_date.year
%>
<h1> <%=year%><h1>
<%end % >
<%=link_to book.title + ", (PDF, " + get_file_size(book.size) + ")" %>
<%end %>
The quick and dirty approach is to simply group_by the year and iterate over those:
#all_books.group_by { |b| b.created_at.year }.each do |year, books|
# All books for one year, so put heading here
books.each do |book|
# ...
end
end
This requires sorting within the Rails application, so you will need to retrieve all relevant records in order to have the data properly organized. To do the sort on the server you will probably need to introduce a year column and keep it in sync with the created_at time.