Class: Annotato::ColumnFormatter

Inherits:
Object
  • Object
show all
Defined in:
lib/annotato/column_formatter.rb

Class Method Summary collapse

Class Method Details

.build_default_block(value) ⇒ Object

Returns nil for non-JSON/empty values, or an Array of un-indented inner lines for multiline formatting: e.g. [""A",", ""B""] for a JSON array.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/annotato/column_formatter.rb', line 70

def self.build_default_block(value)
  return nil if value.nil? || !value.is_a?(String)
  s = value.strip
  return nil unless s.start_with?("[") || s.start_with?("{")

  parsed = JSON.parse(s) rescue nil
  return nil unless parsed.is_a?(Array) || parsed.is_a?(Hash)

  if parsed.is_a?(Array)
    return nil if parsed.empty?

    # JSON array of strings
    parsed.map.with_index do |e, i|
      comma = i == parsed.size - 1 ? "" : ","
      %Q{"#{e}"#{comma}}
    end
  else
    return nil if parsed.empty?

    # JSON hash → key/value pairs
    parsed.map.with_index do |(k, v), i|
      comma = i == parsed.size - 1 ? "" : ","
      %Q{"#{k}": #{v.inspect}#{comma}}
    end
  end
end

.format(model, connection) ⇒ Object

Main entry: returns an array of comment lines per column, flattened.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/annotato/column_formatter.rb', line 8

def self.format(model, connection)
  table_name     = model.table_name
  primary_key    = model.primary_key
  unique_indexes = connection.indexes(table_name).select(&:unique)
  columns        = connection.columns(table_name)

  # Collect native PG enum type names once up front (avoids N+1 queries).
  pg_enum_types = pg_enum_type_names(connection)

  # Compute max widths for name and sql_type so that the table lines up.
  name_width = columns.map(&:name).map(&:length).max
  type_width = columns.map(&:sql_type).map(&:length).max

  columns.flat_map do |col|
    name = col.name
    type = col.sql_type

    # Build the left-hand side and calculate indent.
    left  = "#  %-#{name_width}s :%-#{type_width}s" % [name, type]
    # " default(" is 9 chars; +2 gives the extra gap.
    indent_size = left.length + 1
    indent_str  = " " * indent_size
    closing_indent_str  = " " * (indent_size - 2)

    # Gather all options, pulling out default_lines if multiline.
    opts         = []
    default_block = build_default_block(col.default)
    if default_block
      opts << "__MULTILINE__"  # placeholder
    elsif !col.default.nil?
      opts << "default(#{col.default.inspect})"
    end
    opts << "not null"       unless col.null
    opts << "primary key"    if name == primary_key
    opts << "is an Array"    if type.end_with?("[]")
    opts << "unique"         if unique_indexes.any? { |idx| idx.columns == [name] }
    opts << "enum"           if pg_enum_types.include?(type.delete_suffix("[]"))
    opts << "comment: #{col.comment.inspect}" if col.respond_to?(:comment) && col.comment && !col.comment.empty?

    # Emit either a multiline block or a single line.
    if default_block
      # 1) opening line
      lines = ["#{left} default(#{col.default[0]}"]
      # 2) each interior line, prefixed by "# " + indent_str
      lines += default_block.map { |l| "# #{indent_str}#{l}" }
      # 3) closing line with trailing options
      closing = "# #{closing_indent_str}#{col.default[-1]})"
      trailing = opts.reject { |o| o == "__MULTILINE__" }
      closing += ", #{trailing.join(', ')}" unless trailing.empty?
      lines << closing
      lines
    else
      # single-line comment
      line = left
      line += " #{opts.join(', ')}" unless opts.empty?
      [line.rstrip]
    end
  end
end