Skip to content

Duplicate error messages on User objects #12

@mokolabs

Description

@mokolabs

My app is using message_block to show all errors and flash messages.

So I've got this in my application layout:
= message_block on: :all

It's been working great... with one exception. When I try to update my password while being logged in, if there is a validation error, the error output appears twice:

  • Password is too short (minimum is 3 characters)
  • Password confirmation doesn't match Password
  • Password confirmation can't be blank
  • Password is too short (minimum is 3 characters)
  • Password confirmation doesn't match Password
  • Password confirmation can't be blank

It took me a long time to figure out why this was happening. But I finally did.

When I try to change my password while I'm logged in, there are actually two instances of the User model loaded by the view.

One is an instance of current_user (the convenience method used by most Rails authentication gems as a proxy for a logged in user). The other is a more generic instance of the User model -- accessed via @user -- that I'm using to update the password.

And, yes, @user is really a copy of current_user, but there are still technically two User objects being loaded in the view.

Here are the relevant bits of the controller for reference

class PasswordsController < ApplicationController
  before_action :require_login

  def update
    @user = current_user
    
    if params[:user][:password].blank? and params[:user][:password_confirmation].blank?
      flash.now[:error] = "You forgot to enter a new password."
      render :edit
      return
    end

    @user.password_confirmation = params[:user][:password_confirmation]

    if @user.change_password!(params[:user][:password])
      @user.update_attribute(:reset_password_email_sent_at, nil)
      flash[:notice] = "Your password was successfully updated."
      redirect_to root_path
    else
      render :edit
    end
  end

end

One way to fix this would be to reject the current_user object from the list of objects that are checked for errors in the message_block helper.

model_objects = options[:on].map do |model_object|
  if model_object == :all
    assigns.delete("current_user") # <- ADD THIS LINE OF CODE
    assigns.values.select {|o| o.respond_to?(:errors) && o.errors.is_a?(ActiveModel::Errors) }
  elsif model_object.instance_of?(String) or model_object.instance_of?(Symbol)
    instance_variable_get("@#{model_object}")
  else
    model_object
  end
end.flatten.select {|m| !m.nil? }

But, ultimately, I think a better approach for the :all option would be to grab every user model from the app/models directory -- which is how we're doing it with Graffletopia, albeit as an initializer.

MODELS = Dir['app/models/*.rb'].map {|f| File.basename(f, '.rb').downcase.to_sym}

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions