I'm trying to figure out if there's a better way to run the code below.
Basically Course
is an association of Student
and courses_needed
is an array of Course
where I have many Students have and need many Courses and I'm trying to figure out the demand.
I've came up with the following code and it runs, which is fine, but when I want to display it on a statistics page with 36 Courses, the same code needs to be run once for each Course.
I've solved it now by having it run once a day, instead of real-time.
def self.calculate_needed
Course.all.each do |course|
neededArray = Array.new
needed = 0
Student.all.each do |s|
unless s.courses_needed.nil?
s.courses_needed.each do |c|
if c == course
needed += 1
end
end
end
end
course.needed = needed
course.save
end
end
-
\$\begingroup\$ Your database can produce this info in realtime with a single query, without using any Ruby. Please add the relationship (e.g., 'has_many') of Course and Student. \$\endgroup\$Dan Kohn– Dan Kohn2016年03月25日 11:24:18 +00:00Commented Mar 25, 2016 at 11:24
-
\$\begingroup\$ This relationship is already added. What I want is an integer, so I can enter it in Morris Charts. How would I produce this query in Ruby on Rails? \$\endgroup\$Ramsy de Vos– Ramsy de Vos2016年03月25日 12:21:06 +00:00Commented Mar 25, 2016 at 12:21
1 Answer 1
This should be interesting. The fastest way I can think of is to pluck all the courses_needed arrays out of Student, group them together, and count them:
course_hash = Student.where.not(courses_needed: nil).pluck(:courses_needed).
flatten.group_by { |c| c }.map { |k, v| [k, v.size] }.to_h
Essentially, I grab all students that actually have courses_needed, I then grab just all the courses needed from students. Flatten the course arrays so that all courses are within the same array, then I group them by course. After that I map again to replace the values (just the list of all same courses) with the number of times each course is listed. Then I do to_h to turn each [course, number] into a course => number hash that you can access easier.
So pluck returns:
[[course, course2, course3], [course, course2]]
Flatten makes this:
[course, course2, course3, course, course2]
Group_by turns it into:
{course => [course, course], course2 => [course2, course2], course3 => [course3]}
Map does:
[[course, 2], [course2, 2] , [course3, 1]]
Then to_h:
{course => 2, course2 => 2, course3 => 1}
At that point you can access the course hash and get the number.
course_hash[course]
# 2
Now that we have the hash we can update the needed column in Course:
Course.find_each do |course|
course.update_attributes(needed: course_hash[course].to_i)
end
The to_i is used so that if course_hash does not contain the course it returns nil, then to_i will turn nil into 0.