Refactor User#temp_username (#19252)

* Refactor User#temp_username

* Remove Authentication::Providers dependency from Users::UsernameGenerator

* Adds brief description and yard docs
This commit is contained in:
Goran 2023-03-27 15:21:00 +02:00 committed by GitHub
parent 15bb2e5a8b
commit 0b4a3601a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 24 deletions

View file

@ -569,33 +569,19 @@ class User < ApplicationRecord
end
def set_username
set_temp_username if username.blank?
self.username = username&.downcase
self.username = username&.downcase&.presence || generate_username
end
# @todo Should we do something to ensure that we don't create a username that violates our
# USERNAME_MAX_LENGTH constant?
#
# @see USERNAME_MAX_LENGTH
def set_temp_username
self.username = if temp_name_exists?
"#{temp_username}_#{rand(100)}"
else
temp_username
end
def auth_provider_usernames
attributes
.with_indifferent_access
.slice(*Authentication::Providers.username_fields)
.values.compact || []
end
def temp_name_exists?
User.exists?(username: temp_username) || Organization.exists?(slug: temp_username)
end
def temp_username
Authentication::Providers.username_fields.each do |username_field|
value = public_send(username_field)
next if value.blank?
return value.downcase.gsub(/[^0-9a-z_]/i, "").delete(" ")
end
def generate_username
Users::UsernameGenerator
.call(auth_provider_usernames)
end
def downcase_email

View file

@ -0,0 +1,52 @@
# Generates available username based on
# multiple generators in the following order:
# * list of supplied usernames
# * list of supplied usernames with suffix
# * random generated letters
#
# @todo Extract username validation in separate class
module Users
class UsernameGenerator
def self.call(...)
new(...).call
end
# @param list [Array<String>] a list of usernames
def initialize(list = [])
@list = list
end
def call
from_list(modified_list) || from_list(list_with_suffix) || from_list(Array.new(3) { random_letters })
end
private
def from_list(list)
list.detect { |username| !username_exists?(username) }
end
def username_exists?(username)
User.exists?(username: username) ||
Organization.exists?(slug: username) ||
Page.exists?(slug: username) ||
Podcast.exists?(slug: username)
end
def filtered_list
@list.select { |s| s.is_a?(String) && s.present? }
end
def modified_list
filtered_list.map { |s| s.downcase.gsub(/[^0-9a-z_]/i, "").delete(" ") }
end
def list_with_suffix
modified_list.map { |s| [s, rand(100)].join("_") }
end
def random_letters
("a".."z").to_a.sample(12).join
end
end
end

View file

@ -313,12 +313,18 @@ RSpec.describe User do
end
describe "#username" do
it "receives a temporary username if none is given" do
it "receives a generated username if none is given" do
user.username = ""
user.validate!
expect(user.username).not_to be_blank
end
it "is not valid if generate_username returns nil" do
user.username = ""
allow(user).to receive(:generate_username).and_return(nil)
expect(user).not_to be_valid
end
it "does not allow to change to a username that is taken" do
user.username = other_user.username
expect(user).not_to be_valid

View file

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Users::UsernameGenerator, type: :service do
let(:user) { create(:user) }
it "returns randomly generated username if empty list is passed" do
expect(described_class.call([""])).to be_present
end
it "returns supplied username if does not exist" do
expect(described_class.call(["foo"])).to eq("foo")
end
it "returns modified username" do
expect(described_class.call(["foo.bar"])).to eq("foobar")
end
it "returns supplied username with suffix if exists" do
expect(described_class.call([user.username])).to start_with("#{user.username}_")
end
it "returns randomly generated username" do
expect(described_class.call).to be_present
end
it "returns nil if all generation methods are exhausted" do
username_generator = described_class.new
allow(username_generator).to receive(:random_letters).and_return(user.username)
expect(username_generator.call).to be_nil
end
end