Rails 3和has_many:through:在连接模型上自动设置/初始化属性

时间:2011-08-07 00:58:00

标签: ruby-on-rails-3 activerecord join associations has-many-through

我深入搜索网页,以便找到一种干净简单的方法来处理has_many :through relation的连接模型上的属性初始化,但我没有找到满足我需要的最佳解决方案。

在我下面提供的问题中,我需要在创建或更新role对象时自动设置Training联接模型的属性Course

这是我的模特:

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个对象。我不想创建TheoreticalInstructorPracticalInstructor联接模型(可能爆炸表的数量)来解决这个问题。

此视图提供了提交新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不同我在创建新Trainer时不想创建新的Course对象:我想从已经存在于数据库中的对象中选择它们(通过多选输入字段)。

我需要的是像@course.theoretical_instructor_ids = [1, 2]这样的内容创建了两个Training个对象,role属性设置为理论教练

我在考虑基于关系名称after_initializeTraining设置role :theoretical_instructors的{​​{1}}回调,但我确实不知道怎么做。有什么建议?我错过了一些观点吗?

谢谢你们!

来自oli-g的编辑1

This question处理类似问题:不同之处在于我在创建新:practical_instructors时不想构建Trainer个对象,但我只想关联现有{ {1}}对象为新的Course

来自oli-g的编辑2

基于this(5年之前的帖子)和this博客帖子,我以这种方式更改了Trainer模型:

Course

这段代码让我可以做这样的事情

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>] 和{{1}上迭代创建@course = Course.new(params[:course])个对象}。

但我很好奇,所以问题仍然存在:我可以做些什么才能让Trainingparams[:course][:theoretical_instructor_ids]一起构建params[:course][:practical_instructor_ids]个对象?

现在......我想我在Rails中发现了一个错误:

@course = Course.new(params[:course])

我想我会在github问题上报告这个......

编辑3 by oli-g

github

报告错误

1 个答案:

答案 0 :(得分:1)

您的问题是,在创建记录之后,您将无法添加关联。在这种情况下,使用课程记录ID存储培训关联,并且直到首次保存课程之后才定义课程ID。您要做的是在创建记录后使用after_create回调来调用函数。

将其添加到课程模型的末尾:

# Use attr accessors to store the initial values so they won't conflict with the *_instructor_ids methods defined above 
attr_accessor :create_theoretical_instructors
attr_accessor :create_practical_instructors
# This will call the create_training_records function after the record is created
after_create :create_training_records

private
def create_training_records
  create_theoretical_instructors.each do |instructor_id|
    self.theoretical_instructors << Instructor.find(instructor_id)
  end
  create_practical_instructors.each do |instructor_id|
    self.practical_instructors << Instructor.find(instructor_id)
  end
  save!
end

并在视图中更改表单以使用新的attr_accessors:

<%= course_form.label :create_theoretical_instructors %><br />
<%= course_form.select :create_theoretical_instructors, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, {  }, { multiple: true } %>

<%= course_form.label :create_practical_instructors %><br />
<%= course_form.select :create_practical_instructors, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, {  }, { multiple: true } %>

现在,当您提交表单时,它会将指导者ID写入新的Course实例变量;在课程经过验证和保存后,它将自动创建新的关联。