DEV: Improve User#email= behavior (#11338)
- Only apply the change after `save` is called on the record - Automatically remove matching secondary emails
This commit is contained in:
parent
74d83abcc7
commit
ef19431e44
|
@ -23,7 +23,7 @@ class User < ActiveRecord::Base
|
||||||
has_many :email_tokens, dependent: :destroy
|
has_many :email_tokens, dependent: :destroy
|
||||||
has_many :topic_links, dependent: :destroy
|
has_many :topic_links, dependent: :destroy
|
||||||
has_many :user_uploads, dependent: :destroy
|
has_many :user_uploads, dependent: :destroy
|
||||||
has_many :user_emails, dependent: :destroy
|
has_many :user_emails, dependent: :destroy, autosave: true
|
||||||
has_many :user_associated_accounts, dependent: :destroy
|
has_many :user_associated_accounts, dependent: :destroy
|
||||||
has_many :oauth2_user_infos, dependent: :destroy
|
has_many :oauth2_user_infos, dependent: :destroy
|
||||||
has_many :user_second_factors, dependent: :destroy
|
has_many :user_second_factors, dependent: :destroy
|
||||||
|
@ -40,7 +40,7 @@ class User < ActiveRecord::Base
|
||||||
|
|
||||||
has_one :user_option, dependent: :destroy
|
has_one :user_option, dependent: :destroy
|
||||||
has_one :user_avatar, dependent: :destroy
|
has_one :user_avatar, dependent: :destroy
|
||||||
has_one :primary_email, -> { where(primary: true) }, class_name: 'UserEmail', dependent: :destroy
|
has_one :primary_email, -> { where(primary: true) }, class_name: 'UserEmail', dependent: :destroy, autosave: true
|
||||||
has_one :user_stat, dependent: :destroy
|
has_one :user_stat, dependent: :destroy
|
||||||
has_one :user_profile, dependent: :destroy, inverse_of: :user
|
has_one :user_profile, dependent: :destroy, inverse_of: :user
|
||||||
has_one :single_sign_on_record, dependent: :destroy
|
has_one :single_sign_on_record, dependent: :destroy
|
||||||
|
@ -1263,12 +1263,21 @@ class User < ActiveRecord::Base
|
||||||
primary_email&.email
|
primary_email&.email
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Shortcut to set the primary email of the user.
|
||||||
|
# Automatically removes any identical secondary emails.
|
||||||
def email=(new_email)
|
def email=(new_email)
|
||||||
if primary_email
|
if primary_email
|
||||||
new_record? ? primary_email.email = new_email : primary_email.update(email: new_email)
|
primary_email.email = new_email
|
||||||
else
|
else
|
||||||
self.primary_email = UserEmail.new(email: new_email, user: self, primary: true, skip_validate_email: !should_validate_email_address?)
|
build_primary_email email: new_email, skip_validate_email: !should_validate_email_address?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if secondary_match = user_emails.detect { |ue| !ue.primary && Email.downcase(ue.email) == Email.downcase(new_email) }
|
||||||
|
secondary_match.mark_for_destruction
|
||||||
|
primary_email.skip_validate_unique_email = true
|
||||||
|
end
|
||||||
|
|
||||||
|
new_email
|
||||||
end
|
end
|
||||||
|
|
||||||
def emails
|
def emails
|
||||||
|
|
|
@ -4,6 +4,7 @@ class UserEmail < ActiveRecord::Base
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
attr_accessor :skip_validate_email
|
attr_accessor :skip_validate_email
|
||||||
|
attr_accessor :skip_validate_unique_email
|
||||||
|
|
||||||
before_validation :strip_downcase_email
|
before_validation :strip_downcase_email
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ class UserEmail < ActiveRecord::Base
|
||||||
|
|
||||||
validates :primary, uniqueness: { scope: [:user_id] }, if: [:user_id, :primary]
|
validates :primary, uniqueness: { scope: [:user_id] }, if: [:user_id, :primary]
|
||||||
validate :user_id_not_changed, if: :primary
|
validate :user_id_not_changed, if: :primary
|
||||||
validate :unique_email
|
validate :unique_email, if: :validate_unique_email?
|
||||||
|
|
||||||
scope :secondary, -> { where(primary: false) }
|
scope :secondary, -> { where(primary: false) }
|
||||||
|
|
||||||
|
@ -30,8 +31,13 @@ class UserEmail < ActiveRecord::Base
|
||||||
email_changed?
|
email_changed?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_unique_email?
|
||||||
|
return false if self.skip_validate_unique_email
|
||||||
|
will_save_change_to_email?
|
||||||
|
end
|
||||||
|
|
||||||
def unique_email
|
def unique_email
|
||||||
if self.will_save_change_to_email? && self.class.where("lower(email) = ?", email).exists?
|
if self.class.where("lower(email) = ?", email).exists?
|
||||||
self.errors.add(:email, :taken)
|
self.errors.add(:email, :taken)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2069,6 +2069,35 @@ describe User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#email=" do
|
||||||
|
let(:new_email) { "newprimary@example.com" }
|
||||||
|
it 'sets the primary email' do
|
||||||
|
user.update!(email: new_email)
|
||||||
|
expect(User.find(user.id).email).to eq(new_email)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'only saves when save called' do
|
||||||
|
old_email = user.email
|
||||||
|
user.email = new_email
|
||||||
|
expect(User.find(user.id).email).to eq(old_email)
|
||||||
|
user.save!
|
||||||
|
expect(User.find(user.id).email).to eq(new_email)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'will automatically remove matching secondary emails' do
|
||||||
|
secondary_email_record = Fabricate(:secondary_email, user: user)
|
||||||
|
user.reload
|
||||||
|
expect(user.secondary_emails.count).to eq(1)
|
||||||
|
user.email = secondary_email_record.email
|
||||||
|
puts "done setting"
|
||||||
|
user.save!
|
||||||
|
|
||||||
|
expect(User.find(user.id).email).to eq(secondary_email_record.email)
|
||||||
|
expect(user.secondary_emails.count).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe "set_random_avatar" do
|
describe "set_random_avatar" do
|
||||||
it "sets a random avatar when selectable avatars is enabled" do
|
it "sets a random avatar when selectable avatars is enabled" do
|
||||||
avatar1 = Fabricate(:upload)
|
avatar1 = Fabricate(:upload)
|
||||||
|
|
Loading…
Reference in New Issue