我有三个型号Exam
、User
和ExamResult
。ExamResult
包含用于考试(Exam)的所有学生(用户)的记录。对于一个特定的Exam
记录,每个学生在ExamResult
中应该有一个记录。在ExamController
的edit
方法中,根据是否为一个学生创建了ExamResult
记录,我需要创建一个新记录或跳过它。不确定这是否是惯用的Rails方法。
# ExamController
def edit
User.students.each do |s|
@exam.exam_results.build(user_id: s.id) unless @exam.exam_results.find_by(user_id: s.id)
end
end
或者这样:
def edit
newIds = User.students.map(&:id) - @exam.exam_results.map(&:user_id)
newIds.each do |id|
@exam.exam_results.build(user_id: id)
end
end
也许两者都不是惯用的Rails。欢迎提出任何建议。
编辑
将find_or_initialize_by
(由@user3334690推荐)放在桌子上。如果我正确地理解了文档,这应该与前两个实现相同。
def edit
User.students.each do |s|
@exam.exam_results.find_or_initialize_by(user_id: s.id)
end
end
在上述情况下,您可以使用find_or_create_by_user_id而不是构建。
def edit
User.students.each do |s|
ExamResult.find_or_create_by_exam_id_and_user_id(exam_id, user_id)
end
end
有这样的方法:
def edit
User.students.each do |s|
ExamResult.where(exam_id: @exam.id, user_id: s.id).first_or_create
end
end
使用第二个示例:
def edit
newIds = User.students.map(&:id) - @exam.exam_results.map(&:user_id)
newIds.each do |id|
@exam.exam_results.build(user_id: id)
end
end
正如我在上面的评论中所说,无论学生人数多少,这都会对数据库进行两次查询,这样会更好地扩展。如果你在一个非常极端的环境中,你可以写它只使用一个查询,同时使用"pull"只提取"id"列(并避免对象创建开销),如下所示:
newIds = User.students.where("users.id not in (select user_id from exam_results where exam_id=?)", @exam.id).pluck(:id)
然而,可读性因此而降低。你的原作也将受益于使用"勇气"而不是"地图"。
另一个风格注意事项是,我将使用"new_ids",这是使用Rails进行操作的标准惯用方式。