For polymorphic associations you use a fields_for
block:
<%= 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 %>
This will go inside your form_for
tag. Use fields_for
block to nest any polymorphic relation.
You would only use select
for an attribute that exists when creating something new. The example you've partly written out looks like you're simply selecting from existing items. That would be a different answer if you're looking for that. You've specifically asked about creating an item. So you won't be using select
for creating something by name, you will need a text_field
to enter the new name.
You can ignore the two select
fields for your solution. They are there to demonstrate select
. I don't believe your answer needs a select
field.
https://gorails.com/episodes/forum-nested-attributes-and-fields-for
On another note your naming scheme for Item may be confusing. The standard naming would be more like.
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
In this case you would use a fields_for
on :items
. The polymorphic relationship name activity
or itemable
is not referred to in the fields_for
field. Rather it is the plural for Item
so :items
.
To answer:
i have a page to add an item, where the user fill informations like
title, description, etc, and then chooses in which 'bar' or
'restaurant' he wants to publish it.
<%= 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 %>
Well this is basically what you want to do. But you would need to have an Ajax/JavaScript call to change the available options for :itemable_id
to the list of either Bars or Restaurants mapped with [:name, :id]. You could just use a text field to input the number of the ID of the Bar/Restaurant but this is not a user friendly experience.
If I was proficient in JavaScript I could give you a way to do this. One way would be to have duplicate :itemable_id
fields and have javascript disable/remove whichever the select field it isn't using.
Alternative solution 1:
You could make a page for each type new_bar_item.html.erb
and new_restaurant_item.html.erb
and each of those you would simply put a hidden_field
for itemable_type
to be either Bar.name
or Restaurant.name
respectively. And then you would already know which collection to give to your select
field. Map the collections for the select field to your :name, :id. This removes all the complication for doing this.
A workable solution 2:
A good way I can recommend to do it without JavaScript is to have both Bars and Restaurants listed.
<%= 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 %>
Then in your ItemController you will need to write a method to check which field isn't blank and set the polymorphic type with that.
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.
Solution 3 (revised #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 %>
We've added a b
before the ID for bar or r
before the ID for restaurant. Then we simply need to parse the params for it.
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
This meets your requirement of one select field with both Bars and Restaurants in it.