对于多态关联,您使用 fields_for 块:
<%= form_for(@bar) do |f| %>
Bar<br />
<%= select :id, options_for_select(@bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
New Item<br />
<%= f.fields_for :items do |a| %>
Kind: <%= a.select :kind, options_for_select(Item.kinds.keys.map.with_index {|k,i| [k, i]}) %><br /> <!-- # If you have an enum of kind for item -->
Vendor: <%= a.select :vendor_id, options_for_select(current_user.vendors.map {|i| [i.name, i.id]}) %><br /> <!-- # If you have specific vendors per user -->
Name: <%= a.text_field :name %><br />
<% end %>
<%= f.submit %>
<% end %>
这将进入您的form_for 标记。使用fields_for 块嵌套任何多态关系。
您只能将select 用于创建新 内容时存在的属性。您部分写出的示例看起来就像您只是从现有项目中进行选择。如果您正在寻找它,那将是一个不同的答案。您专门询问了有关创建项目的问题。因此,您不会使用 select 按名称创建内容,您需要 text_field 来输入新名称。
您可以忽略解决方案的两个 select 字段。他们在那里展示select。我认为您的答案不需要select 字段。
https://gorails.com/episodes/forum-nested-attributes-and-fields-for
另一方面,您对 Item 的命名方案可能会造成混淆。标准命名会更像。
class Item < ActiveRecord::Base
belongs_to :itemable, polymorphic: true
end
class Bar < ActiveRecord::Base
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
end
class Restaurant < ActiveRecord::Base
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
end
在这种情况下,您将在 :items 上使用 fields_for。多态关系名称activity 或itemable 未在fields_for 字段中引用。相反,它是Item 的复数形式,所以是:items。
回答:
我有一个页面来添加一个项目,用户在其中填写信息,例如
标题、描述等,然后选择哪个“栏”或
'restaurant' 他想发布它。
<%= form_for(@item) do |f| %>
<%= f.select :itemable_type, options_for_select([Bar.name, Restaurant.name]) %>
<%= f.select :itemable_id, [1,2,3] %># COMPLICATION, NEED AJAX/JS TO GET AVAILABLE IDs
<%= f.text_field :name %>
<% end %>
这基本上就是你想要做的。但是您需要调用 Ajax/JavaScript 来将 :itemable_id 的可用选项更改为使用 [:name, :id] 映射的酒吧或餐厅列表。您可以只使用文本字段来输入酒吧/餐厅的 ID 编号,但这不是一种用户友好的体验。
如果我精通 JavaScript,我可以给你一个方法来做到这一点。一种方法是拥有重复的 :itemable_id 字段并让 javascript 禁用/删除它不使用的任何选择字段。
替代方案一:
您可以为new_bar_item.html.erb 和new_restaurant_item.html.erb 的每种类型创建一个页面,您只需为itemable_type 分别添加一个hidden_field 为Bar.name 或Restaurant.name。然后您就已经知道将哪个集合提供给您的select 字段。将选择字段的集合映射到您的 :name、:id。这消除了执行此操作的所有复杂性。
可行的解决方案2:
我推荐的一个不使用 JavaScript 的好方法是同时列出酒吧和餐厅。
<%= form_tag(@item) do |f| %>
Choose either Bar or Restaurant.<br />
Bar: <%= select_tag 'item[bar_id]', options_for_select(@bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
Restaurant: <%= select_tag 'item[restaurant_id]', options_for_select(@restaurants.map {|i| [i.name, i.id]}, include_blank: true) %><br />
Item name: <%= text_field_tag 'item[name]' %>
<% end %>
然后在您的 ItemController 中,您将需要编写一个方法来检查哪个字段不是空白并使用它设置多态类型。
before_action :set_poly_by_params, only: [:create, :update]
private
def set_poly_by_params
if !params["item"]["bar_id"].empty? ^ !params["item"]["restaurant_id"].empty?
if !params["item"]["bar_id"].empty?
params["item"]["itemable_id"] = params["item"].delete("bar_id")
params["item"]["itemable_type"] = Bar.name
else
params["item"]["itemable_id"] = params["item"].delete("restaurant_id")
params["item"]["itemable_type"] = Restaurant.name
end
else
raise "some error about incorrect selection"
end
end
# NOTE: The above code will fail if the form doesn't submit both a bar_id field and a restaurant_id. It expects both, empty or not.
解决方案 3(修订版 #2)
<%= form_tag(@item) do |f| %>
Location: <%= select_tag 'item[venue]', options_for_select(
@bars.map {|i| [i.name, "b"+i.id.to_s]} +
@restaurants.map {|i| i.name, "r"+i.id.to_s]}
) %><br />
Item name: <%= text_field_tag 'item[name]' %>
<% end %>
我们在酒吧 ID 前添加了b,或在餐厅 ID 前添加了r。然后我们只需要解析它的参数。
before_action :set_poly_by_params, only: [:create, :update]
private
def set_poly_by_params
if params["item"]["venue"]["b"]
params["item"]["itemable_type"] = Bar.name
else
params["item"]["itemable_type"] = Restaurant.name
end
params["item"]["itemable_id"] = params["item"].delete("venue")[1..-1]
end
这符合您对一个包含酒吧和餐厅的选择字段的要求。