Rails 5 - Attribute update if relative model's attribute is different - ruby-on-rails

I've created an attribute that is supposed to update a datetime field with the value of a child model's attribute if it's newer (or older based on which field)
def update_project_record
client = Client.find(project.client_id)
puts client
project.start_date = self.task_start_date if self.task_start_date < project.start_date
project.end_date = self.task_end_date if self.task_end_date > project.end_date
if client.nearest_project_start.present?
client.assign_attributes(:nearest_project_start => self.task_start_date, :nearest_project_id => self.project_id) if self.task_start_date < client.nearest_project_start
else
client.assign_attributes(:nearest_project_start => self.task_start_date, :nearest_project_id => self.project_id)
end
if client.furthest_project_end.present?
client.assign_attributes(:furthest_project_end => self.task_end_date, :furthest_project_id => self.project_id) if self.task_end_date > client.furthest_project_end
else
client.assign_attributes(:furthest_project_end => self.task_end_date, :furthest_project_id => self.project_id)
end
client.save
end
It's messy, but it is working within the app.
A Client has many Projects which has many Tasks and a Client has many Tasks through Projects
However, when I try to seed the DB I am seeing an output similar to:
SDK Enhancement
#<Client:0x007fa89d33bda8>
#<Client:0x007fa89d31a108>
Task 1
#<Client:0x007fa89d2c1580>
#<Client:0x007fa89d2a0880>
Task 3
#<Client:0x007fa89d2488b0>
#<Client:0x007fa89d21bae0>
Task 4
#<Client:0x007fa89d1bfbc8>
#<Client:0x007fa89d19eec8>
Task 5
#<Client:0x007fa89d142e98>
#<Client:0x007fa89d11e0c0>
Task 6
#<Client:0x007fa89d0c1f78>
#<Client:0x007fa89d0a1278>
Task 7
#<Client:0x007fa89d049208>
#<Client:0x007fa89d024430>
Task 8
#<Client:0x007fa89cfcc3c0>
#<Client:0x007fa89cfa3588>
Task 9
#<Client:0x007fa89cf4b478>
#<Client:0x007fa89cf2a5c0>
Task 17
#<Client:0x007fa89ced2438>
#<Client:0x007fa89ceb1670>
Task 28
#<Client:0x007fa89ce59600>
#<Client:0x007fa89ce348a0>
Task 39
Which seems like it's pulling the Client but not updating the record? (Unsure how to test this theory).
I then went into the app and make a Client -> Project -> Task as a user would and all records look as if I would expect, but it seems like Seeding ignores this function, but it IS updating the Project portion.
Seed File
task_list = [
['Task 1', 'Nothing to add', '2017-09-01', '2018-01-01', 1, 1, 0],
['Task 3', 'Nothing to add', '2017-08-01', '2017-10-01', 1, 2, 1],
['Task 4', 'Nothing to add', '2017-06-01', '2017-09-01', 1, 3, 1],
['Task 5', 'Nothing to add', '2017-11-01', '2017-12-01', 1, 4, 0],
['Task 6', 'Nothing to add', '2017-08-01', '2018-02-01', 1, 5, 0],
['Task 7', 'Nothing to add', '2017-08-01', '2017-12-01', 1, 6, 0],
['Task 8', 'Nothing to add', '2017-11-01', '2017-12-15', 1, 7, 0],
['Task 9', 'Nothing to add', '2017-07-01', '2017-08-15', 1, 1, 0],
['Task 17', 'Nothing to add', '2017-04-01', '2017-08-01', 1, 6, 1],
['Task 28', 'Nothing to add', '2017-08-01', '2017-10-01', 1, 7, 0],
['Task 39', 'Nothing to add', '2018-01-01', '2018-04-01', 1, 1, 1]
]
task_list.each do |name, comment, start, end_date, project, product, completed|
dproject = Project.find(project)
dproject.tasks.create!(task_name: name, comment: comment, task_start_date: start, task_end_date: end_date, project_id: project, product: product, completed: completed)
puts name
end
I originally didn't have the project assignment line but I was seeing if perhaps that resolved the MIA Client details.
Tl;dr
If a Task is created and has a later end_date than the Client's furthest_project_end datefield, it should update that field. It is not currently updating. It IS however updating the project.end_date

Related

Hash with 3 different keys each pointing to an array of instances. How do I sort the arrays by id?

I have a hash like so:
def my_requests
result = {
accepted: [],
rejected: [],
pending: [],
}
self.requests.each do |request|
serialized_request = RequestSerializer.new(request)
if request.accept == nil
result[:pending].push(serialized_request)
elsif request.accept
result[:accepted].push(serialized_request)
else
result[:rejected].push(serialized_request)
end
end
result
end
I will have a logged in user. I am trying to organize the logged in user's availabilities by id.
How do I sort each array by id. I know if this was just an array I can do:
array.sort_by{|request| request.id}
But how do I iterate through each key's array? I've tried multiple different ways and the only one that works is if I end up mapping over the hash and then another loop to sort the requests. But that doesn't return a hash. Is there a way for me to keep the structure and sort it?
The availabilities serializer is below:
class RequestSerializer < ActiveModel::Serializer
attributes :id, :start_time, :payment, :number_of_hours, :availability_id, :date, :name, :accept, :postcode, :phone_number
end
Below is one of the key, value pair outputs.
:rejected=>[#<RequestSerializer:0x00007fa416e168a8 #object=#<Request id: 64, payment: 200, number_of_hours: 20, accept: false, start_time: "2000-01-01 16:20:00", venue_id: 1, availability_id: 4, created_at: "2020-08-30 12:15:04", updated_at: "2020-08-30 12:15:52">, #instance_options={}, #root=nil, #scope=nil>, #<RequestSerializer:0x00007fa416e167b8 #object=#<Request id: 4, payment: 160, number_of_hours: 4, accept: false, start_time: "2000-01-01 16:15:00", venue_id: 2, availability_id: 5, created_at: "2020-06-17 21:19:07", updated_at: "2020-06-17 21:21:32">, #instance_options={}, #root=nil, #scope=nil>, #<RequestSerializer:0x00007fa416e166c8 #object=#<Request id: 71, payment: 100, number_of_hours: 1, accept: false, start_time: "2000-01-01 09:45:00", venue_id: 1, availability_id: 6, created_at: "2020-10-01 08:45:43", updated_at: "2020-10-01 08:46:04">, #instance_options={}, #root=nil, #scope=nil>, #<RequestSerializer:0x00007fa416e16560 #object=#<Request id: 66, payment: 30, number_of_hours: 3, accept: false, start_time: "2000-01-01 16:30:00", venue_id: 1, availability_id: 26, created_at: "2020-08-30 12:31:02", updated_at: "2020-08-30 12:32:10">, #instance_options={}, #root=nil, #scope=nil>, #<RequestSerializer:0x00007fa416e163f8 #object=#<Request id: 68, payment: 20, number_of_hours: 3, accept: false, start_time: "2000-01-01 12:00:00", venue_id: 1, availability_id: 28, created_at: "2020-09-01 08:17:26", updated_at: "2020-09-01 13:09:54">, #instance_options={}, #root=nil, #scope=nil>]
Thanks!
result.transform_values { |array| array.sort_by(&:request_id) }
If the arrays are not not arrays of requests, but RequestSerializer, just call .object on them to get the request to sort by.
result.transform_values do |array|
array.sort_by { |serializer| serializer.object.request_id }
end
Another option would be to define request_id on RequestSerializer
You have to sort each hash value separately:
result.each_value { |array| array.sort_by!(&:id) }
Hash#each_value traverses the values and sort_by! sorts the array in-place.
If you need to create a new sorted copy:
result.each_with_object({}) do |(key, value), list|
list[key] = value.sort_by(&:id)
end
As Stefan posted in the comments. I can use a database query to order the requests prior to splitting them into their arrays.
The answer that worked (without doing multiple loops) was:
def my_requests
result = {
accepted: [],
rejected: [],
pending: [],
}
requests.order(:id).each do |request|
serialized_request = RequestSerializer.new(request)
if request.accept == nil
result[:pending].push(serialized_request)
elsif request.accept
result[:accepted].push(serialized_request)
else
result[:rejected].push(serialized_request)
end
end
result
end
Simply by removing self. and using the .order query and the id attribute, everything comes out ordered!
Thanks Stefan! (and everyone else)
(Others worked with multiple methods or loops but as I was using Rails, the query is the quickest and easiest).

Improving performance of a Rails controller method that returns a serialized hash

Would anyone be willing to give me advice on how I can improve the performance of the following controller method?
def index
#contacts = Hash[current_user.company.contacts.map {|contact| [contact.id, ContactSerializer.new(contact).as_json[:contact]] }]
respond_to do |format|
format.json { render json: { contacts: #contacts } }
end
end
This returns the following data structure:
{
contacts: {
79: {
id: 79,
first_name: "Foo",
last_name: "Bar",
email: "t#t.co",
engagement: "0%",
company_id: 94,
created_at: " 9:41AM Jan 30, 2016",
updated_at: "10:57AM Feb 23, 2016",
published_response_count: 0,
groups: {
test: true,
test23: false,
Test222: false,
Last: false
},
invites: [
{
id: 112,
email: "t#t.co",
status: "Requested",
created_at: "Jan 30, 2016, 8:48 PM",
date_submitted: null,
response: null
}
],
responses: [ ],
promotions: [
{
id: 26,
company_id: 94,
key: "e5cb3bc80b58c29df8a61231d0",
updated_at: "Feb 11, 2016, 2:45 PM",
read: null,
social_media_posts: [ ]
}
]
},
81: {
id: 81,
first_name: "Foo2",
last_name: "Bar2",
email: "foobar2#foobar.com",
engagement: "0%",
company_id: 94,
created_at: "12:55PM Feb 04, 2016",
updated_at: " 4:25PM Feb 19, 2016",
published_response_count: 0,
groups: {
test: true,
test23: true,
Test222: false,
Last: false
},
invites: [
{
id: 116,
email: "foobar2#foobar.com",
status: "Requested",
created_at: "Feb 22, 2016, 9:10 PM",
date_submitted: null,
response: null
}
],
responses: [ ],
promotions: [
{
id: 26,
company_id: 94,
key: "e5cb3bc80b58c29df8a61231d0",
updated_at: "Feb 11, 2016, 2:45 PM",
read: null,
social_media_posts: [ ]
}
]
}
}
}
I need the index method to return a hash where the the keys are the contact IDs, as opposed to an Array, which is what would normally be returned. Additionally, I pass each contact through the serializer so that I get all associated data that my client needs.
This method works fine when there are only a few contacts, however when I have 100 or 1000, it really slows down. I benchmarked it with 100 contacts and it took 4 seconds to finish, which is abysmal. I'm wondering how I can improve my code to get the exact same output in a more performant manner. The key here is that the output needs to remain unchanged. I have no interest in modifying the client-side code (it depends on this data structure for numerous applications), so all changes need to occur on the server-side.
Here is my ContactSerializer for reference:
class ContactSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :email, :engagement, :company_id, :created_at, :updated_at, :published_response_count, :groups
has_many :invites
has_many :responses
has_many :promotions
def groups
Hash[object.company.groups.map {|group| [group.name, object.groups.include?(group)] }]
end
def published_response_count
object.responses.where(published: true).count
end
def created_at
object.created_at.in_time_zone("Eastern Time (US & Canada)").strftime("%l:%M%p %b %d, %Y")
end
def updated_at
object.updated_at.in_time_zone("Eastern Time (US & Canada)").strftime("%l:%M%p %b %d, %Y")
end
def engagement
object.engagement
end
end
For what it's worth, I am fully aware that returning JSON data like this from Rails is not a great practice and have since moved away from it completely. Unfortunately this piece of code was written quite awhile ago and I can't afford the time to do a full rewrite of the client side to consume a standard output such as an array of contacts.
I started looking into the queries that we're being generated by ActiveRecord.
I realized after a while though that the queries were only accounting for a few ms of the total processing time. I started benchmarking the code starting with the base query company.contacts and then gradually adding on methods I had, such as map and then passing each contact into the serializer, and finally calling as_json[:contact] on the object returned by ContactSerializer.new(contact).
What I found is that calling as_json[:contact] on the serialized object was consuming an average of about 30ms (averaged over 100 runs) per contact. The reason I had that was to remove the root contact node from the JSON that was returned.
When I benchmarked my original code with 198 contacts, it took an average of 10400ms over 10 runs. When I removed as_json[:contact] and set root false on ContactSerializer, as described by "Abusing ActiveModel::Serializers for HAL", I was able to cut the time down from 10400ms to 87ms, while returning the exact same structure to the client, which is astounding.
It might be possible to shave a few ms off that with some query optimizations, but anything else at this point is just icing on the cake.

Most efficient way to convert a ruby hash with array as keys to one with single values as keys

I'm stuck on what is the best way to re-arrange my ruby hash.
The main goal is to group results from mysql by month and count.
To do it, i make this request:
#data = Model.find(params[:id])
.jobs
.group('year(created_at)')
.group('month(created_at)')
.count(:id)
Which gives me:
#=> {[2013, 12]=>9, [2014, 1]=>4, [2014, 3]=>3,
# [2014, 4]=>1, [2014, 6]=>1, [2014, 7]=>1, [2014, 10]=>2}
I'm trying to have a cleaner hash or array to convert to json, where the years are not duplicated.
How can I have something workable like {"Year" => [Month,Count value]}? (or other form)
Idea? (I run ruby 2.2)
The answer proposed by #mudosobwa might be correct, but because your target is a json file, you may want some 'named' keys. I suggest you this one :
formated_results = #data.group_by{|k, v| k[0]}.collect{|k,v| {year: k, datas: v.collect{|vv| {month: vv.first.last, count: vv.last}}}}
# {:year=>2013, :datas=>[{:month=>12, :count=>9}]}
# {:year=>2014, :datas=>[{:month=>1, :count=>4}, {:month=>3, :count=>3}, {:month=>4, :count=>1}, {:month=>6, :count=>1}, {:month=>7, :count=>1}, {:month=>10, :count=>2}]}
EDIT : An other solution without the group_by method :
formated_results = Hash.new{|h,k| h[k] = []}
#data.each{|k,v| formated_results[k[0]] << {month: k[1], count: v}}
formated_results = formated_results.collect{|k, v| {year: k, datas: v}}
# {:year=>2013, :datas=>[{:month=>12, :count=>9}]}
# {:year=>2014, :datas=>[{:month=>1, :count=>4}, {:month=>3, :count=>3}, {:month=>4, :count=>1}, {:month=>6, :count=>1}, {:month=>7, :count=>1}, {:month=>10, :count=>2}]}
Then you just have to
formated_results.to_json
The json result shall be :
[
{
"year": 2013,
"datas": [
{ "month": 12, "count": 9 }
]
},
{
"year": 2014,
"datas": [
{ "month": 1, "count": 4 },
{ "month": 3, "count": 3 },
{ "month": 4, "count": 1 },
{ "month": 6, "count": 1 },
{ "month": 7, "count": 1 },
{ "month": 10, "count": 2 }
]
}
]
Given you already have your hash in #data, you might:
#data.inject({}) do |memo, ((y, m), cnt)|
(memo[y] ||= {})[m] = cnt
memo
end
#⇒ {
# 2013 => {12 => 9},
# 2014 => {1 => 4, 10 => 2, 3 => 3, 4 => 1, 6 => 1, 7 => 1}
# }
As it was noted by #Surya in comments, it should be hash Year => Hash[Month, Count]. While you still want to have arrays of Month, Count:
#data.inject({}) do |memo, ((y, m), cnt)|
memo[y] ||= []
memo[y] << [m, cnt]
memo
end
To json:
require 'json'
result.to_json
#=> "{\"2013\":{\"12\":9},\"2014\":{\"1\":4,\"3\":3,\"4\":1,\"6\":1,\"7\":1,\"10\":2}}"
#data.to_a.group_by{|ym, c| ym.first }.map{|year, months| [year,months.map{|m,cnt| [m.last, cnt] }] }.to_h
=> {2013=>[[12, 9]], 2014=>[[1, 4], [3, 3], [4, 1], [6, 1], [7, 1], [10, 2]]}

rails grouping an array of hashes

i know this is a simple question but its so frustating for me, i tried many hours and i didnt make it, so i hope can find the answer,
i have array of hashes like this
array = [{date: 1, branch: 1, value: "100"}, {date: 1, branch: 2, value: "200"}, {date: 2, branch: 1, value: "500"}, {date: 3, branch: 2, value: "500"}, {date: 3, branch: 3, value: "300"}]
and i want to grouping it like this
data = [{date: 1, 1: "100", 2: "200"}, {date: 2, 1: "500"}, {date: 3, 2: "500", 3: "300"}]
in the array = [{date: 1, branch: 1, value: "100"}, i want to take the value of branch and value and combine it like this 1: "100"
anyway i can do that, there is no problem if there is using loop or case when method
Probably could be shortened, but it works:
array.group_by { |h| h[:date] }.map do |k, v|
[:date, k, *v.map { |h| [h[:branch], h[:value]] }]
end.map { |x| Hash[*x.flatten] }
# => [{:date=>1, 1=>"100", 2=>"200"}, {:date=>2, 1=>"500"}, {:date=>3, 2=>"500", 3=>"300"}]
Another solution
array = [{date: 1, branch: 1, value: "100"}, {date: 1, branch: 2, value: "200"}, {date: 2, branch: 1, value: "500"}, {date: 3, branch: 2, value: "500"}, {date: 3, branch: 3, value: "300"}]
data = array.inject({}) do |res, val|
if res[val[:date]]
res[val[:date]].merge!({val[:branch] => val[:value]})
else
res.merge!(val[:date] => {val[:branch] => val[:value]})
end
res
end
puts data.collect{|key, val| {date: key}.merge!(val)}

Rails get record count from array

I use the following query to return a list of records
Rating.find(:all, :conditions => ["rating_set = ? and product_id = ?", 1, 2186417])
which returns:
[#<Rating id: 5, label: "Good", rating: 3.0, created_at: "2013-02-20 08:11:36", updated_at: "2013-02-20 08:11:36", recommendation_id: 2186417, notes: "exact match", rating_set: 1, product_id: 2186417>, #<Rating id: 6, label: "Good", rating: 3.0, created_at: "2013-02-20 08:11:36", updated_at: "2013-02-20 08:11:36", recommendation_id: 2054442, notes: "", rating_set: 1, product_id: 2186417>, #<Rating id: 7, label: "Fair", rating: 2.0, created_at: "2013-02-20 08:11:36", updated_at: "2013-02-20 08:11:36", recommendation_id: 2403501, notes: "", rating_set: 1, product_id: 2186417>, #<Rating id: 8, label: "Bad", rating: 3.0, created_at: "2013-02-20 08:11:36", updated_at: "2013-02-20 08:11:36", recommendation_id: 2344645, notes: "", rating_set: 1, product_id: 2186417>]
How can I get a count for each rating label. For example, how many records out of the total are "Good" or how many are "Bad" etc.
You can do that in at least 2 ways.
SQL
klass = Rating.where(rating_set: 1, product_id: 2186417])
good_count = klass.where(label: 'Good').count
bad_count = klass.where(label: 'Bad').count
Array
ratings = Rating.where(rating_set: 1, product_id: 2186417]).all
good_count = ratings.count { |r| r.label == 'Good' }
bad_count = ratings.count { |r| r.label == 'Bad' }
You could try a group by:
Rating.where(:rating_set => 1, :product_id => 2186417).group(:label).count.each{ |k,v| puts "#{k} #{v}" }
Resource: http://guides.rubyonrails.org/active_record_querying.html#group

Resources