Basic user registration for Rails 8 authentication generator

Run this after running `rails generate authentication` to add user registration feature
Icons/chart bar
Used 170 times
Created by
H Haseeb Annadamban

Usage
Rails 8 authentication generator does not have a user registration. This template brings user registration for the Rails 8 authentication generator. It also has an optional email confirmation feature.
This has two expectations
  1.  The Rails 8 authentication generator has been run just before running this (`rails generate authentication`)
  2. the root_url has been set at the end of routes.rb (can be set after running this)

Run this command in your Rails app directory in the terminal:

rails app:template LOCATION="https://railsbytes.com/script/Xg8sMD"
Template Source

Review the code before running this template on your machine.

def create_registrations_controller
  generate :controller, 'Registrations'

  inject_into_file 'app/controllers/registrations_controller.rb', after: "class RegistrationsController < ApplicationController\n" do
    <<-RUBY
  allow_unauthenticated_access only: [ :new, :create ]

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      start_new_session_for(@user)
      redirect_to root_url, notice: "Registered successfully"
    else
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(:email_address, :password, :password_confirmation)
  end
    RUBY
  end
end

def setup_registration_view
  file 'app/views/registrations/new.html.erb', <<-ERB
<h1>Register</h1>

<%= form_with(model: @user, url: registration_path, local: true) do |form| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
      <ul>
        <% @user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
  <%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %><br>
  <%= form.password_field :password_confirmation, required: true, autocomplete: "current-password", placeholder: "Confirm password", maxlength: 72 %><br>

  <%= form.submit "Register" %>
<% end %>

<%= link_to "Login", new_session_path %>
  ERB

  inject_into_file 'app/views/sessions/new.html.erb', before: '<%= link_to "Forgot password?", new_password_path %>' do
    <<-ERB
<%= link_to "Register", new_registration_path %> <br>
    ERB
  end
end

def add_routes
  route('resource :registration, only: [ :new, :create ]')
end

def add_user_validations
  inject_into_file 'app/models/user.rb', after: "normalizes :email_address, with: -> e { e.strip.downcase }\n" do
    <<-RUBY
  validates :email_address, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
    RUBY
  end
end

def setup_email_confirmation
  generate :migration, 'AddConfirmationToUsers', 'confirmation_token:string:index', 'confirmed_at:datetime'

  inject_into_file 'app/models/user.rb', after: "has_secure_password\n" do
    "  has_secure_token :confirmation_token\n"
  end

  inject_into_file 'app/models/user.rb', after: "validates :email_address, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }\n" do
    <<-RUBY

  def confirm!
    update!(confirmed_at: Time.current, confirmation_token: nil)
  end

  def confirmed?
    confirmed_at.present?
  end

  def send_confirmation_instructions
    regenerate_confirmation_token
    UserMailer.confirmation_instructions(self).deliver_now
  end
    RUBY
  end

  gsub_file 'app/controllers/concerns/authentication.rb', /def authenticated\?\n\s+Current\.session\.present\?\n\s+end\n/m do
<<-RUBY
def authenticated?
      Current.session.present? && Current.session.user.confirmed?
    end
RUBY
  end

  gsub_file 'app/controllers/concerns/authentication.rb', /def require_authentication\n\s+resume_session \|\| request_authentication\n\s+end\n/m do
<<-RUBY
def require_authentication
      if resume_session
        request_confirmation unless Current.session.user.confirmed?
      else
        request_authentication
      end
    end
RUBY
  end

  inject_into_file 'app/controllers/concerns/authentication.rb', before: /\n\s*def request_authentication/ do
    <<-RUBY


    def request_confirmation
      redirect_to root_url, alert: "Please confirm your email address to continue."
    end
    RUBY
  end

  generate :mailer, 'UserMailer'

  inject_into_file 'app/mailers/user_mailer.rb', after: "class UserMailer < ApplicationMailer\n" do
    <<-RUBY
  def confirmation_instructions(user)
    @user = user
    @confirmation_url = confirm_registration_url(token: @user.confirmation_token)

    mail(to: @user.email_address, subject: "Confirm your account")
  end
    RUBY
  end

  file 'app/views/user_mailer/confirmation_instructions.html.erb', <<-ERB
<h1>Welcome <%= @user.email_address %>!</h1>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', @confirmation_url %></p>
  ERB

  gsub_file 'app/controllers/registrations_controller.rb', /allow_unauthenticated_access.*\]/ do
  <<-RUBY
allow_unauthenticated_access only: [ :new, :create, :confirm ]
  RUBY
  end

  gsub_file 'app/controllers/registrations_controller.rb', /def create.*?if @user\.save.*?end/m do |match|
    updated_create = match.gsub(
      "if @user.save\n      start_new_session_for(@user)\n      redirect_to root_url, notice: \"Registered successfully\"",
      "if @user.save\n      @user.send_confirmation_instructions\n      redirect_to root_url, notice: \"Please check your email to confirm your account.\""
    )
    updated_create
  end

  route "get 'confirm_registration', to: 'registrations#confirm'"

  inject_into_file 'app/controllers/registrations_controller.rb', before: /\s*private/ do
  <<-RUBY


  def confirm
    @user = User.find_by(confirmation_token: params[:token])

    if @user
      @user.confirm!
      redirect_to root_path, notice: "Your account has been confirmed. Welcome!"
    else
      redirect_to root_path, alert: "Invalid confirmation token."
    end
  end
    RUBY
  end
end


create_registrations_controller
setup_registration_view
add_routes
add_user_validations
if yes?("\nDo you need an email confirmation feature for registration? (y/n)")
  setup_email_confirmation
end
say "\nThe user registration feature is generated successfully", :green
Comments
Marco Beffa
undefined method `send_confirmation_instructions' for an instance of User
Extracted source (around line #12):
10
11
12
13
14
15
               |     @user = User.new(user_params)
    if @user.save
      @user.send_confirmation_instructions
      redirect_to root_url, notice: "Please check your email to confirm your account."
    else
      render :new
Marco Beffa
Sorry I read now your article: 
What’s next 🔗
  • This does not expire the confirmation token. You might want it to expire after a period of like 24 hours.
  • If you want to make some part of the codebase accessible without email confirmation. You can add an exception in Authentication concern.
  • You may want to store the unconfirmed emails in a separate field and copy it to email_address field after confirmation. But this will prevent login.