Introduction

Rails strong parameters protect against mass assignment by requiring explicit whitelisting of permitted attributes. Unpermitted parameters are silently removed from the params hash without raising an error in production. This means form fields can be submitted successfully but never saved to the database, creating confusing bugs where data appears to be lost.

Symptoms

  • Form submits successfully but certain fields are blank in database
  • User profile updates silently ignore new fields after model changes
  • Nested attributes not saved for has_many associations
  • API requests return 200 OK but missing fields in response
  • No error logged when parameters are filtered

Check for unpermitted params in logs: `` Unpermitted parameter: :phone_number. Context: { controller: UsersController, action: update, request: #<ActionDispatch::Request>, params: {"user"=>{"name"=>"John", "phone_number"=>"+1234567890"}} }

Common Causes

  • New field added to form but not to permit list in controller
  • Nested attributes structure changed (array vs hash)
  • require called on wrong param key level
  • Permitted scalar types vs array types confusion
  • ActionController::Parameters not converted to hash before use

Step-by-Step Fix

  1. 1.Enable logging of unpermitted parameters:
  2. 2.```ruby
  3. 3.# config/environments/development.rb
  4. 4.# Already on by default in development

# config/environments/production.rb # Turn on in production temporarily to debug config.action_controller.action_on_unpermitted_parameters = :log

# Or raise error (useful in test environment) config.action_controller.action_on_unpermitted_parameters = :raise ```

  1. 1.Fix permit list for nested attributes:
  2. 2.```ruby
  3. 3.class UsersController < ApplicationController
  4. 4.def user_params
  5. 5.params.require(:user).permit(
  6. 6.:name,
  7. 7.:email,
  8. 8.:phone_number,
  9. 9.:avatar,
  10. 10.# Nested has_many
  11. 11.addresses_attributes: [:id, :street, :city, :state, :zip, :_destroy],
  12. 12.# Nested has_one
  13. 13.profile_attributes: [:id, :bio, :website],
  14. 14.# Array of scalars
  15. 15.role_ids: [],
  16. 16.# Nested array of objects
  17. 17.preferences: [:theme, :language, :notifications],
  18. 18.)
  19. 19.end
  20. 20.end
  21. 21.`
  22. 22.Handle dynamic permitted parameters safely:
  23. 23.```ruby
  24. 24.class UsersController < ApplicationController
  25. 25.def user_params
  26. 26.# Safe approach: derive from model columns
  27. 27.params.require(:user).permit(permitted_user_attributes)
  28. 28.end

private

def permitted_user_attributes # Only permit attributes that exist on the model User.column_names.map(&:to_sym) + [ # Virtual attributes :current_password, :password_confirmation, # Nested attributes addresses_attributes: Address.attribute_names.map(&:to_sym) + [:id, :_destroy], ] end end ```

  1. 1.Add test to catch missing permits:
  2. 2.```ruby
  3. 3.# test/controllers/users_controller_test.rb
  4. 4.test "should permit all submitted parameters" do
  5. 5.Rails.application.config.action_controller.action_on_unpermitted_parameters = :raise

patch user_url(@user), params: { user: { name: "Updated", email: "new@example.com", phone_number: "+1234567890", # Will raise if not permitted } }

assert_response :redirect end ```

  1. 1.Debug missing params in development console:
  2. 2.```ruby
  3. 3.# Check what params actually contains
  4. 4.puts params.inspect
  5. 5.puts params.to_unsafe_h.inspect # Shows ALL params including unpermitted

# Compare permitted vs submitted permitted = params.require(:user).permit(:name, :email) all_params = params.require(:user).to_unsafe_h missing = all_params.keys - permitted.keys

puts "Missing parameters: #{missing}" ```

Prevention

  • Set action_on_unpermitted_parameters = :log in production
  • Add controller tests that submit all expected fields with :raise enabled
  • Use params.to_unsafe_h in logging for debugging (never in production code)
  • Keep permit lists close to the model definition for easy review
  • Use a form object pattern (Reform, Dry-Validation) for complex forms
  • Document parameter structure in API documentation