2
\$\begingroup\$

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
\$\endgroup\$
2
  • \$\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
    Commented 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\$ Commented Mar 25, 2016 at 12:21

1 Answer 1

1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.