|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +module AnnotateRb |
| 4 | + module ModelAnnotator |
| 5 | + module ColumnAnnotation |
| 6 | + class AnnotationBuilder |
| 7 | + BARE_TYPE_ALLOWANCE = 16 |
| 8 | + MD_TYPE_ALLOWANCE = 18 |
| 9 | + |
| 10 | + def initialize(column, model, max_size, options) |
| 11 | + @column = column |
| 12 | + @model = model |
| 13 | + @max_size = max_size |
| 14 | + @options = options |
| 15 | + end |
| 16 | + |
| 17 | + def build |
| 18 | + result = '' |
| 19 | + |
| 20 | + is_primary_key = is_column_primary_key?(@model, @column.name) |
| 21 | + |
| 22 | + table_indices = @model.retrieve_indexes_from_table |
| 23 | + column_indices = table_indices.select { |ind| ind.columns.include?(@column.name) } |
| 24 | + |
| 25 | + column_attributes = AttributesBuilder.new(@column, @options, is_primary_key, column_indices).build |
| 26 | + formatted_column_type = TypeBuilder.new(@column, @options).build |
| 27 | + |
| 28 | + col_name = if @model.with_comments? && @column.comment |
| 29 | + "#{@column.name}(#{@column.comment.gsub(/\n/, '\\n')})" |
| 30 | + else |
| 31 | + @column.name |
| 32 | + end |
| 33 | + |
| 34 | + if @options[:format_rdoc] |
| 35 | + result += format("# %-#{@max_size}.#{@max_size}s<tt>%s</tt>", |
| 36 | + "*#{col_name}*::", |
| 37 | + column_attributes.unshift(formatted_column_type).join(', ')).rstrip + "\n" |
| 38 | + elsif @options[:format_yard] |
| 39 | + result += sprintf("# @!attribute #{col_name}") + "\n" |
| 40 | + |
| 41 | + if @column.respond_to?(:array) && @column.array |
| 42 | + ruby_class = "Array<#{map_col_type_to_ruby_classes(formatted_column_type)}>" |
| 43 | + else |
| 44 | + ruby_class = map_col_type_to_ruby_classes(formatted_column_type) |
| 45 | + end |
| 46 | + |
| 47 | + result += sprintf("# @return [#{ruby_class}]") + "\n" |
| 48 | + elsif @options[:format_markdown] |
| 49 | + name_remainder = @max_size - col_name.length - non_ascii_length(col_name) |
| 50 | + type_remainder = (MD_TYPE_ALLOWANCE - 2) - formatted_column_type.length |
| 51 | + result += format("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", |
| 52 | + col_name, |
| 53 | + ' ', |
| 54 | + formatted_column_type, |
| 55 | + ' ', |
| 56 | + column_attributes.join(', ').rstrip).gsub('``', ' ').rstrip + "\n" |
| 57 | + else |
| 58 | + result += format_default(col_name, @max_size, formatted_column_type, column_attributes) |
| 59 | + end |
| 60 | + |
| 61 | + result |
| 62 | + end |
| 63 | + |
| 64 | + private |
| 65 | + |
| 66 | + def non_ascii_length(string) |
| 67 | + string.to_s.chars.reject(&:ascii_only?).length |
| 68 | + end |
| 69 | + |
| 70 | + def mb_chars_ljust(string, length) |
| 71 | + string = string.to_s |
| 72 | + padding = length - Helper.width(string) |
| 73 | + if padding.positive? |
| 74 | + string + (' ' * padding) |
| 75 | + else |
| 76 | + string[0..(length - 1)] |
| 77 | + end |
| 78 | + end |
| 79 | + |
| 80 | + def map_col_type_to_ruby_classes(col_type) |
| 81 | + case col_type |
| 82 | + when 'integer' then Integer.to_s |
| 83 | + when 'float' then Float.to_s |
| 84 | + when 'decimal' then BigDecimal.to_s |
| 85 | + when 'datetime', 'timestamp', 'time' then Time.to_s |
| 86 | + when 'date' then Date.to_s |
| 87 | + when 'text', 'string', 'binary', 'inet', 'uuid' then String.to_s |
| 88 | + when 'json', 'jsonb' then Hash.to_s |
| 89 | + when 'boolean' then 'Boolean' |
| 90 | + end |
| 91 | + end |
| 92 | + |
| 93 | + def format_default(col_name, max_size, col_type, attrs) |
| 94 | + format('# %s:%s %s', |
| 95 | + mb_chars_ljust(col_name, max_size), |
| 96 | + mb_chars_ljust(col_type, BARE_TYPE_ALLOWANCE), |
| 97 | + attrs.join(', ')).rstrip + "\n" |
| 98 | + end |
| 99 | + |
| 100 | + # TODO: Simplify this conditional |
| 101 | + def is_column_primary_key?(model, column_name) |
| 102 | + if model.primary_key |
| 103 | + if model.primary_key.is_a?(Array) |
| 104 | + # If the model has multiple primary keys, check if this column is one of them |
| 105 | + if model.primary_key.collect(&:to_sym).include?(column_name.to_sym) |
| 106 | + return true |
| 107 | + end |
| 108 | + else |
| 109 | + # If model has 1 primary key, check if this column is it |
| 110 | + if column_name.to_sym == model.primary_key.to_sym |
| 111 | + return true |
| 112 | + end |
| 113 | + end |
| 114 | + end |
| 115 | + |
| 116 | + false |
| 117 | + end |
| 118 | + end |
| 119 | + end |
| 120 | + end |
| 121 | +end |
0 commit comments