ruby-on-railssimple-formsimple-form-for

deeply nested simple_form and simple fields


My nested json payload looks like :

{
    "id": 1,
    "courses": [
        {
            "math": [
                {
                    "name": "Math A",
                    "hours": 10,
                    "grade": "B"
                },
                {
                    "name": "Math B",
                    "hours": 20,
                    "grade": "A"
                },
                {
                    "name": "Math C",
                    "hours": 10,
                    "grade": "B"
                }
            ],
            "english": [
                {
                    "number_of_students": 10,
                    "hours": 20,
                    "name": "Intro to English",
                    "grade": "B"
                },
                {
                    "number_of_students": 15,
                    "hours": 25,
                    "name": "English 101",
                    "grade": "A"
                }
            ],
        },
        {}, {}, {}.... 
    ]
}

I need to loop through the available options to generate the form, then construct a nested payload as above.

<%= simple_form_for @courses_payload, course_path(id), method: :post do |f| %>
  <% available_options.each do |option| %>
    <%= f.simple_fields_for Course.new do |c| %>
      <%= option.name %>
      <% if option.name == "english" %>
        <%= c.input : number_of_students %>
      <% end %>
      <%= c.input :hours %>
      <%= c.input :grade %>
     <% end %>
  <% end %>
<% end %>
api.create(
    params[:courses_payload].permit(
       courses:[math: [:name, :hours, :grade], english: [:number_of_students, :hours, :name, :grade]]
    ) 

However, I was not able to get all the nested fields into the payload. The form can only record grade field, and not other nested fields(name, hours, number_of_students) . Is there something wrong with my loop?


Solution

  • If I have only hash to work with, I'll use it for my form. But the form applies to models as well, maybe with a few tweaks.

    <%
      hash = {
        "id": 1,
        "courses": [
          {
            "math": [
              { "name": "Math A", "hours": 10, "grade": "B" },
              { "name": "Math B", "hours": 20, "grade": "A" },
              { "name": "Math C", "hours": 10, "grade": "B" }
            ],
            "english": [
              { "number_of_students": 10, "hours": 20, "name": "Intro to English", "grade": "B" },
              { "number_of_students": 15, "hours": 25, "name": "English 101", "grade": "A" }
            ],
          }
        ]
      }
    %>
    

    The goal is to make our inputs name attributes like this:

    courses_payload[courses][0][english][0][number_of_students]
    
    <%= simple_form_for :courses_payload, url: "/courses/#{hash[:id]}", method: :post do |f| %>
      <% hash[:courses].each.with_index do |courses,i| %>
        <% courses.each do |course, values| %>
          <%= f.simple_fields_for :courses, index: i do |ff| %>
    
            <%# values = [{:name=>"Math A", :hours=>10, :grade=>"B"}, {:name=>"Math B", :hours=>20, :grade=>"A"}, {:name=>"Math C", :hours=>10, :grade=>"B"}] %>
            <% values.each.with_index do |value, k| %>
    
             <%# course = 'math' %>
              <%= ff.simple_fields_for course, index: k do |fff| %>
    
                <%# value = {:name=>"Math A", :hours=>10, :grade=>"B"}  %>
                <% value.each do |input_name, input_value| %>
    
                  <%# input_name = "hours"; input_value = "10" %>
                  <%= fff.input input_name, input_html: { value: input_value } %> <%# <input value="10" class="string required" type="text" name="courses_payload[courses][0][math][0][hours]" id="courses_payload_courses_0_math_0_hours"> %>
    
                <% end %>
              <% end %>
    
            <% end %>
          <% end %>
        <% end %>
      <% end %>
    
      <%= f.submit  %>
    <% end %>
    

    To make the form submit an actual array is a bit tricky. Input name attribute has to look like this:

    courses_payload[courses][][english][][number_of_students]
    

    We need to explicitly tell the form builder the index values are blank (nil works in some situations).

    <%= simple_form_for :courses_payload, url: "/courses/#{hash[:id]}", method: :post do |f| %>
      <% hash[:courses].each do |courses| %>
        <% courses.each do |course, values| %>
          <%= f.simple_fields_for 'courses', index: '' do |ff| %>
            <% values.each do |value| %>
              <% value.each do |input_name, input_value| %>
                <%= ff.simple_fields_for course, index: '' do |fff| %>
                  <%= fff.input input_name, input_html: { value: input_value } %> <%# <input value="10" class="string required" type="text" name="courses_payload[courses][][math][][hours]" id="courses_payload_courses__math__hours"> %>
                <% end %>
              <% end %>
            <% end %>
          <% end %>
        <% end %>
      <% end %>
    
      <%= f.submit  %>
    <% end %>
    

    A couple of my other answers that involve arrays in forms:

    https://stackoverflow.com/a/71688643/207090

    https://stackoverflow.com/a/71921385/207090