使用 Rails 中的嵌套表单将来自另一个 table 的 ID 分配给输入 4

Assigning id's from another table to input using nested forms in Rails 4

我第一次体验 Rails/一般 Web 开发已经几周了,遇到了一个我似乎无法在溢出或解决问题时发现的障碍。我觉得我快到了,但还不完全...

我正在为一家慈善机构开发员工网站,需要对新员工进行分类,以便为他们每个人分配权限。目前,我有一个创建新员工的员工新视图,并且有一个嵌套表单用于在其中输入以同时添加员工所在的部门。但是,目前每个新员工都会产生一个新部门。因此,如果我创建了部门 "Admin" 的 2 名员工,我最终会得到 2 个具有单独 type_id 的管理部门,而不是将他们分配给同一部门。假设部门已经存在,我如何在表单中创建员工时分配员工的 type/department。在控制台中我可以这样做:

s = Staff.create(staff_name: "John", staff_email: "John@example.com", password: "asdsadsad")
t = Type.create(department: "Admin")
s.types << t

到目前为止,我的员工和类型(类别)模型看起来像这样(删除了不相关的部分),我在其中添加了 accepts_nested_attributes_for:

class Staff < ActiveRecord::Base
  has_many :staff_types
  has_many :types, through: :staff_types
  accepts_nested_attributes_for :types
  validates :staff_name, presence: true, length: { minimum: 5, maximum: 50}
  has_secure_password
end

class StaffType < ActiveRecord::Base
  belongs_to :staff
  belongs_to :type
end

class Type < ActiveRecord::Base
  validates :department, presence: true, length: { minimum: 2, maximum: 25 }
  has_many :staff_types
  has_many :staffs, through: :staff_types
end

我在其中将类型变量列入白名单的员工控制器如下:

class StaffsController < ApplicationController
  before_action :set_staff, only: [:edit, :update, :show]

  def new
    @staff = Staff.new
    1.times { @staff.types.build}
  end 

  def create
    @staff = Staff.new(staff_params)
    if @staff.save
      flash[:success] = "Your account has been created successfully"
      redirect_to staff_path(@staff)
    else
      render 'new'
    end
  end

  private

  def staff_params
    params.require(:staff).permit(:staff_name, :staff_email, types_attributes: [:id, :department])
  end
end

最后我的观点如下,我在 form_for 助手中嵌入了嵌套表单:

<div class="row">
   <div class="well col-md-8 col-md-offset-2">
     <%= form_for @staff do |f| %>

       <%= f.label :staff_name %>
       <%= f.text_field :staff_name %>

       <%= f.label :staff_email %>
       <%= f.email_field :staff_email %>

       <%= f.label :password %>
       <%= f.password_field :password %>

       <%= f.label :password_confirmation %>
       <%= f.password_field :password_confirmation %>

       <%= f.fields_for :types, @staff.types do |types_form| %>
         <%= types_form.label :department %>
         <%= types_form.text_field :department %>
       <% end %>

       <%= f.submit(@staff.new_record? ? "Submit Profile" : "Submit Edited Profile", class: "btn btn-success") %>
    <% end %>
  </div>
</div>

每次提交新员工时都会创建新记录的原因是您实际上并没有让控制器知道员工的类型可能是现有类型。所以现在的问题是,执行此操作的最佳方法是什么?编辑此句子

方法#1:在多select

中选择员工类型

如果你有一定数量的类型,你已经插入了它们,你不需要改变它们,那么你可以使用多select。为此,请在您的代码中替换以下代码...

<%= f.fields_for :types, @staff.types do |types_form| %>
  <%= types_form.label :department %>
  <%= types_form.text_field :department %>
<% end %>

...与:

<%= f.collection_select :type_ids, Type.all, :id, :department,
    { selected: @staff.type_ids }, { multiple: true, size: Type.all.size } %>

然后,在您的控制器中,将您的 staff_params 方法更改为:

def staff_params
  params.require(:staff).permit(:staff_name, :staff_email, type_ids: [])
end

使用此方法,您将能够 select 从所有可能的类型中为每位员工选择多种类型。假设您在创建员工时不需要创建类型,我建议您继续使用该成员。

您也可以使用复选框而不是多选框 select,但我不会在本回答中介绍它。您可以阅读复选框 here.

方法 #2:确定控制器中哪些类型是新的

如果您想保留输入员工类型的功能,这会有点棘手,但我认为您可以将所有逻辑保留在您的 create 方法中控制器。目前,在该方法中,您可以在获取其参数后立即保存一名工作人员。您首先可以做的是检查传递的任何类型是否是新的(即工作人员尚未与该类型相关联),然后如果至少有一个是,则检查每个类型是否都是现有类型。将您的 create 方法更新为:

def create
  @staff = Staff.assign_attributes(staff_params)

  @staff.staff_types.each_with_index do |staff_type, index|
    if (type = Type.find_by(department: staff_type.type.department)).present?
      @staff.staff_type[index].assign_attribute(:id, type.id)
    end
  end

  if @staff.save
    flash[:success] = "Your account has been created successfully"
    redirect_to staff_path(@staff)
  else
    render 'new'
  end
end

我应该注意到,可能有更好的方法来做到这一点,希望这个答案能鼓励某人 post 更好的方法,但这个答案应该适合你的目的。