Basic user registration for Rails 8 authentication generator
Run this after running `rails generate authentication` to add user registration feature
Used 170 times
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
This has two expectations
- The Rails 8 authentication generator has been run just before running this (`
rails generate authentication
`) - 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
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 🔗
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.