Search code examples
ruby-on-railsrubydevise

How to create user without password and set it later with Devise form?


I'm building a Rails app and I want to create users on the rails console WITHOUT password, receive a confirmation email, and by clicking on the link in the confirmation email, set the password on my website. (I'm using Devise)

Here is what I tried so far:

app/models/user.rb

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

  protected
  def password_required?
    confirmed? ? super : false
  end
end

app/controllers/users/confirmations_controller.rb

class Users::ConfirmationsController < Devise::ConfirmationsController
  protected
  def after_confirmation_path_for(resource_name, resource)
    sign_in(resource)
    edit_registration_path(resource)
  end
end

I specifically did sign_in(resource) because I want people to be signed in during the process.

app/controllers/users_controller.rb

class UsersController < ApplicationController
    def create
    end
end

For the moment, when I create a user through the rails console and then click the confirmation link, I end up in the devise view, to edit my account (more specifically my password), which is great, but I can't validate the form since I have to fill my previous password to change it. But since I don't have set any password during the creation I'm stuck!

Any ideas about how I could this?

Thanks

EDIT

As mentioned in the comment, I tried to use the "forgot my password" link. It works, but after setting their password, user will have to sign in (so enter again their password). In my opinion, it might not be a very good customer experience, that's why I would like to know if there's a way to do it as I explained in my post, OR a way to sign in the user after he set his password for the first time.

UPDATE

After some suggestions, I did some changes in my files, but I still get the error saying that the current password can't be blank. Here is my code:

routes.rb

Rails.application.routes.draw do
  root to: 'page#index'

  devise_for :users, path: '', path_names: { sign_in: 'sign_in', sign_out: 
  'sign_out'}, controllers: { confirmations: 'users/confirmations', 
  registrations: 'users/registrations' }
end

app/controllers/users/confirmations_controller.rb

class Users::ConfirmationsController < Devise::ConfirmationsController
  def update_resource(resource, params)
    if resource.encrypted_password.present?
      super
    else
      resource.update(params)
    end
  end

  protected
  def after_confirmation_path_for(resource_name, resource)
    sign_in(resource)
    edit_registration_path(resource)
  end
end

user.rb

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

  protected
  def password_required?
    confirmed? ? super : false
  end
end

app/views/devise/registrations/edit.html.erb

<h2>Edit your account</h2>

<div>
  <%= devise_error_messages! %>
  <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
    <p><%= current_user.first_name %> <%= current_user.last_name %></p>
    <p>Email address: <strong><%= current_user.email %></strong></p>

    <div class="container mb-5">
      <div class="row">
        <%= f.label :password %>
      </div>
      <div class="row">
        <%= f.password_field :password, autofocus: true, class: 'form-control', :required => 'required' %>
      </div>
    </div>

    <div class="container mb-5">
      <div class="row">
        <%= f.label :password_confirmation %>
      </div>
      <div class="row">
        <%= f.password_field :password_confirmation, autofocus: true, class: 'form-control', :required => 'required' %>
      </div>
    </div>



    <div class="container text-center mb-3">
      <%= f.submit "Update", class: 'navbar-cta' %>
    </div>
  <% end %>
</div>

logs

Started GET "/edit" for ::1 at 2020-01-20 18:06:29 +0100
Processing by Users::RegistrationsController#edit as HTML
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 33], ["LIMIT", 1]]
  Rendering devise/registrations/edit.html.erb within layouts/application
DEPRECATION WARNING: [Devise] `DeviseHelper.devise_error_messages!`
is deprecated and it will be removed in the next major version.
To customize the errors styles please run `rails g devise:views` and modify the
`devise/shared/error_messages` partial.
 (called from _app_views_devise_registrations_edit_html_erb___445667363343301985_70311530657080 at /Users/victor/Documents/SaaS projects/ChurnTarget/app/views/devise/registrations/edit.html.erb:6)
  Rendered devise/registrations/edit.html.erb within layouts/application (Duration: 7.3ms | Allocations: 1979)
  Rendered layouts/_google_analytics.html.erb (Duration: 0.4ms | Allocations: 164)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered page/_navbar.html.erb (Duration: 1.9ms | Allocations: 669)
  Rendered page/_footer.html.erb (Duration: 0.8ms | Allocations: 162)
Completed 200 OK in 226ms (Views: 221.6ms | ActiveRecord: 0.5ms | Allocations: 61076)


Started PUT "/" for ::1 at 2020-01-20 18:07:00 +0100
Processing by Users::RegistrationsController#update as HTML
  Parameters: {"authenticity_token"=>"99pJ5XaS5k5NQmba31GrTu5+jeN57mdPV51XlG6WFJoizS/5rbeLerzmTQv+kbsPIPorjH9fjAz3ihPxXENo1w==", "user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Update"}
  User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 33], ["LIMIT", 1]]
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 33], ["LIMIT", 1]]
Unpermitted parameter: :password_confirmation
  Rendering devise/registrations/edit.html.erb within layouts/application
DEPRECATION WARNING: [Devise] `DeviseHelper.devise_error_messages!`
is deprecated and it will be removed in the next major version.
To customize the errors styles please run `rails g devise:views` and modify the
`devise/shared/error_messages` partial.
 (called from _app_views_devise_registrations_edit_html_erb___445667363343301985_70311530657080 at /Users/victor/Documents/SaaS projects/ChurnTarget/app/views/devise/registrations/edit.html.erb:6)
  Rendered devise/shared/_error_messages.html.erb (Duration: 2.0ms | Allocations: 441)
  Rendered devise/registrations/edit.html.erb within layouts/application (Duration: 7.5ms | Allocations: 1514)
  Rendered layouts/_google_analytics.html.erb (Duration: 0.1ms | Allocations: 8)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered page/_navbar.html.erb (Duration: 0.1ms | Allocations: 15)
  Rendered page/_footer.html.erb (Duration: 0.0ms | Allocations: 5)
Completed 200 OK in 208ms (Views: 40.7ms | ActiveRecord: 1.1ms | Allocations: 20837)

Tell me if there are other files that you would like to see.


Solution

  • A different solution altogether would be to use Devise::Invitable that provides the feature that you're probably looking for.

    It gives you a /users/invitations/new path with a form that you can fill out which invites users. The user record is saved and then the user completes the registration process by accepting the invitation.

    If you really wanted to you could send the invitations from the console with:

    User.invite!(email: 'someone@example.com')
    

    But really I would just setup some basic authorization with Pundit or CanCanCan to lock down the invitations controller and do it through the GUI. You're most likely going to need it anyways.