Rails group_by id and group created - ruby-on-rails

I have a model call GcMission
it contains gamecharacter_id, mission_id and of course created_at.
Now I need to group_by gamecharacter_id, and then group the created_at to see how many mission_id per day.
I assum the result will be like
{
69 => {
'2017-08-18' => 1
},
75 => {
'2017-08-18' => 2
},
78 => {
'2017-08-18' => 1,
'2017-08-19' => 1,
}
}
But I am stuck at that.
Here is what I've written.
GcMission.select('gamecharacter_id, mission_id, created_at').where(mission_id: assign_mission_ids).where('finish_counter >= 1').group_by{|a| a.gamecharacter_id}
And the result is like
{
69 => [
[0] #<GcMission:0x007ff6c6a53650> {
:gamecharacter_id => 69,
:mission_id => 3,
:created_at => Tue, 18 Apr 2017 03:20:36 UTC +00:00
}
],
75 => [
[0] #<GcMission:0x007ff6c6a52d18> {
:gamecharacter_id => 75,
:mission_id => 3,
:created_at => Tue, 18 Apr 2017 06:38:27 UTC +00:00
},
[1] #<GcMission:0x007ff6c6a52408> {
:gamecharacter_id => 75,
:mission_id => 4,
:created_at => Tue, 18 Apr 2017 07:55:40 UTC +00:00
}
],
78 => [
[0] #<GcMission:0x007ff6c6a51be8> {
:gamecharacter_id => 78,
:mission_id => 3,
:created_at => Tue, 18 Apr 2017 17:29:24 UTC +00:00
},
[1] #<GcMission:0x007ff6c6a51580> {
:gamecharacter_id => 78,
:mission_id => 4,
:created_at => Wed, 19 Apr 2017 03:20:31 UTC +00:00
}
]
}

Try Following
gc_missions = GcMission.select("gamecharacter_id, DATE(created_at) AS date,
COUNT(mission_id) AS mission_count")
.group('gamecharacter_id, date')
If you want count of unique mission_id per gamecharacter_id per day use
COUNT(DISTINCT(mission_id))
You may like to verify using following code
gc_missions.each {|gc| puts "#{gc.gamecharacter_id}, #{gc.date}, #{gc.mission_count}"}

Related

Uniqueness validation not functioning as expected

I have a Call model with the following validations:
class Call < ActiveRecord::Base
validates_uniqueness_of :external_id, scope: :source
end
I generate new calls through a webhook that calls the following service:
class AircallWebhookService
include HubspotExtension
def initialize(params)
#event = params["event"]
#params = params["data"]
#call = nil
#aircall_number = nil
#employee_email = nil
end
def process
#call = Call.find_by(source: :aircall, external_id: #params["id"])
if #call.present?
p "Found existing call!"
else
p "Could not locate existing call."
#call = Call.new(source: :aircall, external_id: #params["id"])
end
#call.source = 1
#call.external_id = #params["id"]
#call.url = #params["direct_link"]
#call.direction = #params["direction"]
#call.status = #params["status"]
#call.missed_call_reason = #params["missed_call_reason"]
#call.started_at = Time.at(#params["started_at"]) if #params["started_at"].present?
#call.answered_at = Time.at(#params["answered_at"]) if #params["answered_at"].present?
#call.ended_at = Time.at(#params["ended_at"]) if #params["ended_at"].present?
#call.duration = #params["duration"]
#call.raw_digits = #params["raw_digits"]
#call.aircall_user_id = #params.dig("user", "id")
#call.contact_id = #params.dig("contact", "id")
#aircall_number = #params.dig("number", "digits").try{|n| n.gsub(/\s|-|\(|\)|\+/, "")}
#call.aircall_user_id = #params.dig("user", "id")
#employee_email = #params.dig("user", "email")
if !#params["tags"].empty?
mapTagToReferrer
end
#call.comments = mapComments
if #call.save
linkTagToCall
linkCallToEmployee
updateHubspotEngagement
end
end
...
end
For some reason, despite the uniqueness validation, I continue to see calls with the same external_id and source. For example these are 2 records in my DB:
[
[0] #<Call:0x000055d780f639b8> {
:id => 8149,
:location_id => nil,
:referrer => nil,
:consultation => nil,
:created_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:updated_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:worldwide => nil,
:external_id => 582402916,
:source => "aircall",
:direction => "inbound",
:started_at => Tue, 07 Sep 2021 15:41:03 EDT -04:00,
:answered_at => Tue, 07 Sep 2021 15:41:10 EDT -04:00,
:ended_at => Tue, 07 Sep 2021 15:41:57 EDT -04:00,
:duration => 54,
:status => "done",
:missed_call_reason => nil,
:aircall_user_id => 567754,
:contact_id => nil,
:comments => nil,
:lead_status => nil,
:call_type => "unknown"
},
[1] #<Call:0x000055d780f636e8> {
:id => 8150,
:location_id => nil,
:referrer => nil,
:consultation => nil,
:created_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:updated_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:worldwide => nil,
:external_id => 582402916,
:source => "aircall",
:direction => "inbound",
:started_at => Tue, 07 Sep 2021 15:41:03 EDT -04:00,
:answered_at => Tue, 07 Sep 2021 15:41:10 EDT -04:00,
:ended_at => Tue, 07 Sep 2021 15:41:57 EDT -04:00,
:duration => 54,
:status => "done",
:missed_call_reason => nil,
:aircall_user_id => 567754,
:contact_id => nil,
:comments => nil,
:lead_status => nil,
:call_type => "unknown"
}
]
They are identical and even the created_at is identical down to the millisecond. How is this possible?
Here's the controller in case it's necessary:
class API::WebhooksController < ApplicationController
def aircall_webhook
ac = AircallWebhookService.new(params)
ac.process
head :ok
end
end
validates_uniqueness_of doesn't actually guarantee that duplicate values cannot be inserted. It merely catches most of the cases where users input duplicated data and provides user feedback. Its very much prone to race conditions, and is foiled by stuff as simple as double clicking grannies.
If uniqueness is actually important you need to enforce it on the database layer with a unique index.
add_index :calls, [:external_id, :source], unique: true

Rails update massive data

I have one model call DataIndicator, it contains daily data,
And It has the following column.
:id => :integer,
:date => :datetime,
:dau => :integer,
:login_count => :integer
It had many data, but now I need to change some of it.
How do I massive update its value by date?
EX:
The original
{ "id" => 1, "date" => 2017-01-01 00:00:00 UTC, "dau" => 5 , "login_count" => 150 },
{ "id" => 2, "date" => 2017-01-02 00:00:00 UTC, "dau" => 5 , "login_count" => 140 },
{ "id" => 3, "date" => 2017-01-03 00:00:00 UTC, "dau" => 5 , "login_count" => 300 }
Now I have a hash value, which would be referred to modify the original data.
Like this
update_date = {
"2017-01-01" => {
"dau" => 5,
"login_count" => 5,
},
"2017-01-02" => {},
"2017-01-03" => {
"dau" => 5,
},
...
}
As you can see, the update_date will not contain all attributes, it may only have one or even zero new data.
What is the best way to update this value?
I can only think about the bad one.
Like this
update_date.each do |k, v|
data_by_date = DataIndicator.where(date: DateTime.parse(k)).first
next if data_by_date.nil?
data_by_date.update(v)
end
I think I misread your question.
Wouldn't it be easier if you just picked all DataIndicator records and then checked if the hash contained data for it?
DataIndicator.all.each do |di|
date = di.date.strftime("%Y-%m-%d")
to_update = update_date[date]
next if to_update.blank?
to_update.each do |field,value|
di.send("#{field}=".to_sym, value)
end
di.save!
end
This works for me.
For Rails 4+
DataIndicator.where(["date(date) = ?"], "2017-01-01").update_all(dau: 5, login_count: 5)

Sum of array in rails

I have a array suppose
[#<Data id: 1, date: "2016-01-06", value: "1">,
#<Data id: 2, date: "2015-12-31", value: "3">,
#<Data id: 3, date: "2016-01-06", value: "6">...]
and so on..
I want to sum the values having same date
i.e here first and third record are of same date so the result array will give
#<Data id: 1, date: "2016-01-06", value: "1">,
#<Data id: 3, date: "2016-01-06", value: "7">,
Hey you can use try this way if you have already fetch an array from database
arr.group_by{|a| a.date.to_date}.map{|k,v| {k => v.map(&:value).sum()}}
If you are are not fetch array/active record from database you can directly use database query as
If your database stores only date then you can use
Model.group("date").sum(:value)
If your database stores date with time here i have use DATE_FORMAT function for skipping Time part of date
Model.group("DATE_FORMAT(date, '%Y%m%d')").sum(:value)
You can use sql groupping on the model:
Data.where(date:(42.days.ago..Date.today)).group(:date).sum(:value)
This will return a hash of {date => sum}
On an array:
Hash[your_array.group_by(&:date).map{|k,v| [k, v.sum(&:value)]}]
sum = Hash.new(0)
array.each do |data|
<p>sum[data[:date]] += data[:value]</p>
end
# => {:id => 1, "Wed, 04 May 2011" => 300, "Tue, 03 May 2011" => 450...}
# => If you then want this in the same array format you started with:
new_array = sum.collect{ |key, value| {:date => key, :value => value} }
# => [{:id => 1,:date => "Wed, 04 May 2011", :value => 300}, {....}]

Testing for FactoryGirl Results

I'm trying to test to see if the items the array exist after I create the factory.
spec/models/thing_spec.rb
require 'rails_helper'
RSpec.describe Thing, :type => :model do
let(:thing) { Array.new(3) {FactoryGirl.create(:thing) } }
it "should sort the items in order" do
expect(thing).to include(ordering:1, ordering:2, ordering:3)
end
end
spec/factories/things.rb
FactoryGirl.define do
factory :thing, :class => 'Thing' do
name "item_name"
sequence(:ordering)
end
end
Below are the results that I received.
results
1) Things should be sorted in order
Failure/Error: expect(thing).to include(ordering:1, ordering:2, ordering:3)
expected [#<Thing id: 1, name: "item_name", create_date: "2014-11-07 04:18:17", modified_date: "2014-11-14 04:18:17", ordering: 1>, #<Thing id: 2, name: "item_name", create_date: "2014-11-07 04:18:17", modified_date: "2014-11-14 04:18:17", ordering: 2>, #<Thing id: 3, name: "item_name", create_date: "2014-11-07 04:18:17", modified_date: "2014-11-14 04:18:17", ordering: 3>] to include {:ordering => 2}
Diff:
## -1,2 +1,19 ##
-[{:ordering=>2}]
+[#<Thing:0x007fb96217cc30
+ id: 1,
+ name: "item_name",
+ create_date: Fri, 07 Nov 2014 04:18:17 UTC +00:00,
+ modified_date: Fri, 14 Nov 2014 04:18:17 UTC +00:00,
+ ordering: 1>,
+ #<Thing:0x007fb9621cfca0
+ id: 2,
+ name: "item_name",
+ create_date: Fri, 07 Nov 2014 04:18:17 UTC +00:00,
+ modified_date: Fri, 14 Nov 2014 04:18:17 UTC +00:00,
+ ordering: 2>,
+ #<Thing:0x007fb96221eda0
+ id: 3,
+ name: "item_name",
+ create_date: Fri, 07 Nov 2014 04:18:17 UTC +00:00,
+ modified_date: Fri, 14 Nov 2014 04:18:17 UTC +00:00,
+ ordering: 3>]
You can't do it this way. You'll have to check each record individually like this
it "should sort the items in order" do
expect(thing[0].ordering).to eq(1)
expect(thing[1].ordering).to eq(2)
expect(thing[2].ordering).to eq(3)
end
Or do something like this:
it "should sort the items in order" do
expect(thing.map(&:ordering)).to eq([1, 2, 3])
end
You can only use include to check if the array includes an element as a whole, like this:
expect(thing).to include(thing[0])

Why ActiveRecord not receiving these getting aggregate functions

Doing a query with aggregate functions directly on ActiveRecord with Postgres seems to be working ok.
ActiveRecord::Base.connection.execute("
SELECT created_at::date as date,
sum(item1_count) as sum_item1,
sum(item2_count) as sum_item2,
sum(item3) as sum_item3 from items
GROUP by
created_at::date ORDER BY date desc").to_a
And returns something like this which is ok.
[
{
"date" => "2014-01-23",
"sum_item1" => "3239",
"sum_item2" => "90",
"sum_item3" => "0.00000"
},
{
"date" => "2014-01-22",
"sum_item1" => "1981",
"sum_item2" => "19",
"sum_item3" => "0.00000"
}
]
The problem is when trying to do the same using scopes, for instance.
class Item < ActiveRecord::Base
scope :myscope, -> {
select("created_at::date as date, sum(item1_count) as sum_item1,
sum(item2_count) as sum_item2,
sum(item3) as sum_item3")
.group("created_at::date")
.order("date desc") }
end
The result here is different. When running user.items.myscope.to_a I get the following result missing the aggregate values and adding an id field that should not be there.
[
#<Item:0x00000103cc3d38> {
:id => nil,
:date => Thu, 23 Jan 2014
},
#<Item:0x00000103cc39a0> {
:id => nil,
:date => Wed, 22 Jan 2014
}
]
How it would be possible to pass the aggregate functions to the scope?

Resources