【问题标题】:Rails 3 and has_many :through: automagically set/initialize attributes on join modelRails 3 和 has_many :through: 在连接模型上自动设置/初始化属性
【发布时间】: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 对象保存在一个表中。我不想创建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 不同,当我创建新的Course 时,我不想创建新的Trainer 对象:我想从数据库中已经存在的对象中选择它们(通过多选输入字段)。

我需要像@course.theoretical_instructor_ids = [1, 2] 这样的东西创建两个Training 对象,并将role 属性设置为Theoretical Instructor

我正在考虑基于关系名称(:theoretical_instructors:practical_instructors)在Training 上设置roleafter_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


    【解决方案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 实例变量;课程经过验证并保存后,会自动创建新的关联。

    【讨论】:

      猜你喜欢
      • 2011-03-06
      • 2012-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-16
      • 2011-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多