I've been struggling to create a form for a Mongoid model that has an array field. I want my form to have on text box per entry in the array. If I'm creating a new record, the default will be one empty field (and some javascript to add new fields dynamically on the page).
I've searched around for a solution using fields_for but it seems that is more intended to handle the case where you have an array of objects/models and not the case I have, which is an array of strings.
I'm going to use the example of a person and a phone number.
c开发者_如何学编程lass Person
include Mongoid::Document
field :name, :type => String
field :phone_numbers, :type => Array
end
For the controller, just assume the typical controller but in the new
method I initialized the phone_number array with one blank string.
Here's the form code:
<%= form_for(@person) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :phone_numbers %><br />
<% @person.phone_numbers.each do |phone_number| %>
<%= text_field_tag "person[phone_numbers][]", phone_number %>
<% end %>
</div>
<% end %>
This all works fine. There are a few things that I don't like.
- The hardcoded name of the field in the text_field_tag call.
- Using text_field_tag instead of f.text_field
- Having the feeling like I should somehow be using fields_for instead of this
Does anybody have any better suggestions on how to implement this? Or would you consider this correct?
I agree with your concerns -
The hard-coded name of the field in the
text_field_tag
call.Using text_field_tag instead of
f.text_field
using
fields_for
After doing some research found that first two concerns can be solved and probably also third can but haven't tried yet.
<%= form_for(@person) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :phone_numbers %><br />
<% @person.phone_numbers.each do |phone_number| %>
<%= f.text_field :phone_numbers, :name => "#{f.object_name}[phone_numbers][]"%>
<% end %>
</div>
<%end%>
Another clean approach could be having form builder defined text_field and then having -
def text_field(attribute, *args)
args.last.merge!(:name => "#{object_name}[#{attribute}][]") if args.last && args.last.is_a?(Hash) && args.last.delete(:array)
super(attribute, args)
end
<% @person.phone_numbers.each do |phone_number| %>
<%= f.text_field :phone_numbers, :array => true%>
<% end %>
You can find more information here
You could work with embeds_many:
class Person
include Mongoid::Document
field :name
embeds_many :phone_numbers
end
class PhoneNumber
include Mongoid::Document
field :number
embedded_in :person
end
And then, within your view, you could use:
<%= form_for(@person) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= @person.phone_numbers.each do |phone_number| %>
<%= f.fields_for phone_number do |p| %>
<div class="field">
<%= p.label :number %><br />
<%= p.text_field :number %>
</div>
<% end %>
<% end %>
<% end %>
According to a comment posted mosch in a comment to his own solution:
whenever you use fields_for, it expects an object with accessors for the attributes and some other methods like new_record? Basically speaking, the object hat to implement the ActiveModel interface.
The answer to my question is that there is not a better way unless I create another model for the phone nubmers, like was mosch suggested.
The second div should be like the following
<div class="field"><%= f.fields_for :phone_numbers do | phone | %>
<%= phone.text_field "phone_numer[]" %><% end %></div>
精彩评论