Rails how to group by date and count records? - ruby-on-rails

In my model I have:
start = 3.weeks.ago
kliks = where(created_at: start.beginning_of_day..Time.zone.now)
kliks = kliks.group("date(created_at)")
How do I count all the records for each day as total?

I believe that this should work:
kliks = count(
:group => 'date(created_at)',
:conditions => { :created_at => start.beginning_of_day..Time.zone.now }
)
This will return you a hash mapping dates to their count.

Related

Rails - Iterate thru Multiple Arrays

I'm looking for a more DRY way to iterate through some code. I have a User model and I want to keep count of certain Users in a ReportRecord model (for reporting).
I have a defined list of values of User.names that I want to record (i.e. "Jan", "Lisa", "Tina"). How can I make this code more DRY as the list is much longer than three values?
#users = User.all
#users.each do |u|
# this part repeats with different names
quantity = u.where("name = ?", "Jan").count
ReportRecord.create(:user_id => u.id, :name => "Jan", :quantity => quantity)
# repeated code with different name
quantity = u.where("name = ?", "Lisa").count
ReportRecord.create(:user_id => u.id, :name => "Lisa", :quantity => quantity)
# repeated code with different name
quantity = u.where("name = ?", "Tina").count
ReportRecord.create(:user_id => u.id, :name => "Tina", :quantity => quantity)
end
I would sum all users first (1 query instead of 3):
quantity_by_name = User.select(:name).where(name: %w(Jan List Tina))
.group(:name).sum(:quantity)
#=> { 'Lisa' => 1, 'Jan' => 2, 'Tina' => 3 }
quantity_by_name.each do |name, quantity|
ReportRecord.create(name: name, quantity: quantity)
end
names = %w(Jan List Tina)
names.each do |name|
count = User.where(name: name).count
ReportRecord.create(name: name, quantity: count) # I don't understand `u.id`
end

IN clause in :conditions in rails

I am working in rails 2, I want to execute Query
PunchingInformation.all(
:select => "users.id, login, firstname, lastname,
sec_to_time(avg(time_to_sec(punching_informations.punch_in_time))) as 'avg_pit',
sec_to_time(avg(time_to_sec(punching_informations.punch_out_time))) as 'avg_pot'",
:joins => :user,
:group => "users.id",
:conditions => {
"punching_informations.date between '#{start_date}' and '#{end_date}'",
["punching_informations.user_id IN (?)", employees.map { |v| v.to_i } ]
}
)
But it always return error like
Mysql::Error: Unknown column 'punching_informations.date between '2012-09-01' and '2012-09-25'' in 'where clause': SELECT users.id,login, firstname,lastname, sec_to_time(avg(time_to_sec(punching_informations.punch_in_time))) as 'avg_pit',
sec_to_time(avg(time_to_sec(punching_informations.punch_out_time))) as 'avg_pot' FROM punching_informations INNER JOIN users ON users.id = punching_informations.user_id AND (users.type = 'User' OR users.type = 'AnonymousUser' ) WHERE (punching_informations.date between '2012-09-01' and '2012-09-25' IN ('punching_informations.user_id IN (?)','--- \n- 28\n- 90\n')) GROUP BY users.id
Need your help.
It is a bit unclear what you meant (you have array, but taken in curly braces {} like a hash), but it seems ruby treats first string ("punching_informations.date between '#{start_date}' and '#{end_date}'") as a column, and second array, as array of expected values, thus making the invalid IN condition.
Perhaps it would work if rewritten as
:conditions => {
[ "(punching_informations.date between '#{start_date}' AND '#{end_date}') AND punching_informations.user_id IN (?)", employees.map { |v| v.to_i } ]
}
or even better
:conditions => {
[ "(punching_informations.date between ? AND ?) AND punching_informations.user_id IN (?)", start_date, end_date, employees.map { |v| v.to_i } ]
}
add punching_informations.date and punching_informations.user_id in select
:select => "punching_informations.date, punching_informations.user_id, users.id, ....

Calculate Month Statistics

I have a donations table where I'm trying to calculate the total amount for each month. For months without without any donations, I'd like the result to return 0.
Here's my current query:
Donation.calculate(:sum, :amount, :conditions => {
:created_at => (Time.now.prev_year.all_year) },
:order => "EXTRACT(month FROM created_at)",
:group => ["EXTRACT(month FROM created_at)"])
which returns:
{7=>220392, 8=>334210, 9=>475188, 10=>323661, 11=>307689, 12=>439889}
Any ideas how to grab the empty months?
Normally you'd left join to a calendar table (or generate_series in PostgreSQL) to get the missing months but the easiest thing with Rails would be to merge your results into a Hash of zeroes; something like this:
class Donation
def self.by_month
h = Donation.calculate(:sum, :amount, :conditions => {
:created_at => (Time.now.prev_year.all_year) },
:order => "EXTRACT(month FROM created_at)",
:group => ["EXTRACT(month FROM created_at)"])
Hash[(1..12).map { |month| [ month, 0 ] }].merge(h)
end
end
then just call the class method, h = Donation.by_month, to get your results.
In addition to mu is too short answer, in Rails 3.2.12 did not work for me, ActiveRecord returns the keys as strings:
h = Donation.calculate(:sum, :amount, :conditions => {
:created_at => (Time.now.prev_year.all_year) },
:order => "EXTRACT(month FROM created_at)",
:group => ["EXTRACT(month FROM created_at)"])
Which returns:
{"7"=>220392, "8"=>334210, "9"=>475188, "10"=>323661, "11"=>307689, "12"=>439889}
So when I merge the hash with zeros:
{1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0, 7=>0, 8=>0, 9=>0, 10=>0, 11=>0, 12=>0, "7"=>220392, "8"=>334210, "9"=>475188, "10"=>323661, "11"=>307689, "12"=>439889}
The little fix (to_s):
Hash[(1..12).map { |month| [ month.to_s, 0 ] }].merge(h)

Ruby: group an array of ActiveRecord objects in a hash

I'd like to group an array of ActiveRecord objects into a hash with an interface that's simple to query after an SQL statement that looks like the following:
SELECT name,value from foo where name IN ('bar', 'other_bar') LIMIT 2;
After that query, I want a hash where I can go:
foo[:bar] # output: value1
foo[:other_bar] # output: value2
What's the best way to collect the objects with ActiveRecord and group them so I can use the interface above?
In Rails 2
foos = Foo.all :select => "name, value",
:conditions => ["name in (?)", %w(bar other_bar)],
:limit => 2
In Rails 3
foos = Foo.where("name in (?)", %w(bar other_bar)).select("name, value").limit(2)
Then
foo = Hash[foos.map { |f| [f.name.to_sym, f.value] }]
or
foo = foos.inject({}) { |h, f| h[f.name.to_sym] = f.value; h }
or in Ruby 1.9
foo = foos.each_with_object({}) { |f, hash| hash[f.name.to_sym] = f.value }
If I understood you correctly:
foo = Hash[Foo.find(:all, :limit => 2, :select => "name, value", :conditions => ["name in ('bar', 'other_bar')"]).map { |s| [s.name.intern, s.value] }]
Hash[result.map { |r| [r[:name].to_sym, r[:value]] } ]
models.inject({}) {|h,m| h[ m.name.to_sym ] = m.value; h }

Rails: Making this query database-agnostic...?

I have these two lines in my model, written for PostgreSQL:
named_scope :by_month, lambda { |month| { :conditions => ["EXTRACT(MONTH FROM recorded_on) = ?", month] }}
named_scope :by_year, lambda { |year| { :conditions => ["EXTRACT(YEAR FROM recorded_on) = ?", year] }}
I'm running PostgreSQL in production, but I'm developing with SQLite3. How can I write those lines in a way that is database-agnostic?
Btw, "recorded_on" is formed from the following:
Model.recorded_on = Time.parse("Fri, 01 May 2009 08:42:23 -0400")
Okay, found out there's a better way (thanks to this article):
Basically do nothing in the model!
date = Date.new(year, month, 1)
Model.find(:all, :conditions => { :recorded_on => date..date.end_of_month })
BETWEEN should be pretty universal: how about something like this:
named_scope :by_month, lambda { |month|
d1 = Date.new(2009, month, 1)
d2 = d1.end_of_month
{ :conditions => ['recorded_on between ? and ?', d1, d2]}
}
named_scope :by_year, lambda { |year|
d1 = Date.new(year, 1, 1)
d2 = d1.end_of_year
{ :conditions => ['recorded_on between ? and ?', d1, d2]}
}
If you have times, you'd need to get a little smarter with the hours, minutes and seconds.

Resources