How do I group these objects into a hash? - ruby-on-rails

I have an event modal, which has a datetime field titled scheduled_time. I need to create a hash that has a day name in a certain format ('mon', 'tue' etc) as the key, and the count of events that take place on that day as the value. How can I do this?
{
'mon' => 2,
'tue' => 4,
'wed' => 3,
'thu' => 5,
'fri' => 12,
'sat' => 11,
'sun' => 7,
}
I'm using Rails 3.2.0 and Ruby 1.9.2

The easiest would be to use count with a :group option:
h = Model.count(:group => %q{to_char(scheduled_time, 'dy')})
The specific function that you'd GROUP BY would, as usual, depend on the database; the to_char approach above would work with PostgreSQL, with MySQL you could use date_format and lower:
h = Model.count(:group => %q{lower(date_format(scheduled_time, '%a'))})
For SQLite you'd probably use strftime with a %w format and then convert the numbers to strings by hand.
Note that using Model.count(:group => ...) will give you a Hash with holes in it: if there aren't any entries in the table for that day then the Hash won't have a key for it. If you really want seven keys all the time then create a Hash with zeros:
h = { 'mon' => 0, 'tue' => 0, ... }
and then merge the count results into it:
h.merge!(Model.count(:group => ...))

Related

activerecord-postgres-hstore after save error ordering in Hash

store as Hash Table with Hstore, wrong ordering in Hash after Save
class Service < ActiveRecord::Base
serialize :properties, ActiveRecord::Coders::Hstore
end
service = Service.new
service.properties = { "aaa" => 1, "zz" => 2, "cc" => 3, "d" => 4 }
#=> { "aaa" => 1, "zz" => 2, "cc" => 3, "d" => 4 }
service.save
reload!
service = Service.find(:id)
service.properties
#=> { "d" => "4", "cc" => "3", "zz" => 2, "aaa" => 1 }
Bug::: wrong ordering after save
Is it because after serialize that it orders by Tree. Any ideas or anyone had faced this problem before? Thanks in advance.
From the fine PostgreSQL manual:
F.16. hstore
[...]
This module implements the hstore data type for storing sets of key/value pairs within a single PostgreSQL value.
[...]
The order of the pairs is not significant (and may not be reproduced on output).
So PostgreSQL's hstore type is an unordered set of key/value pairs that doesn't guarantee any particular order of the key/value pairs. Once your Ruby Hash is converted to an hstore, the ordering is lost.
If you need to maintain the order in your Hash you'll have to use a different serialize format.

Using group_by in rails/ruby

Here is my array:
finalcount = Vote.where(:month => month, :year => year, :vote_id => vote.id).count
I'd like to use group_by (I think?) to group the votes by user_id (which each vote has), and then return the user_id with the highest votes.
Any ideas how I can do this?
Have you tried the group method?
finalcount = Vote.where(:month => month, :year => year, :vote_id => vote.id).group(:user_id).count
will give you a Hash where the keys are the user_id and the values are the count. You can then call max on this result to get a two-element array containing the user_id and the count.
Here's an example from an app I have handy, where I use the result of the calculation to find the correct model instance and read properties from it:
ruby-1.9.2-p136 :001 > ExternalPost.group(:service_id).count
=> {18=>1, 19=>1, 21=>4}
ruby-1.9.2-p136 :002 > ExternalPost.group(:service_id).count.max
=> [21, 4]
ruby-1.9.2-p136 :003 > Service.find(ExternalPost.group(:service_id).count.max.first).user.first
=> "Brandon"
I think what you really want is this:
Vote.select("votes.user_id, count(votes.id)").
where(:month => month, :year => year, :id => vote.id).
group("votes.user_id").
order("count(votes.id) DESC").
limit(1)
This will grab the user_id and count of votes they have, but only return the one with the most votes.
Why did your originall call have :vote_id => vote.id? Does your Vote model really have a column named vote_id and is it different than it's normal id?
If your table structure is like:
vote_id
user_id
...
This should return an array where the first element is the user_id with the most votes, the second element is the count of that user_id.
Vote.group(:user_id).count.max{|a, b| a.last <=> b.last}

How to compare a string to dates in a find clause?

I want to get models whom date is within a date range.
So I want to do something like
MyModel.find_all_by_field1_id_and_field2_id(value1, value2, :conditions => { :date => nb_days_ago..Date.yesterday })
The thing is, the date attribute of my model is a string (with the format "08-24-2010"), and I can't modify this.
So to compare it to my range of dates, I tried this:
MyModel.find_all_by_field1_id_and_field2_id(value1, value2, :conditions => { Date.strptime(:date, "%m-%d-%Y") => nb_days_ago..Date.yesterday })
But I get an error that basically says that strptime can't process the :date symbol. I think my solution is not good.
How can I compare my string to my range of dates ?
Thanks
You have to convert the DB string to date in the database rather than in Ruby code:
Model.all(:conditions => [ "STR_TO_DATE(date,'%m-%d-%Y') BETWEEN ? AND ? ",
nb_days_ago, Date.yesterday])
Better solution is to normalize your model by adding a shadow field.
class Model
after_save :create_shadow_fields
def create_shadow_fields
self.date_fld = Date.strptime(self.date_str, "%m-%d-%Y")
end
end
Now your query can be written as follows:
Model.all(:conditions => {:date_fld => nb_days_ago..Date.yesterday})
Don't forget to add an index on the date_fld column.
Edit 1
For SQLLite, first solution can be rewritten as follows:
Model.all(:conditions => [ "STRFTIME('%m-%d-%Y', date) BETWEEN ? AND ? ",
nb_days_ago, Date.yesterday])
First of all I do not envy your situation. That's a pretty ugly date format. The only thing I can think of is to generate an array of strings, in that format, representing ALL the days between your starting date and your finish date, then use the SQL "IN" syntax to find dates in that set (which you can do from within ActiveRecord's :conditions param).
For example, if you wanted to search to 10 days ago:
num = 10 #number of days ago for search range
# range starts at 1 because you specified yesterday
matching_date_strings = (1..num).to_a.map{|x| x.days.ago.strftime("%m-%d-%Y")}
=> ["08-24-2010", "08-23-2010", "08-22-2010", "08-21-2010", "08-20-2010"]
# then...
records = MyModel.all(:conditions => { :date => matching_date_strings })
# or in your case with field1 and field2
records = MyModel.find_all_by_field1_id_and_field2_id(value1, value2, :conditions => { :date => matching_date_strings })
The idea is this should generate SQL with something like "... WHERE date IN ("08-24-2010", "08-23-2010", "08-22-2010", "08-21-2010", "08-20-2010")

How to order string columns differently

I have a has_many association like this:
has_many :respostas_matriz_alternativas, :class_name => 'RespostaMatrizAlternativa',
:order => 'respostas_matriz.codigo asc',
:include => :resposta_matriz
Well the fragment that matters is the "order". Codigo is a varchar column wich will contain in most cases numeric values. To show this data I need to order it by code, but when I have only numbers the order becomes awkward, something like:
1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, ...
What do you suggest for me to solve it?
Thanks.
I don't know that there's much you can do if the column will contain a mix of strings and numbers, but if the column will always be numeric you could use something like:
:order => 'cast(respostas_matriz.codigo as unsigned) asc'
The accepted answer works for SQLite but not PostgreSQL, I had to use one of the built in types to achieve the same result with different databases:
order: 'cast(respostas_matriz.codigo as integer) asc'
I'm kind of just a beginner with Ruby, but could you use something like the following to split the string into an array of smaller strings, then convert each string to an integer.
I don't have my rubydoc in front of me, but something like
:order => 'respostas_matriz.codigo.split.map {|s| s.to_i}'

How can I count the number of records that have a unique value in a particular field in ROR?

I have a record set that includes a date field, and want to determine how many unique dates are represented in the record set.
Something like:
Record.find(:all).date.unique.count
but of course, that doesn't seem to work.
This has changed slightly in rails 4 and above :distinct => true is now deprecated. Use:
Record.distinct.count('date')
Or if you want the date and the number:
Record.group(:date).distinct.count(:date)
What you're going for is the following SQL:
SELECT COUNT(DISTINCT date) FROM records
ActiveRecord has this built in:
Record.count('date', :distinct => true)
Outside of SQL:
Record.find(:all).group_by(&:date).count
ActiveSupport's Enumerable#group_by is indispensable.
the latest #count on rails source code only accept 1 parameter.
see: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-count
so I achieved the requirement by
Record.count('DISTINCT date')
Detailing the answer:
Post.create(:user_id => 1, :created_on => '2010-09-29')
Post.create(:user_id => 1, :created_on => '2010-09-29')
Post.create(:user_id => 2, :created_on => '2010-09-29')
Post.create(:user_id => null, :created_on => '2010-09-29')
Post.group(:created_on).count
# => {'2010-09-29' => 4}
Post.group(:created_on).count(:user_id)
# => {'2010-09-29' => 3}
Post.group(:created_on).count(:user_id, :distinct => true) # Rails <= 3
Post.group(:created_on).distinct.count(:user_id) # Rails = 4
# => {'2010-09-29' => 2}
As I mentioned here, in Rails 4, using (...).uniq.count(:user_id) as mentioned in other answers (for this question and elsewhere on SO) will actually lead to an extra DISTINCT being in the query:
SELECT DISTINCT COUNT(DISTINCT user_id) FROM ...
What we actually have to do is use a SQL string ourselves:
(...).count("DISTINCT user_id")
Which gives us:
SELECT COUNT(DISTINCT user_id) FROM ...
Also, make sure you have an index on the field in your db, or else that query will quickly become sloooow.
(It's much better to do this in SQL, otherwise you pull the entire db table into memory just to answer the count.)

Resources