Как получить все записи, которые также "владеют" указанными c записями - PullRequest
1 голос
/ 01 февраля 2020

У меня есть следующие две модели в приложении Rails

class Students < ApplicationRecord
  has_many :courses
  attr_accessor :name
end

class Courses < ApplicationRecord
  has_many :students
  attr_accessor :name, :course_id
end

Я хотел бы получить список всех курсов, общих для каждого студента, который был в одном классе с выбранным студентом эффективным способом .

С учетом следующих студентов:

  • Jerry's courses ["math", "english", "spanish"],
  • Bob's courses ["math", "english", "french"],
  • Frank's courses ["math", "basketweaving"]
  • Harry's courses ["basketweaving"]

Если выбранный студент был Джерри, я бы хотел, чтобы следующий объект был возвращен

{ Bob: ["math", "english"], Frank: ["math"] }

Я знаю, что это будет дорогостоящая операция, но моя интуиция говорит мне, что есть лучший способ, чем то, что я делаю. Вот что я попробовал:

# student with id:1 is selected student
courses = Student.find(1).courses

students_with_shared_classes = {}
courses.each do |course|
  students_in_course = Course.find(course.id).students
  students_in_course.each do |s|
    if students_with_shared_classes.key?(s.name)
      students_with_shared_classes[s.name].append(course.name)
    else
      students_with_shared_classes[s.name] = [course.name]
    end
  end
end

Есть ли какие-нибудь трюки ActiveRecord или SQL для такой ситуации?

Ответы [ 2 ]

2 голосов
/ 02 февраля 2020

Если вы настроили модель соединения (если требуется, если вы не используете has_and_belongs_to_many), вы можете запросить ее напрямую и использовать агрегат массива:

# given the student 'jerry'
enrollments = Enrollment.joins(:course)
                        .joins(:student)
                        .select(
                          'students.name AS student_name',
                          'array_agg(courses.name) AS course_names'
                        )
                        .where(course_id: jerry.courses)
                        .where.not(student_id: jerry.id)
                        .group(:student_id)

array_agg - это Postgres конкретная c функция. На MySQL и я верю Oracle вы можете использовать JSON_ARRAYAGG к тому же концу. SQLite имеет только group_concat, который возвращает строку, разделенную запятыми.

Если вы хотите получить га sh, вы можете сделать:

enrollments.each_with_object({}) do |e, hash|
  hash[e.student_name] = e.course_names
end

Эта опция не в качестве базы данных Независимо от того, что Гэвин Миллерс отлично ответит, но выполняет всю работу на стороне базы данных, чтобы вам не приходилось перебирать записи в ruby и сортировать курсы, которых у них нет общего.

2 голосов
/ 01 февраля 2020

Я думаю, что вы хотите сделать что-то вроде этого:

student_id = 1
courses = Student.find(student_id).courses

other_students = Student
  .join(:courses)
  .eager_load(:courses)
  .where(courses: courses)
  .not.where(id: student_id)

Это даст коллекцию других студентов, которые взяли courses только с двумя запросами в БД, и тогда вам нужно будет сузьте до коллекции, которую вы пытаетесь создать:

course_names = courses.map(&:name)
other_students.each_with_object({}) do |other_student, collection|
  course_names = other_student.courses.map(&:name)
  collection[other_student.name] = course_names.select { |course_name| course_names.include?(course_name) }
end

Выше будет построено collection, где ключ - это имена учеников, а значения - массив курсов, которые соответствуют тому, что student_id взял.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...