@@ -34,42 +34,71 @@ class PreviewQuery
3434 Result = Data . define ( :projects_data , :total_count )
3535 DISPLAY_COUNT = 5
3636
37+ # Priority-ordered format rules for identifier classification.
38+ FORMAT_RULES = [
39+ [ :too_long , -> ( id , max ) { id . length > max } ] ,
40+ [ :numerical , -> ( id , _ ) { id . match? ( /\A \d +\z / ) } ] ,
41+ [ :starts_with_number , -> ( id , _ ) { id . match? ( /\A \d / ) } ] ,
42+ [ :special_characters , -> ( id , _ ) { id . match? ( /[^a-zA-Z0-9]/ ) } ] ,
43+ [ :not_uppercase , -> ( id , _ ) { id != id . upcase } ]
44+ ] . freeze
45+
3746 def call
38- total = problematic_scope . count
39- preview = problematic_scope
40- . select ( :id , :name , :identifier )
41- . limit ( DISPLAY_COUNT )
42- . to_a
43-
44- suggestions = WorkPackages ::IdentifierAutofix ::ProjectIdentifierSuggestionGenerator . call (
45- preview ,
46- in_use_identifiers :,
47- reserved_identifiers :
48- )
47+ Result . new ( projects_data : build_projects_data , total_count : problematic_scope . count )
48+ end
4949
50- projects_data = suggestions . map do |entry |
50+ private
51+
52+ def build_projects_data
53+ generate_suggestions . map do |entry |
5154 entry . merge ( error_reason : error_reason ( entry [ :current_identifier ] ) )
5255 end
56+ end
5357
54- Result . new ( projects_data :, total_count : total )
58+ def generate_suggestions
59+ ProjectIdentifierSuggestionGenerator . call (
60+ preview_projects ,
61+ in_use_identifiers :,
62+ reserved_identifiers :
63+ )
5564 end
5665
57- private
66+ def preview_projects
67+ problematic_scope
68+ . select ( :id , :name , :identifier )
69+ . limit ( DISPLAY_COUNT )
70+ . to_a
71+ end
5872
73+ # Scope conditions must cover all identifiers classifiable by #error_reason.
5974 def problematic_scope
60- @problematic_scope ||= Project . where (
61- "length(identifier) > ? OR identifier ~ ?" ,
62- ProjectIdentifierSuggestionGenerator ::IDENTIFIER_LENGTH [ :max ] ,
63- "[^a-zA-Z0-9_]"
64- )
75+ @problematic_scope ||= exceeds_max_length
76+ . or ( contains_non_alphanumeric )
77+ . or ( starts_with_digit )
78+ . or ( not_fully_uppercased )
6579 end
6680
81+ def exceeds_max_length = Project . where ( "length(identifier) > ?" , max_identifier_length )
82+ def contains_non_alphanumeric = Project . where ( "identifier ~ ?" , "[^a-zA-Z0-9]" )
83+ def starts_with_digit = Project . where ( "identifier ~ ?" , "^[0-9]" )
84+ def not_fully_uppercased = Project . where ( "identifier != UPPER(identifier)" )
85+
86+ def max_identifier_length = ProjectIdentifierSuggestionGenerator ::IDENTIFIER_LENGTH [ :max ]
87+
88+ # Must handle all identifiers matched by #problematic_scope.
6789 def error_reason ( identifier )
68- if identifier . length > ProjectIdentifierSuggestionGenerator ::IDENTIFIER_LENGTH [ :max ]
69- :too_long
70- elsif identifier . match? ( /[^a-zA-Z0-9_]/ )
71- :special_characters
72- elsif in_use_identifiers . include? ( identifier )
90+ format_error_reason ( identifier ) || collision_error_reason ( identifier ) || :unknown
91+ end
92+
93+ def format_error_reason ( identifier )
94+ FORMAT_RULES . each do |reason , check |
95+ return reason if check . call ( identifier , max_identifier_length )
96+ end
97+ nil
98+ end
99+
100+ def collision_error_reason ( identifier )
101+ if in_use_identifiers . include? ( identifier )
73102 :in_use
74103 elsif reserved_identifiers . include? ( identifier )
75104 :reserved
0 commit comments