Rails 5 如何从父模型的视图编辑连接模型的属性?

Rails 5 how to edit join model attributes from the parent model's view?

我有一个系统,管理员可以在其中创建考试并根据他们出售考试的分支机构定价。例如,考试一在分支机构一的费用为 5 美元,但在分支机构二的费用为 10 美元。

我创建了一个名为 ExamOffering 的联接 table,它具有考试价格,因此每个考试可以在许多分支机构以不同的价格出售,并且每个分支机构可以有许多考试。像这样:

    class Branch < ApplicationRecord
      has_many :exam_offerings
      has_many :exams, through: :exam_offerings
    end

    class Exam < ApplicationRecord
      has_many :exam_offerings
      has_many :branches, through: :exam_offerings
    end

    class ExamOffering < ApplicationRecord
      # this class has a 'price' attribute
      belongs_to :exam
      belongs_to :branch
    end

我需要能够在表单中创建一个新的考试和 select 一个分支以便输入价格,但该属性不是考试模型的一部分,而是 ExamOffering 加入 table。我尝试了一些方法但失败了(在考试模型中使用 accepts_nested_attributes_for :exam_offerings 或遍历所有分支并为控制器中的每个分支创建 ExamOfferings)。这样做的 'Rails way' 是什么?我认为这是一个很常见的情况,但我还没有找到适合我的情况的答案。也许这个有名字我不知道。

它可以这样表述:当我创建一个新的考试时,我希望能够为每个现有的分支输入一个价格。

谢谢。

您要做的是显式创建连接模型:

# config/routes.rb
resources :exam_offerings, only: [:new, :create]
class ExamOffering < ApplicationRecord
  # this class has a 'price' attribute
  belongs_to :exam
  belongs_to :branch
  accepts_nested_attributes_for :exam
  validates_associated :exam
end
# app/views/exam_offerings/new.html.erb
<%= form_with(model: @exam_offering) do |f| %>
  <div class="field" %>
    <%= f.label(:branch_id) %>  
    <%= f.collection_select(:branch_id, Branch.all, :id, :name) %>
  </div>
  <div class="field" %>
    <%= f.label(:price) %>  
    <%= f.number_field(:price) %>
  </div>
  <%= f.fields_for(:exam) do |exam_field| %>
    <div class="field" %>
      <%= exam_field.label(:foo) %>  
      <%= exam_field.text_field(:foo) %>
    </div>
  <% end %>
  <%= f.submit %>
<% end %>
class ExamOfferingsController < ApplicationController

  # GET /exam_offerings/new
  def new
    @exam_offering = ExamOffering.new
  end
 
  # POST /exam_offerings
  def create
    @exam_offering = ExamOffering.new(exam_offering_params)
    if @exam_offering.save
       redirect_to @exam_offering, notice: 'Offering Created'
    else
       render :new, notice: 'Oh No!'
    end
  end

  private

  def exam_offering_params
    params.require(:exam_offering)
          .permit(
            :branch_id, :price, 
            exam_attributes: [:foo]
          )
  end
end

请记住,连接模型没有什么特别之处——它们并不总是必须隐式创建。通常它们实际上是业务逻辑的重要组成部分,而不仅仅是管道。

但实际上,如果我正在构建它,我只会创建一个普通的 POST /exams 路径,用户可以在其中创建考试,然后让他们在之后创建产品。如果需要使其看起来无缝,请使用 AJAX。

我最终在 Exam 模型中使用了 accepts_nested_attributes_for,所以我不必使用另一个视图来设置价格,所以它是这样的:

class Branch < ApplicationRecord
  has_many :exam_offerings
  has_many :exams, through: :exam_offerings
end

class Exam < ApplicationRecord
  has_many :exam_offerings
  has_many :branches, through: :exam_offerings
  accepts_nested_attributes_for :exam_offerings
end

class ExamOffering < ApplicationRecord
  # this class has a 'price' attribute
  belongs_to :exam
  belongs_to :branch
end

重要的是风景。我需要在创建考试时将 fields_for 与新对象一起使用,但在编辑考试时获取现有的 exam_offerings,所以我有以下代码:

<% if exam.exam_offerings.empty? %>
  <% Branch.all.each do |branch| %>
    <%= form.fields_for :exam_offerings, @exam.exam_offerings.build do |offering| %>
      <div class="field">
        <%= offering.label "Price in #{branch.name}" %>
        <%= offering.text_field :price %>
        <%= offering.hidden_field :branch_id, value: branch.id %>
      </div>
    <% end %>
  <% end %>
<% else %>
  <%= form.fields_for :exam_offerings do |offering| %>
    <div class="field">
      <%= offering.label "Price in #{offering.object.branch.name}" %>
      <%= offering.text_field :price %>
    </div>
  <% end %>
<% end %>