ActiveRecord get average from group - ruby-on-rails

I am trying to write this query in ActiveRecord 4, but to no avail.
SELECT date, AVG(gain) AS avg_gain FROM counters WHERE ( date > '2014-03-03' ) GROUP BY date ORDER BY date DESC;
So I scrambled this together:
Counter.select("date, AVG(gain) as avg_gain").where("date > '2014-03-03'").group(:date).order(date: :desc)
=> #<ActiveRecord::Relation [#<Counter id: nil, date: "2014-04-01">, #<Counter id: nil, date: "2014-03-31">, #<Counter id: nil, date: "2014-03-30">, #<Counter id: nil, date: "2014-03-29">, #<Counter id: nil, date: "2014-03-28">, #<Counter id: nil, date: "2014-03-27">, #<Counter id: nil, date: "2014-03-26">, #<Counter id: nil, date: "2014-03-25">, #<Counter id: nil, date: "2014-03-24">, #<Counter id: nil, date: "2014-03-23">, ...]>
The only trouble is, the result does not contain avg_gain columns. Any ideas?

When you use select, the selected fields are added to the instances in the returned Relation. The returned result does not include those selected fields in the printed output.
Access these fields just as you would access a column attribute:
result = Counter.select("date, AVG(gain) as avg_gain").where("date > '2014-03-03'").group(:date).order(date: :desc)
result.first.avg_gain # Prints expected value for the first Counter

OP wants to see hash values for the items queried. Here are two options, but you should just use the first, the second is here as more of a warning or option when performance doesn't matter:
I'd highly recommend this option, due to performance reasons:
Counter.
where("date > '2014-03-03'").
group(:date).
order(date: :desc).
pluck("date, AVG(gain) as avg_gain").
map { |date, avg_gain| {date: date, avg_gain: avg_gain} }
The solution below looks nice but is about 10x slower. This is because as_json will instantiate a model object for every result:
The Active Model JSON Serializer method, as_json will also return an array of hashes. By default, each result will also include "id"=>nil if you don't use the only: except: option.
Counter.
select("date, AVG(gain) as avg_gain").
where("date > '2014-03-03'").
group(:date).
order(date: :desc).
as_json(except: [:id])

Related

Rspec - archives array to match tested array

I'm having this class method on my Post model for getting archives
def self.archives
Post.unscoped.select("YEAR(created_at) AS year, MONTHNAME(created_at) AS month, COUNT(id) AS total")
.group("year, month, MONTH(created_at)")
.order("year DESC, MONTH(created_at) DESC")
end
This is the test I have wrote for my method
context '.archives' do
first = FactoryGirl.create(:post, published_at: Time.zone.now)
second = FactoryGirl.create(:post, published_at: 1.month.ago)
it 'returns articles archived' do
archives = Post.archives()
expect(
[{
year: first.published_at.strftime("%Y"),
month: first.published_at.strftime("%B"),
published: 1
},
{
year: second.published_at.strftime("%Y"),
month: second.published_at.strftime("%B"),
published: 1
}]
).to match_array(archives)
end
end
However I get the following error
expected collection contained: [#<Post id: nil>, #<Post id: nil>]
actual collection contained: [{:year=>"2017", :month=>"October", :published=>1}, {:year=>"2017", :month=>"September", :total=>1}]
the missing elements were: [#<Post id: nil>, #<Post id: nil>]
the extra elements were: [{:year=>"2017", :month=>"October", :total=>1}, {:year=>"2017", :month=>"September", :total=>1}]
So although I have created 2 factories, the archives array is empty. What am I doing wrong?
Rspec standard is to use the let syntax for defining variables within a context or describe block. The test should look something like this:
describe '.archives' do
let!(:first) { FactoryGirl.create(:post, published_at: Time.zone.now) }
let!(:second) { FactoryGirl.create(:post, published_at: 1.month.ago) }
it 'returns year, month, and total for articles archived' do
actual_attributes = Post.archives.map { |post| [post.year, post.month, post.total] }
expected_total = 1 # I don't know why the query is returning 1 for total, but including this for completeness
expected_attributes = [first, second].map { |post| [post.created_at.year, post.created_at.strftime("%B"), expected_total] }
expect(actual_attributes).to match_array(expected_attributes)
end
end
The issue here is that you are comparing records pulled with only a few attributes (the result of your SQL query) with fully-formed records (created by your test). This test pulls the applicable attributes from both groups and compares them.
Actual array is not empty, it's an array of two Post instances with ids unset (because Select in .archives method doesn't contain id field).
You could compare expected hashes not with archives, but with smth like that:
actual = Post.archives().map do |post|
{ year: post["year"].to_s, month: post["month"], published: post["total"] }
end
expected = [{
year: first.published_at.strftime("%Y").to_s,
month: first.published_at.strftime("%B"),
published: 1
},
{
year: second.published_at.strftime("%Y").to_s,
month: second.published_at.strftime("%B"),
published: 1
}]
expect(actual).to match_array(expected)

how to make an array into array of arrays in ruby?

I have a query company_in_outs = company.in_outs.where('date >= ? and date <= ?', Date.today.at_beginning_of_month, Date.today.end_of_month) which is returning me <ActiveRecord::AssociationRelation. now i want to make this array into an array of arrays, those are grouped based on the date value.
for example.
#<ActiveRecord::AssociationRelation [#<InOut id: 2806, date: "2016-06-01", created_at: "2016-06-02 07:01:52", updated_at: "2016-06-16 07:43:45", company_id: 1>,#<InOut id: 2806, date: "2016-06-01", created_at: "2016-06-02 07:01:52", updated_at: "2016-06-16 07:43:45", company_id: 1>,#<InOut id: 2806, date: "2016-06-01", created_at: "2016-06-02 07:01:52", updated_at: "2016-06-16 07:43:45", company_id: 1>,#<InOut id: 2806, date: "2016-06-02", created_at: "2016-06-02 07:01:52", updated_at: "2016-06-16 07:43:45", company_id: 1>,#<InOut id: 2806, date: "2016-06-02", created_at: "2016-06-02 07:01:52", updated_at: "2016-06-16 07:43:45", company_id: 1>,#<InOut id: 2806, check_in: "2016-06-24 16:16:00", check_out: "2016-06-25 01:16:00", date: "2016-06-01", created_at: "2016-06-02 07:01:52", updated_at: "2016-06-16 07:43:45", company_id: 1> ]
from this array i want to make an array of arrays, which needs to be grouped based on the date. meaning all records which contains same date needs to be grouped as one array. like this i want to have an array of arrays.
If you already have all the records loaded:
company_in_outs.each_with_object({}) do |record, hash|
(hash[record.date] ||= []) << record
end.values
if you aren't using Rails (ActiveSupport) you should use inject instead of each_with_object
company_in_outs.inject({}) do |hash, record|
(hash[record.date] ||= []) << record
hash
end.values
I think you should be able to done sorting on db site too.
There is actually a group_by method in Array, but instead of returning array of array, it returns an object with key and array of the same key as value.
In your case:
company_in_outs.group_by do |com|
com.date
end
The code above will return an object grouped by date as a key, and array of the same date as value.
You could loop through the returning object which will return key and value, and do whatever you like :)
Hope this helps!

Rails - how to fetch from ActiveRecord object only specific records?

I get this from an ActiveRecord call:
#<ActiveRecord::Associations::CollectionProxy [
#<CarService id: nil, car_id: nil, car_service: 1,
created_at: nil, updated_at: nil, car_type: 0>,
#<CarService id: nil, car_id: nil, car_service: 11,
created_at: nil, updated_at: nil, car_type: 1>]>
Once I get this, I need to filter only records where car_type = "0". How to do that without doing another database call (WHERE car_type = "0")?
Thank you in advance.
EDIT:
this:
car.car_services.select{|key, hash| hash['car_type'] == "1" }
does not work.
just convert your result to an array then filter it like this
result = car.car_services.to_a.select do |e|
e.car_type == "0"
end
You can use scope in CarService model:
scope :type_ones, -> { where(car_type: 1) }
and you can use it like this:
car.car_services.type_ones
If you use enum, it will be better. Because the enum creates to scopes automatically instead of you. And of course it has more features. More about the enum.

'Where-in' query in postgresql for two different queries showing same result

I have the active record assosciation relation as follows.
#tasks = #<ActiveRecord::AssociationRelation [#<Task id: 3130, title: "Commit to at least one small win today", content: "When I check-in on the app it lets me acknowledge m...",created_at: "2016-01-13 01:36:15", updated_at: "2016-01-13 04:47:57", state: "active", #<Task id: 3131, title: "Purposefully walk 3 minutes ", content: "More than just my ordinary day, I choose 5 minutes ...", created_at: "2016-01-13 04:52:32", updated_at: "2016-01-13 04:56:22", state: "active", #<Task id: 3132, title: "1km Walk or Run by Sunday", content: "I pick a direction, start with a 10 minute warm up,...", created_at: "2016-01-13 04:56:05", updated_at: "2016-01-13 04:56:05", state: "active",#<Task id: 3249, title: "1km Walk or Run by Wednesday", content: "I pick a direction, start with a 10 minute warm up,...", created_at: "2016-01-24 23:23:34", updated_at: "2016-01-24 23:23:34", state: "active"]>
#array = []
#tasks.each do |task|
if (condition)
#array << task.id
end
#tasks = #tasks.where.not('tasks.id in (?)',#array)
If I get any non empty value in #array, the above condition is working fine. If I get #array = [] i,e empty array,
#tasks = #tasks.where.not('tasks.id in (?)',#array) is not giving me correct result.
Also, #tasks = #tasks.where('tasks.id in (?)',#array), this condition by removing 'not' giving the same result when not is present when the array is []
#habits = #habits.where.not('habits.id in (?)',#id_s) ====> output => []
#habits = #habits.where('habits.id in (?)',#id_s) ====> output => []
They both are returning same optput if #id_s is []
Why these queries are returning the same value for two different conditions?
If your Rails version is up to date you should switch to the hash notation, which handles all special cases like empty arrays for you:
#tasks = #tasks.where.not(id: #array)

Delete a record if nil is returned

I am making requests to the Facebook API and some of the responses are empty/nil and I am wondering how I can delete these so that when I save them to my model I don't have any nil entries.
def formatted_data
for record in results['data'] do
attrs = {
message: record['message'],
picture: record['picture'],
link: record['link'],
object_id: record['object_id'],
description: record['description'],
created_time: record['created_time']
}
attrs.delete_if { |x| x.nil? }
Post.where(attrs).first_or_create! do |post|
post.attributes = attrs
end
end
As you can see I am trying to use the delete_if method but it's not working.
Here's an example of a response that I would like to delete:
id: 45
message:
picture:
link:
object_id:
large_image_url:
description:
created_time: 2014-04-12 11:38:02.000000000 Z
created_at: 2014-05-01 10:27:00.000000000 Z
updated_at: 2014-05-01 10:27:00.000000000 Z
This kind of record is no good to me as it has no message, so maybe I could make the query specify if message.nil ? then delete
Edit
Been reading the delete_if docs and after iceman's comment, I thought this would work but it doesn't, though it seems closer to what I want:
attrs = attrs.delete_if {|key, value| key = 'message', value = nil }
There are about 25 records returned, of which 5 should be deleted, but after running the above I get one result left in the model:
[#<Post id: 81, message: nil, picture: nil, link: nil, object_id: nil, large_image_url: nil, description: nil, created_time: nil, created_at: "2014-05-01 11:22:40", updated_at: "2014-05-01 11:22:40">]
Why are all the rest being deleted, maybe my syntax for accessing the key is incorrect?
Since #delete_if passes into block two arguments: the key, and value, try this usage:
attrs.delete_if { |k,v| v.nil? }
and for ruby-on-rails you can remove all blank lines, i.e. nil, and empty:
attrs.delete_if { |k,v| v.blank? }
Im adding this in that someone could provide a more efficient way of doing this, maybe before the records get written to the model..But i have managed a solution, albeit a hacky one i would say
I have added this after the creation of the posts
delete_if_nil = Post.where(message: nil)
delete_if_nil.destroy_all
Its another query on the db which isnt ideal i guess
Any other suggestions appreciated

Resources