Forms to create and update Mongoid array fields

Dave Jensen picture Dave Jensen · Jun 14, 2011 · Viewed 9.8k times · Source

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.

class 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?

Answer

Sandip Ransing picture Sandip Ransing · Jan 19, 2012

I agree with your concerns -

  1. The hard-coded name of the field in the text_field_tag call.

  2. Using text_field_tag instead of f.text_field

  3. 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