问题描述
我有一个系统,管理员可以根据他们销售的分支机构创建考试并为它们定价。因此,例如,第一分支的考试费用为5美元,而第二分支的考试费用为10美元。
我制作了一个名为ExamOffering的联接表,该表具有考试的价格,因此每个考试可以在许多分支机构以不同的价格出售,并且每个分支机构可以进行许多考试。像这样:
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
我需要能够创建一个新的Exam并在表单中选择一个Branch以便输入价格,但是该属性不是Exam模型的一部分,而是ExamOffering连接表。我尝试了一些方法但失败了(在Exam模型中使用accepts_nested_attributes_for:exam_offerings或遍历所有分支并为控制器中的每个分支创建ExamOfferings)。这样做的“路轨方式”是什么?我认为这是一个很常见的案例,但是我没有找到适合我的案例的答案。也许这有个名字,我不知道。
它可以这样表达:创建新的考试时,我希望能够为每个现有分支机构输入价格。
谢谢。
解决方法
您想要做的是显式创建联接模型:
# 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 %>