docbrown/app/services/users/username_generator.rb
Joshua Wehner a20cf3e02b
Unique cross-model-slug check is likely misconfigured (#19263)
* Page unique_cross_model_slug seems misconfigured

* Update validator test

* More verbose invalid attribute message

* Try dynamic model/attribute registration

* Rubocop

* Use the cross-model-slug existence check

* Register with the dynamic cross-model-slug checker

* Cleanup

* Add missing keys to fr

* Update format regex

* Adjust validation: always presence, unique if changed

* Tests can create reserved-word pages when they need to

* Slugs can be mixed case?

* Case-sensitive is better, actually

* Refactor to use CrossModel check

* Refactor, rename for clarity

* Refactor, avoid mocking oneself

* Refactor, injectable everything

* Add reservedword check to extracted exists? checker

* Move to concerns

* Without dynamic registration

* extend when needed

* Cleanup comments, remove registration references

---------

Co-authored-by: Goran <gorang.pub@gmail.com>
2023-04-07 17:04:45 +02:00

61 lines
1.5 KiB
Ruby

# 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
attr_reader :usernames
def self.call(...)
new(...).call
end
# @param usernames [Array<String>] a list of usernames
def initialize(usernames = [], detector: CrossModelSlug, generator: nil)
@detector = detector
@generator = generator || method(:random_username)
@usernames = usernames
end
def call
first_available_from(normalized_usernames) ||
first_available_from(suffixed_usernames) ||
first_available_from(random_usernames)
end
def normalized_usernames
@normalized_usernames ||= filtered_usernames.map { |s| s.downcase.gsub(/[^0-9a-z_]/i, "").delete(" ") }
end
def filtered_usernames
@filtered_usernames ||= usernames.select { |s| s.is_a?(String) && s.present? }
end
def random_username
("a".."z").to_a.sample(12).join
end
def random_usernames
Array.new(3) { @generator.call }
end
private
def first_available_from(list)
list.detect { |username| !username_exists?(username) }
end
def username_exists?(username)
@detector.exists?(username)
end
def suffixed_usernames
return [] unless filtered_usernames.any?
normalized_usernames.map { |stem| [stem, rand(100)].join("_") }
end
end
end