I have a list of invoices...
#invoices_1_week = Invoice.order("due_date DESC").where("status != 'paid' AND due_date >= ? AND due_date < ?", Date.today, 1.week.from_now)
The invoices model has a total attribute. How can I get a sum of the totals in the #invoice_1_week collection?
I know I can do it in the view like this...
<% week_1_total = 0 %>
<% #invoices_1_week.each do |invoice| %>
<% week_1_total = week_1_total + invoice.total %>
<% end %>
<%= week_1_total %>
But I'm wondering if there is a more Railsy way of doing it.
Here's a Rails way, using ActiveRecord's sum method:
#invoices_1_week.sum("total")
Here are the docs.
You might want to consider using the symbol notation
#invoices_1_week.sum(:total)
or use single quotes
#invoices_1_week.sum('total')
In both cases, the attribute name is immutable.
Related
I have a collection of products users have purchased, grouped by their name, so I can count the number of unique products and how many of each has been purchased:
Controller:
#line_items = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"})
#products = #line_items.group_by(&:name)
View:
<% #products.each do |name, line_items| %>
<%= name %> - <%= line_items.count %><br>
<% end %>
Is there a way to order the .each loop so that it descends by line_items.count?
Thanks
It will perform better getting the correct data directly from the db:
#products = #line_items.group(:name).order("count_all DESC").count
That will give you the names and counts directly, e.g.
# => { "line_1" => 3, "line_2" => 2, "line_3" => 8 }
There's a bit of Rails magic at work here: the SQL generated using group, order and count will look like:
SELECT COUNT(*) AS count_all, name AS name FROM spree_line_items GROUP BY name ORDER BY count_all DESC
That's where count_all comes from: Rails attaches it to the count column automatically.
Then you can plug this directly into your view:
<% #products.each do |name, line_item_count| %>
<%= name %> - <%= line_item_count %><br>
<% end %>
Alternatively, if you're using the instance variable elsewhere, here's a simple Ruby solution:
#products = #line_items.group_by(&:name).sort_by { |_k, line_items| line_items.count }.reverse
This simply uses sort_by to get records ordered by the relevant count, then reverses to get decending order. There's a good benchmark on doing this here.
Hope that helps - let me know how you get on / if you've any questions.
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 %>
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 %>
i am new to rails and any help would be much appreciated.
i am trying to sort my list by status, but i have string in code stating "Pending"
i believe the error is prompting up because it can not place the status in order because i have a string
could one kindly advise the best way to go about sorting out a list in alphabetical order where by the list contains a string - thank you
error message
comparison of String with nil failed
jobseekerpg.html.erb
<% #userj_applications.sort_by(&:status).each do |application| %>
<%= link_to application.advert.title, userr_advert_path(application.advert.userr, application.advert) %> |
<%= application.advert.city %> |
<%= application.advert.category_country.name %> |
<span>
status:
<% if application.status == nil %>
<%= "Pending" %>
<% else %>
<%= application.status %>
<% end %>
</span> |
applied: <%= application.created_at.strftime("%B %d, %Y") %><br>
<% end %>
static_controller.rb
class StaticPagesController < ApplicationController
respond_to :html, :xml, :json
def jobseekerpg
#userj = Userj.find(current_userj)
#userj_applications = #userj.forms
end
end
my suggestion:
i currently have a column named status in my table
do i create another column in my table called statusPending
make this as a hidden_field and assign it to pending statusPending:Pending
so whenever an application status is nill i can use an if & else statement to call up application.statusPending
but then i will not need to only sort by status , i will need to sort by status & statusPending
any advise or help will be much appreciated
could one kindly advise the best way to go about sorting out a list in
alphabetical order where by the list contains a string - thank you
Array#sort
stringList.sort!
The "array.sort!" should sort the list in-line.
Here are some ruby examples with blocks http://www.dotnetperls.com/sort-ruby
Looks like you are getting that error when the sort_by method tries to compare a non null status with a null one. You could try to replace your statement:
#userj_applications.sort_by(&:status).each do |application|
with
#userj_applications.sort {|x,y| (x.status || '') <=> (y.status || '') }.each do |application|
This way nil values are treated as empty strings during sort.
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.