Commit 18a96f9b authored by Manoj M J's avatar Manoj M J Committed by James Lopez

Send Devise emails triggered from the 'Email' model asynchronously

This change sends Devise emails triggered from the
'Email' model asynchronously
parent 5f353263
# frozen_string_literal: true
module AsyncDeviseEmail
extend ActiveSupport::Concern
private
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args)
return true unless can?(:receive_notifications)
devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
end
end
...@@ -15,9 +15,14 @@ class Email < ApplicationRecord ...@@ -15,9 +15,14 @@ class Email < ApplicationRecord
after_commit :update_invalid_gpg_signatures, if: -> { previous_changes.key?('confirmed_at') } after_commit :update_invalid_gpg_signatures, if: -> { previous_changes.key?('confirmed_at') }
devise :confirmable devise :confirmable
# This module adds async behaviour to Devise emails
# and should be added after Devise modules are initialized.
include AsyncDeviseEmail
self.reconfirmable = false # currently email can't be changed, no need to reconfirm self.reconfirmable = false # currently email can't be changed, no need to reconfirm
delegate :username, to: :user delegate :username, :can?, to: :user
def email=(value) def email=(value)
write_attribute(:email, value.downcase.strip) write_attribute(:email, value.downcase.strip)
......
...@@ -58,6 +58,10 @@ class User < ApplicationRecord ...@@ -58,6 +58,10 @@ class User < ApplicationRecord
devise :lockable, :recoverable, :rememberable, :trackable, devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable :validatable, :omniauthable, :confirmable, :registerable
# This module adds async behaviour to Devise emails
# and should be added after Devise modules are initialized.
include AsyncDeviseEmail
BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \ BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
"administrator if you think this is an error." "administrator if you think this is an error."
LOGIN_FORBIDDEN = "Your account does not have the required permission to login. Please contact your GitLab " \ LOGIN_FORBIDDEN = "Your account does not have the required permission to login. Please contact your GitLab " \
...@@ -1746,13 +1750,6 @@ class User < ApplicationRecord ...@@ -1746,13 +1750,6 @@ class User < ApplicationRecord
ApplicationSetting.current_without_cache&.usage_stats_set_by_user_id == self.id ApplicationSetting.current_without_cache&.usage_stats_set_by_user_id == self.id
end end
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args)
return true unless can?(:receive_notifications)
devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
end
def ensure_user_rights_and_limits def ensure_user_rights_and_limits
if external? if external?
self.can_create_group = false self.can_create_group = false
......
---
title: Send Devise emails triggered from the 'Email' model asynchronously
merge_request: 32286
author:
type: fixed
...@@ -9,6 +9,12 @@ describe Profiles::EmailsController do ...@@ -9,6 +9,12 @@ describe Profiles::EmailsController do
sign_in(user) sign_in(user)
end end
around do |example|
perform_enqueued_jobs do
example.run
end
end
describe '#create' do describe '#create' do
context 'when email address is valid' do context 'when email address is valid' do
let(:email_params) { { email: "add_email@example.com" } } let(:email_params) { { email: "add_email@example.com" } }
......
...@@ -67,7 +67,7 @@ describe 'Profile > Emails' do ...@@ -67,7 +67,7 @@ describe 'Profile > Emails' do
email = user.emails.create(email: 'my@email.com') email = user.emails.create(email: 'my@email.com')
visit profile_emails_path visit profile_emails_path
expect { click_link("Resend confirmation email") }.to change { ActionMailer::Base.deliveries.size } expect { click_link("Resend confirmation email") }.to have_enqueued_job.on_queue('mailers')
expect(page).to have_content("Confirmation email sent to #{email.email}") expect(page).to have_content("Confirmation email sent to #{email.email}")
end end
......
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
require 'spec_helper' require 'spec_helper'
describe Email do describe Email do
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(AsyncDeviseEmail) }
end
describe 'validations' do describe 'validations' do
it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :email do it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :email do
subject { build(:email) } subject { build(:email) }
...@@ -45,4 +51,16 @@ describe Email do ...@@ -45,4 +51,16 @@ describe Email do
expect(build(:email, user: user).username).to eq user.username expect(build(:email, user: user).username).to eq user.username
end end
end end
describe 'Devise emails' do
let!(:user) { create(:user) }
describe 'behaviour' do
it 'sends emails asynchronously' do
expect do
user.emails.create!(email: 'hello@hello.com')
end.to have_enqueued_job.on_queue('mailers')
end
end
end
end end
...@@ -17,6 +17,7 @@ describe User do ...@@ -17,6 +17,7 @@ describe User do
it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(TokenAuthenticatable) } it { is_expected.to include_module(TokenAuthenticatable) }
it { is_expected.to include_module(BlocksJsonSerialization) } it { is_expected.to include_module(BlocksJsonSerialization) }
it { is_expected.to include_module(AsyncDeviseEmail) }
end end
describe 'delegations' do describe 'delegations' do
...@@ -165,6 +166,18 @@ describe User do ...@@ -165,6 +166,18 @@ describe User do
end end
end end
describe 'Devise emails' do
let!(:user) { create(:user) }
describe 'behaviour' do
it 'sends emails asynchronously' do
expect do
user.update!(email: 'hello@hello.com')
end.to have_enqueued_job.on_queue('mailers').exactly(:twice)
end
end
end
describe 'validations' do describe 'validations' do
describe 'password' do describe 'password' do
let!(:user) { create(:user) } let!(:user) { create(:user) }
......
...@@ -8,10 +8,10 @@ describe Emails::ConfirmService do ...@@ -8,10 +8,10 @@ describe Emails::ConfirmService do
subject(:service) { described_class.new(user) } subject(:service) { described_class.new(user) }
describe '#execute' do describe '#execute' do
it 'sends a confirmation email again' do it 'enqueues a background job to send confirmation email again' do
email = user.emails.create(email: 'new@email.com') email = user.emails.create(email: 'new@email.com')
mail = service.execute(email)
expect(mail.subject).to eq('Confirmation instructions') expect { service.execute(email) }.to have_enqueued_job.on_queue('mailers')
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment