【发布时间】:2011-10-21 15:38:25
【问题描述】:
我在网上进行了深入搜索,以找到一种干净简单的方法来处理 has_many :through relation 的连接模型上的属性初始化,但我没有找到满足我需要的最佳解决方案。
在我下面提供的示例中,我需要在创建或更新Course 对象时自动设置Training 连接模型的属性role。
这是我的模型:
QUALIFICATIONS = ["Theoretical Instructor", "Practical Instructor"]
class Course < ActiveRecord::Base
has_many :trainings, dependent: :destroy
has_many :theoretical_instructors, through: :trainings, source: :trainer, conditions: { "trainings.role" => "Theoretical Instructor" }
accepts_nested_attributes_for :theoretical_instructors
has_many :practical_instructors, through: :trainings, source: :trainer, conditions: { "trainings.role" => "Practical Instructor" }
accepts_nested_attributes_for :practical_instructors
end
class Trainer < ActiveRecord::Base
has_many :trainings, dependent: :destroy
has_many :courses, through: :trainings
end
class Training < ActiveRecord::Base
belongs_to :trainer
belongs_to :course
# Join model has the :role attribute, that I wish I could validate this way:
# validates :role, presence: true, inclusion: { in: QUALIFICATIONS }
end
这个模型背后的基本原理是我想将Training 对象保存在一个表中。我不想创建TheoreticalInstructor 和PracticalInstructor 连接模型(可能会增加表的数量)来解决这个问题。
该视图提供了提交新Course的表单:
<%= form_for @course do |course_form| %>
<%- # fields for course attributes, as usual... %>
<%= course_form.label :theoretical_instructor_ids %><br />
<%= course_form.select :theoretical_instructor_ids, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %>
<%= course_form.label :practical_instructor_ids %><br />
<%= course_form.select :practical_instructor_ids, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %>
<%= course_form.submit %>
<% end%>
问题是:我该怎么做才能使@course = Course.new(params[:course]) 成为Course 控制器中唯一需要在提交前一个表单时保存此关联的代码行?
与this question 不同,当我创建新的Course 时,我不想创建新的Trainer 对象:我想从数据库中已经存在的对象中选择它们(通过多选输入字段)。
我需要像@course.theoretical_instructor_ids = [1, 2] 这样的东西创建两个Training 对象,并将role 属性设置为Theoretical Instructor
我正在考虑基于关系名称(:theoretical_instructors 和:practical_instructors)在Training 上设置role 的after_initialize 回调,但我真的不知道该怎么做。有什么建议吗?我错过了什么吗?
谢谢你们!
来自oli-g的编辑1
This question 处理类似的问题:不同之处在于我不想在创建新的Course 时构建Trainer 对象,而只是想将现有的Trainer 对象关联到新的Trainer 对象Course.
来自oli-g的EDIT 2
基于this(5 年前的帖子)和this 博客帖子,我以这种方式更改了Course 模型:
class Course < ActiveRecord::Base
has_many :trainings, dependent: :destroy
has_many :theoretical_instructors, through: :trainings, source: :trainer, conditions: ["trainings.role = ?", "Theoretical Instructor"] do
def <<(theoretical_instructor)
Training.send(:with_scope, create: { role: "Theoretical Instructor" }) { self.concat theoretical_instructor }
end
end
accepts_nested_attributes_for :theoretical_instructors
has_many :practical_instructors, through: :trainings, source: :trainer, conditions: ["trainings.role = ?", "Practical Instructor"] do
def <<(practical_instructor)
Training.send(:with_scope, create: { role: "Practical Instructor" }) { self.concat practical_instructor }
end
end
accepts_nested_attributes_for :practical_instructors
end
这段代码让我可以做这样的事情
:001 > c = Course.first
=> #<Course id: 1>
:002 > t1 = Trainer.first
=> #<Trainer id: 1, name: "Tom">
:003 > c.theoretical_instructors << t1
=> #<Trainer id: 1, name: "Tom">
:004 > Training.all
=> [#<Training id: 1, role: "Theoretical Instructor", trainer_id: 1, course_id: 1>]
这是一个可接受的解决方法,即使在我的控制器中我仍然不能只做 @course = Course.new(params[:course]),但我必须创建 Training 对象迭代 params[:course][:theoretical_instructor_ids] 和 params[:course][:practical_instructor_ids]。
但我很好奇,所以问题仍然悬而未决:我该怎么做才能使@course = Course.new(params[:course]) 与Course 一起构建Training 对象?
现在...我想我在 Rails 中发现了一个错误:
:005 > c.practical_instructors
=> [] # correct
:006 > c.practical_instructor_ids
=> [] # obviously
:007 > c.reload
=> #<Course id: 1>
:008 > c.practical_instructor_ids
=> [1] # WRONG!!!
:009 > c.practical_instructors
=> [] # now it's correct...
:010 > c.practical_instructor_ids
=> [] # WTF!?
我想我会在 github 问题上报告这个...
oli-g 编辑 3
在github 报告的错误
【问题讨论】:
标签: ruby-on-rails-3 activerecord join associations has-many-through