ruby Rails中多对多模型的连接

bmvo0sr5  于 2023-10-17  发布在  Ruby
关注(0)|答案(2)|浏览(115)

我有一个ruby on rails API,它在以下模型之间有多对多的关系:

class Course < ApplicationRecord
  has_many :student_courses
  has_many :students, through: :student_courses
end

class Student < ApplicationRecord
  has_many :student_courses
  has_many :courses, through: :student_courses
end

class StudentCourse < ApplicationRecord
  belongs_to :student
  belongs_to :courses
end

我想以以下格式提供JSON:

[
  {
    "course": "English",
    "students": [
      "John",
      "Sarah"
    ]
  },
  {
    "course": "Maths",
    "students": [
      "John",
      "Ella",
      "Lee"
    ]
  },
  {
    "course": "Classics",
    "students": [
      "Una",
      "Tom"
    ]
  }
]

现在我用一个循环来实现:

def index
  @courses = Course.all

  output = []
  @courses.each do |course|
    course_hash = {
      course: course.name,
      students: course.students.map { |student| student.name }
    }
    output << course_hash
  end

  render json: output.to_json
end

有没有一种更有效的方法可以使用活动记录对象关系Map来实现这一点?

jxct1oxe

jxct1oxe1#

在您的示例中,迭代Course.all.each,然后在每次迭代中调用course.students将导致N+1问题。这意味着将有一个数据库查询来获取所有课程,以及N个额外的数据库查询来加载列表中每个课程的学生。
为了避免N+1个查询,Ruby on Rails允许使用includes在一个或最多两个查询中快速加载学生和课程
另一个优化可以通过使用Enumerable#map重用现有数组来减少内存消耗,而不是使用each迭代数组并将转换后的数据复制到新数组中。
把它放在一起:

def index
  courses_with_students = Course.includes(:students).map do |course|
    { course: course.name, students: course.students.map(&:name) }
  end

  render json: courses_with_students.to_json
end
li9yvcax

li9yvcax2#

你不需要重新发明JSON序列化轮子。ActiveModel已覆盖。

def index
  @courses = Course.eager_load(:students) # prevents a N+1 query issue
                   .all
  render json: @courses, 
    include: {
      students: { 
        only: [:name] 
      }
    }
end

如果你需要更高级的序列化或者想避免控制器膨胀,有一些gem,比如ActiveModelSerializersjBuilderwhole plethora of gems that do jsonapi.org style serialization
请注意,生成的JSON略有不同:

[
  {
    "id": 1,
    "name": "Ruby",
    "created_at": "2023-10-16T14:24:48.356Z",
    "updated_at": "2023-10-16T14:24:48.356Z",
    "students": [
      {
        "name": "Bob"
      },
      {
        "name": "Melinda"
      },
      {
        "name": "Havier"
      }
    ]
  }
]

但这实际上是一件好事,因为你可能想在响应中包含学生的其他属性,这将使你在不破坏现有前端代码的情况下做到这一点。

相关问题