Search code examples
ruby-on-railsruby-on-rails-4deviserails-activerecordsingle-table-inheritance

Multiple User Types with Devise and STI - Password Change Issue


I am building out an app using ruby 2.3.1 and rails 5, user authentication is Devise 4.

This project, my instructor wants an app with User, AdminUser ClientUser and SupportUser types, he is not allowing DeviseInvitable to be used, this app will never be used in production.

My workflow, at present is as follows, when a user (of any type is created) a password is generated and sent (this works), a user after confirming the account and logging in is forced to change their password (this works for User but not for AdminUser SupportUser and ClientUser)

here is how this is all setup:

#application_controller.rb
  # Force User To Change Password On First Login
  def after_sign_in_path_for(resource)
    if current_user.pw_changed == "false"
      edit_passwords_path
    else
      authenticated_root_path
    end
  end

passwords_controller.rb

class PasswordsController < ApplicationController

  def edit
    @user = current_user
  end

  def update
    if current_user.update_with_password(user_params.merge(pw_changed: "true"))
      flash[:notice] = 'Your Password Has Been Sucessfully Updated.'
      redirect_to authenticated_root_path
    else
      flash[:error] = 'Oh No! Something Went Wrong, Please Try Again.'
      render :edit
    end
  end

  private
    def user_params
      params.require(:user).permit(:current_password, :password, :password_confirmation)
    end


#views
_form.html.erb
edit.html.erb



# routes.rb
  # Force Password Change Routes and Resources
  resource :passwords

  resources :users, :type => 'User'
  resources :admin_users, :type => 'AdminUser'
  resources :client_users, :type => 'ClientUser'
  resources :support_users, :type => 'SupportUser'

My Models:

class User < ApplicationRecord - This also acts as custom show and user index
end

class AdminUser < User
end

class ClientUser < User
end

class SupportUser < User
end

when I Try to update an AdminUsers Password on first login this is the error and server output I get:

enter image description here

Server Output

Started PATCH "/passwords" for ::1 at 2016-09-26 14:59:28 -0600
Processing by PasswordsController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"yEO6cUTLv8x2MU9lf1+bOYrpWTie17wE0TsIDEilbJzMWKSWmlQHC8mC3Bb9bkP3L3OhooY+wuwolFDO6fzxlg==", "admin_user"=>{"current_password"=>"[FILTERED]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Update Admin user"}
  User Load (0.9ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 5], ["LIMIT", 1]]
Completed 400 Bad Request in 3ms (ActiveRecord: 0.9ms)



    ActionController::ParameterMissing - param is missing or the value is empty: user:
      actionpack (5.0.0.1) lib/action_controller/metal/strong_parameters.rb:308:in `require'
      app/controllers/passwords_controller.rb:19:in `user_params'
      app/controllers/passwords_controller.rb:8:in `update'
      actionpack (5.0.0.1) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
      actionpack (5.0.0.1) lib/abstract_controller/base.rb:188:in `process_action'
      actionpack (5.0.0.1) lib/action_controller/metal/rendering.rb:30:in `process_action'
      actionpack (5.0.0.1) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:126:in `call'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:506:in `block (2 levels) in compile'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:455:in `call'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:448:in `block (2 levels) in around'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:267:in `block (2 levels) in halting_and_conditional'
      activesupport (5.0.0.1) lib/active_support/core_ext/time/zones.rb:64:in `use_zone'
      app/controllers/application_controller.rb:20:in `user_time_zone'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:382:in `block in make_lambda'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:266:in `block in halting_and_conditional'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:447:in `block in around'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:455:in `call'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:101:in `__run_callbacks__'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:750:in `_run_process_action_callbacks'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:90:in `run_callbacks'
      actionpack (5.0.0.1) lib/abstract_controller/callbacks.rb:19:in `process_action'
      actionpack (5.0.0.1) lib/action_controller/metal/rescue.rb:20:in `process_action'
      actionpack (5.0.0.1) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
      activesupport (5.0.0.1) lib/active_support/notifications.rb:164:in `block in instrument'
      activesupport (5.0.0.1) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
      activesupport (5.0.0.1) lib/active_support/notifications.rb:164:in `instrument'
      actionpack (5.0.0.1) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
      actionpack (5.0.0.1) lib/action_controller/metal/params_wrapper.rb:248:in `process_action'
      activerecord (5.0.0.1) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
      actionpack (5.0.0.1) lib/abstract_controller/base.rb:126:in `process'
      actionview (5.0.0.1) lib/action_view/rendering.rb:30:in `process'
      actionpack (5.0.0.1) lib/action_controller/metal.rb:190:in `dispatch'
      actionpack (5.0.0.1) lib/action_controller/metal.rb:262:in `dispatch'
      actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
      actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:32:in `serve'
      actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:39:in `block in serve'
      actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:26:in `each'
      actionpack (5.0.0.1) lib/action_dispatch/journey/router.rb:26:in `serve'
      actionpack (5.0.0.1) lib/action_dispatch/routing/route_set.rb:725:in `call'
      warden (1.2.6) lib/warden/manager.rb:35:in `block in call'
      warden (1.2.6) lib/warden/manager.rb:34:in `catch'
      warden (1.2.6) lib/warden/manager.rb:34:in `call'
      rack (2.0.1) lib/rack/etag.rb:25:in `call'
      rack (2.0.1) lib/rack/conditional_get.rb:38:in `call'
      rack (2.0.1) lib/rack/head.rb:12:in `call'
      rack (2.0.1) lib/rack/session/abstract/id.rb:222:in `context'
      rack (2.0.1) lib/rack/session/abstract/id.rb:216:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/cookies.rb:613:in `call'
      activerecord (5.0.0.1) lib/active_record/migration.rb:552:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/callbacks.rb:38:in `block in call'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:97:in `__run_callbacks__'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:750:in `_run_call_callbacks'
      activesupport (5.0.0.1) lib/active_support/callbacks.rb:90:in `run_callbacks'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/callbacks.rb:36:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
      better_errors (2.1.1) lib/better_errors/middleware.rb:84:in `protected_app_call'
      better_errors (2.1.1) lib/better_errors/middleware.rb:79:in `better_errors_call'
      better_errors (2.1.1) lib/better_errors/middleware.rb:57:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/debug_exceptions.rb:49:in `call'
      web-console (3.3.1) lib/web_console/middleware.rb:131:in `call_app'
      web-console (3.3.1) lib/web_console/middleware.rb:28:in `block in call'
      web-console (3.3.1) lib/web_console/middleware.rb:18:in `catch'
      web-console (3.3.1) lib/web_console/middleware.rb:18:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
      railties (5.0.0.1) lib/rails/rack/logger.rb:36:in `call_app'
      railties (5.0.0.1) lib/rails/rack/logger.rb:24:in `block in call'
      activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:70:in `block in tagged'
      activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:26:in `tagged'
      activesupport (5.0.0.1) lib/active_support/tagged_logging.rb:70:in `tagged'
      railties (5.0.0.1) lib/rails/rack/logger.rb:24:in `call'
      sprockets-rails (3.2.0) lib/sprockets/rails/quiet_assets.rb:13:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/request_id.rb:24:in `call'
      rack (2.0.1) lib/rack/method_override.rb:22:in `call'
      rack (2.0.1) lib/rack/runtime.rb:22:in `call'
      activesupport (5.0.0.1) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
      actionpack (5.0.0.1) lib/action_dispatch/middleware/static.rb:136:in `call'
      rack (2.0.1) lib/rack/sendfile.rb:111:in `call'
      railties (5.0.0.1) lib/rails/engine.rb:522:in `call'
      puma (3.6.0) lib/puma/configuration.rb:225:in `call'
      puma (3.6.0) lib/puma/server.rb:578:in `handle_request'
      puma (3.6.0) lib/puma/server.rb:415:in `process_client'
      puma (3.6.0) lib/puma/server.rb:275:in `block in run'
      puma (3.6.0) lib/puma/thread_pool.rb:116:in `block in spawn_thread'

    Started POST "/__better_errors/462cce3c438acc05/variables" for ::1 at 2016-09-26 14:59:28 -0600

Solution

  • You can use ActiveModel::Naming to get param keys, I18n keys or humanized versions of the names of any of your models.

    class PasswordsController < ApplicationController
    
      def edit
      end
    
      def update
        if current_user.update_with_password(user_params.merge(pw_changed: "true"))
          flash[:notice] = 'Your Password Has Been Sucessfully Updated.'
          redirect_to authenticated_root_path
        else
          flash[:error] = 'Oh No! Something Went Wrong, Please Try Again.'
          render :edit
        end
      end
    
      private
        def user_params
          param_key = current_user.class.model_name.param_key
          params.require(param_key)
                .permit(:current_password, :password, :password_confirmation)
        end
    end
    

    You should also learn how to use the case statement (the Ruby equivalent of switch)