Search code examples
ruby-on-railsparameterswhitelist

Rails form fields passing old data to whitelisted params


UPDATE: Fixed, thanks for pointing it out kjmagic13. You can see the solution below.

So I've been working on this one since yesterday. I have a Rails project with a form_for collecting client data like name, age, etc. There's an "Add spouse?" button which uses javascript to reveal additional spouse input fields. All of these attributes are whitelisted, and the spouse data are actually no more than attributes on the client model (no spouse object).

The FIRST time you input data to these fields, they all pass through fine and get saved. Then when you revisit the page, the form is prepopulated with that data. You can edit the fields and update the client info, but NOT THE SPOUSE FIELDS --> even though those fields are whitelisted and are nothing more than client attributes. I can't even imagine how I'd make this happen on purpose, much less figure out how to fix it.

The form gets submitted, and the params passed in are always the old spouse data, not what went into the input field. However all the new client info IS updated in the same form. Looking in the logs always shows the old spouse data being passed through, as if I never typed anything

Here's the relevant snippets:

in the clients_controller

def update
  if @client.report_ready?
    outdate_report
  end

  respond_to do |format|
    if @client.update(client_params)
      format.html { redirect_to @client, notice: 'Client was successfully updated.' }
      format.json { render :show, status: :ok, location: @client }
    else
      format.html { render :show }
      format.json { render json: @client.errors, status: :unprocessable_entity }
    end
  end
end

def client_params
  params.require(:client).permit(:first_name, :last_name, :email, :dob, :address, :city, :state, :zip, :phone, :retire_age, :ss_age, :user_id, :spouse_dob, :spouse_retire_age, :spouse_ss_age, :spouse_first_name, :spouse_last_name)
end

# the form in the view
<%= form_for (@client) do |f| %>
<% if @client.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@client.errors.count, "error") %> prohibited this plan from being saved:</h2>
      <ul>
      <% @client.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <div>
      <%= f.label :first_name, class: 'left' %><br>
      <%= f.text_field :first_name %>
    </div>
    <div>
      <%= f.label :last_name, class: 'left' %><br>
      <%= f.text_field :last_name %>
    </div>
    <div>
      <%= f.label :email, class: 'left' %><br>
      <%= f.email_field :email %>
    </div>
    <div>
      <%= f.label :phone, class: 'left' %><br>
      <%= f.phone_field :phone %>
    </div>
    <div>
      <%= f.label :dob, 'Birthdate', class: 'left' %>
      <%= f.date_field :dob, style: "font-size: .9em;" %>
    </div>
    <div>
      <% if @client.dob.present? %>
        <%= f.label :age, class: 'left' %><br>
        <%= text_field_tag :age, @client.age, readonly: true %>
      <% else %>
        <%= hidden_field :age, value: nil %>
      <% end %>
    </div>
    <div>
      <%= f.label :retire_age, 'Ret. age', class: 'left' %><br>
      <%= f.number_field :retire_age %>
    </div>
    <div>
      <%= f.label :ss_age, 'S.S. age', class: 'left' %><br>
      <%= f.number_field :ss_age %>
    </div>
    <div>
      <%= f.submit "Save", class: "button radius tiny" %>  
    </div>

    <% if !@client.new_record? && @client.has_spouse? %>
      <%= render 'spouse_fields', f: f  %>
    <% else %>
      <%= render 'spouse_button' %>
    <% end %>

    <span id="spouse_fields" style="display:none">
      <%= render 'spouse_fields', f: f %>
    </span>

# the spouse fields (which are really just a continuation of the form)
<hr>
<h1>Spouse Information</h1>
<div>
  <%= f.label :spouse_first_name, 'First name', class: 'left' %><br>
  <%= f.text_field :spouse_first_name %>
</div>
<div>
  <%= f.label :spouse_last_name, 'Last name', class: 'left' %><br>
  <%= f.text_field :spouse_last_name %>
</div>
<div>
  <%= f.label :spouse_dob, 'Date of Birth', class: 'left' %><br>
  <%= f.date_field :spouse_dob, style: "font-size: .9em;" %>
</div>
<div>
  <% if @client.spouse_dob.present? %>
    <%= f.label :spouse_age, 'Age', class: 'left' %><br>
    <%= text_field_tag :spouse_age, @client.spouse_age, readonly: true%>
  <% else %>
    <%= hidden_field :spouse_age, value: nil %>
  <% end %>
</div>
<div>
  <%= f.label :spouse_retire_age, 'Ret. age', class: 'left' %><br>
  <%= f.number_field :spouse_retire_age %>
</div>
<div>
  <%= f.label :spouse_ss_age, 'S.S. age', class: 'left' %><br>
  <%= f.number_field :spouse_ss_age %>
</div>
<div>
  <%= f.submit "Save", class: "button radius tiny" %>  
</div>

# the js for the 'Add spouse button' (just reveals fields, then disappears)
<text id="spouse_button" onclick="toggleFields()">
  Add Spouse?
</text>

<script>
  function toggleFields() {
    document.getElementById("spouse_fields").style.cssText = "";
    document.getElementById("spouse_button").style.cssText = "display:none";
  };
</script>

FIX:

All I had missed was the location of the hidden extra spouse fields. They needed to be moved into the if/else block above it, like this (but kjmagic13's 2-line solution is clearly better):

<% if !@client.new_record? && @client.has_spouse? %>
  <%= render 'spouse_fields', f: f  %>
<% else %>
  <%= render 'spouse_button' %>

  <span id="spouse_fields" style="display:none">
    <%= render 'spouse_fields', f: f %>
  </span>
<% end %>

Solution

  • You're duplicating the spouse_fields partial when editing the record. There will be two elements with the ID of spouse_fields and the Javascript will only toggle the first one it comes across. The other will remain hidden and is most likely the fields being passed to the server.

    <% if !@client.new_record? && @client.has_spouse? %>
        <%= render 'spouse_fields', f: f # renders here on edit  %>
    <% else %>
        <%= render 'spouse_button' %>
    <% end %>
    
    <span id="spouse_fields" style="display:none">
        <%= render 'spouse_fields', f: f # and renders here on edit %>
    </span>
    

    You'll want something more along the lines of this:

    <%= render 'spouse_button' unless @client.has_spouse? %>
    <%= content_tag :span, render('spouse_fields', f: f), style: ( @client.has_spouse? ? nil : 'display:none' ) -%>