You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails长表单优化:新增控制器能否提升组织性?

Hey Owen, great question! Let's break this down clearly—yes, adding a second controller can absolutely help organize your long health questionnaire, but we'll also tackle your two immediate issues along the way.

1. Does adding a second controller improve form organization?

Absolutely. When you're dealing with 50+ fields split into distinct sections (like basic health, medication history, lifestyle habits), splitting your single QuestionnaireController into smaller, focused controllers (e.g., BasicHealthController, MedicationHistoryController) will:

  • Keep each controller's code concise and focused on one section of the form
  • Let you split the long form into a multi-step/wizard flow (better for user experience)
  • Make it easier to maintain and update individual sections without touching the entire codebase

That said, this works best when paired with model refactoring—don't cram all 50 fields into a single Questionnaire model. Extract repeatable sections (like medication history) into their own associated models (e.g., Medication).

2. Fixing your two current pain points

(1) Friendlier field names in the show view

You don't need a new controller to fix this—there are two straightforward approaches:

  • Add human-readable attribute names directly in your Questionnaire model:
    class Questionnaire < ApplicationRecord
      HUMANIZED_NAMES = {
        q1: "Have you had a temperature over 37.5°C in the last week?",
        q2: "Do you have a history of chronic heart disease?",
        # Add all your field mappings here
      }
    
      def self.human_attribute_name(attr, options = {})
        HUMANIZED_NAMES[attr.to_sym] || super
      end
    
    Now SimpleForm will automatically use these friendly names in both forms and the show view, no manual edits needed.
  • If you prefer to set labels per-view, override them directly in your SimpleForm calls:
    <%= f.input :q1, label: "Have you had a temperature over 37.5°C in the last week?" %>
    

(2) Dynamic multi-row fields for repeatable sections

This requires nested attributes + a bit of JavaScript, and splitting into an associated model will make this cleaner:

  1. Extract the repeatable section to a new model:
    # app/models/questionnaire.rb
    class Questionnaire < ApplicationRecord
      has_many :medications, dependent: :destroy
      accepts_nested_attributes_for :medications, allow_destroy: true, reject_if: :all_blank
    end
    
    # app/models/medication.rb
    class Medication < ApplicationRecord
      belongs_to :questionnaire
    end
    
  2. Build the dynamic form with SimpleForm:
    In your main questionnaire form:
    <%= simple_form_for @questionnaire do |f| %>
      <!-- Your non-repeatable health fields here -->
    
      <h3>Medication History</h3>
      <table class="medication-table">
        <thead>
          <tr>
            <th>Medication Name</th>
            <th>Dosage</th>
            <th>Duration of Use</th>
            <th></th>
          </tr>
        </thead>
        <tbody id="medications-container">
          <%= f.simple_fields_for :medications do |med_form| %>
            <%= render "medication_fields", f: med_form %>
          <% end %>
        </tbody>
      </table>
      <%= link_to "Add Medication", "#", id: "add-med-btn", class: "btn" %>
    
      <%= f.submit "Save Questionnaire" %>
    <% end %>
    
  3. Create a partial for medication fields:
    app/views/questionnaires/_medication_fields.html.erb:
    <tr class="medication-row">
      <td><%= f.input :name, label: false %></td>
      <td><%= f.input :dosage, label: false %></td>
      <td><%= f.input :duration, label: false %></td>
      <td>
        <%= f.hidden_field :_destroy %>
        <%= link_to "Remove", "#", class: "remove-med-btn" %>
      </td>
    </tr>
    
  4. Add JavaScript for dynamic rows:
    document.addEventListener('DOMContentLoaded', function() {
      const addBtn = document.getElementById('add-med-btn');
      const container = document.getElementById('medications-container');
      const template = document.querySelector('.medication-row').cloneNode(true);
    
      addBtn.addEventListener('click', function(e) {
        e.preventDefault();
        const timestamp = new Date().getTime();
        // Replace placeholder IDs with a unique timestamp
        const newFields = template.innerHTML.replace(/\[(\d+)\]/g, `[${timestamp}]`).replace(/_(\d+)_/g, `_${timestamp}_`);
        const newRow = document.createElement('tr');
        newRow.innerHTML = newFields;
        container.appendChild(newRow);
      });
    
      // Handle row removal
      document.addEventListener('click', function(e) {
        if (e.target.classList.contains('remove-med-btn')) {
          e.preventDefault();
          const row = e.target.closest('.medication-row');
          row.querySelector('input[type="hidden"]').value = '1';
          row.style.display = 'none';
        }
      });
    });
    
3. Controller splitting best practices

If you go the controller split route, organize controllers by form section:

  • BasicHealthController: Handles static, non-repeatable fields (age, gender, general health)
  • MedicationHistoryController: Manages CRUD for medication entries (can work with nested routes under questionnaires)
  • LifestyleController: Handles lifestyle-related questions (smoking, exercise, diet)

This way, each controller only manages one logical section of your questionnaire, making debugging and updates far easier.

内容的提问来源于stack exchange,提问作者Owen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:19:32