Categories
Mastering Development

Rails – mark invalid nested fields

Rails is not marking nested attributes as invalid when validations fail.

For example:
I have two models; User and UserProfile.

User contains email and password
UserProfile contains username, first_name, last_name, etc

User validates that email is present and unique
UserProfile validates that username is present and matches a regex

On the sign-up form, User accepts nested attributes for UserProfile.username.

If you leave all fields blank and press submit, all validation errors are showing correctly. However, only the fields that are directly related to User have the .is-invalid class, and the username field does not.

How do I get nested form fields to append the .is-invalid class on failed validations? Is that something I need to do manually somewhere?

User model:

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :confirmable, :lockable, stretches: 13

  # -- Relationships --------------------------------------------------------

  has_one :user_profile, dependent: :destroy
  accepts_nested_attributes_for :user_profile

  # -- Validations --------------------------------------------------------

  validates_presence_of :email
  validates_presence_of :password
  validates_presence_of :password_confirmation
end

UserProfile model:

class UserProfile < ApplicationRecord
  # -- Relationships --------------------------------------------------------

  belongs_to :user

  # -- Validations --------------------------------------------------------
  validates_presence_of :username
end

Sign up form:

= simple_form_for @new_user do |f|
  = render 'devise/shared/error_messages', resource: resource
  = f.simple_fields_for :user_profile, :user_profile do |up|
    = up.input :username, autocomplete: 'username'
  = f.input :email, autocomplete: 'email'
  = f.input :password, autocomplete: 'new-password'
  = f.input :password_confirmation, autocomplete: 'new-password'
  .mt-4.mb-0.col-12
    = f.submit t('submit'), class: 'btn btn-primary btn-block'

Note that in the view I’m using simple_form, but I tested the same with the out-of-the-box functionality.

Registrations controller:

class Users::RegistrationsController < Devise::RegistrationsController
  layout 'unauthenticated_blank', only: [:new, :create]
  before_action :configure_sign_up_params
  before_action :build_user

  protected

  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password, :password_confirmation, user_profile_attributes: [:username]])
  end

  private

  def build_user
    @new_user = User.new
    @new_user.build_user_profile
  end
end

Leave a Reply

Your email address will not be published. Required fields are marked *