
[英]Ruby on Rails - form_for collection_select options not visable
[英]Rails 7.0.4- form_for - collection_select with multiple options in edit action
我有三个表:
商业案例:员工可以在多个地点工作。 Staff 和 Location 之间的关联是通过 staff_locations 表完成的。 在创建员工条目时,我正在选择他/她所属的位置。 这工作正常。
但是我在编辑操作中正确显示 collection_select 时遇到问题。 它显示的次数与 staff_locations 表中与 staff_id 匹配的条目的数量一样多。
我不知道如何解决这个问题,到目前为止我没有在任何地方找到任何好的提示。
楷模
class Staff < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :work_schedules
has_many :customers, through: :visits
has_many :staff_locations, dependent: :destroy
has_many :locations, through: :staff_locations
accepts_nested_attributes_for :staff_locations, allow_destroy: true
def staff_locations_attributes=(staff_locations_attributes)
staff_locations_attributes.values[0][:location_id].each do |loc_id|
if !loc_id.blank?
staff_location_attribute_hash = {};
staff_location_attribute_hash['location_id'] = loc_id;
staff_location = StaffLocation.create(staff_location_attribute_hash)
self.staff_locations << staff_location
end
end
end
end
class StaffLocation < ApplicationRecord
belongs_to :staff
belongs_to :location
validates :staff_id, :location_id, uniqueness: true
end
class Location < ApplicationRecord
has_many :staff_locations
has_many :staffs, through: :staff_locations
end
员工控制器
class StaffsController < ApplicationController
before_action :set_staff, only: %i [ show edit update destroy ]
def index
@staffs = Staff.all
end
def show
end
def new
@staff = Staff.new
@staff.staff_locations.build
end
def create
@staff = Staff.new(staff_params)
if @staff.save
redirect_to @staff
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
respond_to do |format|
if @staff.update(staff_params)
format.html { redirect_to @staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, staff: @staff }
else
format.html { render :edit }
format.json { render json: @staff.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def staff_params
params.require(:staff).permit(:first_name, :last_name, :status, :staff_type, staff_locations_attributes: [:location_id => [] ])
#due to multiple select in the new staff form, staff_locations_attributes needs to contain Array of location_ids.
#Moreover check Staff model's method: staff_locations_attributes. It converts staff_locations_attributes into hashes.
end
def set_staff
@staff = Staff.find(params[:id])
end
end
形式偏
<%= form_for(@staff) do |form| %>
<div>
<% if params["action"] != "edit" %>
<%= form.fields_for :staff_locations do |staff_location_form| %>
<%= staff_location_form.label :location_id, 'Associated Locations' %><br>
<%= staff_location_form.collection_select :location_id, Location.all, :id, :loc_name, {include_blank: false}, {:multiple => true } %>
<% end %>
<% else %>
<%= form.fields_for :staff_locations do |staff_location_form| %>
<%= staff_location_form.label :location_id, 'Associated Locations' %><br>
<%= staff_location_form.collection_select :location_id, Location.all, :id, :loc_name, {selected: @staff.locations.map(&:id).compact, include_blank: false}, {:multiple => true} %>
<% #debugger %>
<% end %>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
更新
在@Beartech 建议的更改之后,更新方法工作正常。 然而,新的行动停止了。 下面我粘贴了我在提交表单时捕获的内容,以在 Staff 表中创建一个条目并在 Staff_locations 表中创建两个相关条目。
在将 objetct 保存到数据库之前,我在控制台中进行了检查:
之后我确实保存了。 我不明白为什么它以 FALSE 状态结束。
14| #@staff.staff_locations.build
15| end
16|
17| def create
18| @staff = Staff.new(staff_params)
=> 19| debugger
20|
21| respond_to do |format|
22| if @staff.save
23| format.html { redirect_to @staff, notice: 'Staff was successfully created.' }
=>#0 StaffsController#create at ~/rails_projects/dentysta/app/controllers/staffs_controller.rb:19
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/rails_projects/dentysta/vendor/bundle/ruby/3.0.0/gems/actionpack-7.0.4/lib/action_controller/metal/basic_implicit_render.rb:6
# and 75 frames (use `bt' command for all frames)
(ruby) @staff
#<Staff:0x00007f2400acb2e8 id: nil, first_name: "s", last_name: "dd", status: "Active", staff_type: "Doctor", created_at: nil, updated_at: nil>
(ruby) @staff.location_ids
[4, 5]
(ruby) staff_params
#<ActionController::Parameters {"first_name"=>"s", "last_name"=>"dd", "status"=>"Active", "staff_type"=>"Doctor", "location_ids"=>["", "4", "5"]} permitted: true>
(ruby) @staff.save
TRANSACTION (0.1ms) begin transaction
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 4], ["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
CACHE StaffLocation Exists? (0.0ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.3ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 5], ["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
TRANSACTION (0.1ms) rollback transaction
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
false
(rdbg) c # continue command
TRANSACTION (0.1ms) begin transaction
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.2ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 4], ["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
CACHE StaffLocation Exists? (0.0ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.2ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 5], ["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
TRANSACTION (0.1ms) rollback transaction
↳ app/controllers/staffs_controller.rb:22:in `block in create'
Rendering layout layouts/application.html.erb
Rendering staffs/new.html.erb within layouts/application
Location Count (0.1ms) SELECT COUNT(*) FROM "locations"
↳ app/views/staffs/_form.html.erb:36
Location Load (0.1ms) SELECT "locations".* FROM "locations"
↳ app/views/staffs/_form.html.erb:36
Rendered staffs/_form.html.erb (Duration: 18.5ms | Allocations: 2989)
Rendered staffs/new.html.erb within layouts/application (Duration: 21.7ms | Allocations: 3059)
Rendered layout layouts/application.html.erb (Duration: 24.6ms | Allocations: 4054)
Completed 422 Unprocessable Entity in 2302301ms (Views: 30.1ms | ActiveRecord: 1.8ms | Allocations: 174939)
编辑重要提示:使用多选可能会出现意想不到的用户界面问题。 当您使用下面的代码时,现有记录的多选将加载现有关联位置突出显示为选择。 如果您不触摸该表单元素然后保存表单,它们将保持关联。 但是整个多选列表可能不会立即显示。 如果此人看不到所有选定的元素,他们可以单击一个元素,这将取消选择所有其他元素,从而在记录保存时删除这些关联。 我编辑了答案以添加size:
到 HTML 属性。 这将显示所有选项,以便他们可以看到选择了哪些选项以及单击一个选项时会发生什么(取消选择所有其他选项需要 shfit/option select 才能重新选择)。 我会考虑这是否是您正在做的事情的正确界面元素。 您可能希望将collection_check_boxes
视为正确的 UI 元素,因为它们必须有目的地取消选择任何想要删除的内容,并且不必在每次添加或删除一个位置时都重新选择它们。
我花了一段时间才记住如何做到这一点。 这是因为您专注于连接表。 通常这就是您想要多个表单域时会做的事情。 但您实际上是在寻求利用has_many
关系。
请记住,您的accepts_nested_attributes_for
为您提供了一种location_ids=
方法,它允许您仅通过传递 ID 来设置这些位置。 Rails 将负责使用连接 model 建立关联。
在您的控制台中尝试:
@staff = Staff.first
# returns a staff object
@staff.locations
#returns an array of location objects due to the has_many
@staff.location_ids
# [12, 32]
@staff.location_ids = [12, 44, 35]
#this will update the joined locations to those locations by id. If any current locations are not in that array, they get deleted from the join table.
改变你的强参数:
params.require(:staff).permit(:first_name, :last_name, :status,
:staff_type, staff_locations_attributes: [:location_id => [] ])
到:
params.require(:staff).permit(:first_name, :last_name, :status,
:staff_type, :location_ids => [] )
在您的表单中,您只需要一个表单元素,使用@staff
上的方法构建:
<%= f.label :locations %><br />
<%= f.collection_select :location_ids, Location.all, :id, :name,{selected: @staff.location_ids,
include_blank: false}, {:multiple => true, size: Location.all.count } %>
所以这是可行的,因为.location_ids
是@staff
上的有效方法, Location.all
返回所有位置的集合,然后这两个符号(:id 和:name)都是单个位置 object 的有效方法。然后在selected...
您只是使用相同的.location_ids
来抓取已经存在的那些以将它们标记为已选中。
我忘了怎么做,已经有一段时间了。 一旦我记得它是那么容易。
对于那些将来会遇到类似情况的人,我现在正在粘贴对我有用的东西。 @Beartech 再次感谢您的帮助。 它为我节省了很多时间。
楷模
class Staff < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :work_schedules
has_many :customers, through: :visits
has_many :staff_locations, dependent: :destroy
has_many :locations, through: :staff_locations
accepts_nested_attributes_for :staff_locations, allow_destroy: true
end
class StaffLocation < ApplicationRecord
belongs_to :staff
belongs_to :location
end
class Location < ApplicationRecord
has_many :staff_locations
has_many :staffs, through: :staff_locations
end
员工控制器
class StaffsController < ApplicationController
before_action :set_staff, only: %i[ show edit update destroy ]
def index
@staffs = Staff.all
end
def show
#debugger
end
def new
@staff = Staff.new
end
def create
@staff = Staff.new(staff_params)
debugger
respond_to do |format|
if @staff.save!
format.html { redirect_to @staff, notice: 'Staff was successfully created.' }
format.json { render :show, status: :ok, staff: @staff }
#redirect_to @staff
else
format.html { render :new, status: :unprocessable_entity, notice: 'Somthing went wrong' }
format.json { render json: @staff.errors, status: :unprocessable_entity }
#render :new, status: :unprocessable_entity
end
end
end
def edit
end
def update
respond_to do |format|
if @staff.update(staff_params)
format.html { redirect_to @staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, staff: @staff }
else
format.html { render :edit }
format.json { render json: @staff.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def staff_params
params.require(:staff).permit(:first_name, :last_name, :status, :staff_type, :location_ids => [] )
end
def set_staff
@staff = Staff.find(params[:id])
end
end
_form部分
<%= form_for(@staff) do |form| %>
<div>
<%= form.label :first_name %><br>
<%= form.text_field :first_name %>
<% @staff.errors.full_messages_for(:first_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :last_name %><br>
<%= form.text_field :last_name %>
<% @staff.errors.full_messages_for(:last_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :staff_type %><br>
<%= form.collection_select :staff_type, Staff.valid_types, :to_s, :to_s, {include_blank: false}, {:multiple => false} %>
<% @staff.errors.full_messages_for(:staff_type).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :status %><br>
<%= form.collection_select :status, Staff.valid_statuses, :to_s, :to_s, {include_blank: false}, {:multiple => false} %>
<% @staff.errors.full_messages_for(:status).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :locations %><br />
<%= form.collection_select :location_ids, Location.all, :id, :loc_name,{selected: @staff.location_ids, include_blank: false}, {:multiple => true, size: Location.all.count } %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.