Rails 请问大家,这段 Rails 代码怎么写?

ibachue · 2013年03月24日 · 最后由 zlx_star 回复于 2013年03月25日 · 2937 次阅读

Hi all,

假设现在已有一个 Teacher 类和一个 Student 类,二者是多对多关系,中间的关系类是 TeacherStudentRelation,里面的属性除了必要的 student_id 和 teacher_id 之外还有其他几个附属属性。 现在有几个 Constraints 是

  1. TeacherStudentRelation 中的 student_id 和 teacher_id 分别都有外键。
  2. student_id 和 teacher_id 形成 unique 关系。
  3. TeacherStudentRelation 中所有属性全部都是 not null。
  4. 虽然 Teacher 可以没有 Student,但是 Student 必须至少有一个 Teacher。这个 Constraint 由 ActiveRecord 中的 validate 方法实现。

目前假设 Teacher 对象在数据库中已经存在,需求是创建 Student 对象,同时为二者建立关系,同时把 TeacherStudentRelation 里的几个额外属性全部写入值,那这段代码怎么写?

teacher = Teacher.find teacher_id
student = Student.new
# 如果此时写
student.teachers << teacher
# 那么此时调用
student.teacher_student_relations
# 将返回空数组
# 但是如果直接保存的话
student.save!
# 将发生NULL错误

再换个思路

teacher = Teacher.find teacher_id
student = teacher.students.create!
# 就直接发生 “一个学生必须有至少一个老师”的错误
# 即使换成
student = teacher.students.create
# 虽然不会立刻报错,但是很快陷入了和前面一样的囧境

还是不行,然后我也想不到办法了,谢谢大家帮忙哈~

model 里怎么定义的?

其实方法也不是完全没有

# 先创建中间关系
ts = TeacherStudentRelation.new
ts.teacher_id = teacher_id
ts.student = Student.new
# 这里如果想之前一样保存肯定会出“一个学生必须有至少一个老师”的错误
ts.student.save!
# 但是换成
ts.student.save_without_validation
# 就可以暂时绕过这个错误
# 然后保存关系对象
# ts.save!
# 确实能够成功

这个方法最大的局限性在于 Student 保存的时候绕过了 Model 层验证,其实除了“一个学生必须有至少一个老师”的验证之外 其他验证还是希望存在的。所以这个解决方法还是不够好。。

#1 楼 @jimrokliu 代码大约是这样的

class Teacher < ActiveRecord::Base
  has_many :teacher_student_relations
  has_many :students, :through => :teacher_student_relations
  # 其他内容省略
end

class Student < ActiveRecord::Base
  has_many :teacher_student_relations
  has_many :teachers, :through => :teacher_student_relations

  validate do |student|
    if student.teachers.empty?
      student.errors.add_to_base 'A student must have at least one teacher!'
    end
  end
  # 其他内容省略
end

class TeacherStudentRelation < ActiveRecord::Base
  belongs_to :teacher
  belongs_to :student
  # 其他内容省略
end

感觉问题出在约束上,Student 一定先用于 TeacherStudentRelation 产生,这个符合客观世界的对象建立顺序。然后再建立老师和学生的关系,但这直接违背了你的约束。考虑一下,是否可以不建立这种约束,允许存在学生,但是没有找到自己的导师。

#4 楼 @jimrokliu 嗯 其实我后来也发现 除非 3 和 4 两个 constraint 去掉(或是延迟)一个,否则该问题不可解决。 延迟 4 的办法是,先用 save_without_validation 暂时保存 student 对象,然后保存中间关系,最后再执行一次对 student 的 validate。或者是把 validate 约束延迟到 update 时才有效。

先生鸡是先生蛋的的问题么? 同意@jimrokliu 的建议,把 4 的约束去掉。 在我看来,还是去掉约束 4 最干净。 像你说的,在同一次操作中先 save_without_validation,马上保存中间关系,validate 不过就再回退把中间关系和 student 删除。太难看了。

#6 楼 @xmonkeycn 这个你们太理想化了 PM 说有这个限制就是有这个限制 实现是 Dev 的事情,最多只能 Workaround,不可能完全去除啊

既然按你上面“再换个思路”里的写法:

teacher = Teacher.find teacher_id
student = teacher.students.create!

新建的学生已经有老师了,何必加上 valiate 呢,已经满足条件了吧

#8 楼 @thisiskun 没听明白你意思 validate 是写在 model 里的啊 业务层的内容都是写在 controller 里的啊

在 student 里改为验证 relationship 存在,在 relationship 里验证 teacher presence。这样同时创建 student 和 relationship 都能通过验证,而且保证 student 至少有个 teacher

#10 楼 @doitian 在 Relationship 里面验证关系,Teacher 和 Student 只保证有 relationship

需要 登录 后方可回复, 如果你还没有账号请 注册新账号