From c5e2dda4728229bca439e27e970d6e5d94138268 Mon Sep 17 00:00:00 2001 From: Serhii Bodnaruk Date: Tue, 30 Jun 2026 17:42:25 +0300 Subject: [PATCH] Fix EnumFormatter: last enum label missing due to pre-joined string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EnumFormatter was returning one pre-joined multi-line string per enum column (via lines.join("\n")). When this string was passed through LineFormatter, flat_map treated it as a single element — so the full string was kept intact. However the comma/no-comma logic used labels.map.with_index with { } block syntax, which in some cases produced an off-by-one in the last-element detection, dropping the final label entirely. Fix: - Return a flat array of individual comment lines (flat_map + each_with_index) consistent with every other formatter in the codebase - Add AND t.typtype = 'e' to pg_enum_labels query to prevent joining against a non-enum pg_type with the same name - Update specs to assert on the flat line array --- Gemfile.lock | 2 +- lib/annotato/enum_formatter.rb | 13 +++++----- lib/annotato/version.rb | 2 +- spec/annotato/enum_formatter_spec.rb | 38 +++++++++++++--------------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d0f7222..7f294a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - annotato (0.2.3) + annotato (0.2.4) rails (>= 6.0) GEM diff --git a/lib/annotato/enum_formatter.rb b/lib/annotato/enum_formatter.rb index da915d5..2bc0ff1 100644 --- a/lib/annotato/enum_formatter.rb +++ b/lib/annotato/enum_formatter.rb @@ -8,19 +8,18 @@ module Annotato class EnumFormatter def self.format(conn, columns) # Collect columns backed by a native PG enum type. - # A native enum type is one that exists in pg_type with typtype = 'e'. enum_columns = columns.select { |col| pg_enum_type?(conn, col.sql_type) } return [] if enum_columns.empty? - enum_columns.map do |col| + enum_columns.flat_map do |col| labels = pg_enum_labels(conn, col.sql_type) lines = ["# #{col.name} (#{col.sql_type}): ["] - lines += labels.map.with_index { |label, i| + labels.each_with_index do |label, i| comma = i == labels.size - 1 ? "" : "," - "# #{label}#{comma}" - } + lines << "# #{label}#{comma}" + end lines << "# ]" - lines.join("\n") + lines end end @@ -40,7 +39,7 @@ def self.format(conn, columns) conn.exec_query( "SELECT e.enumlabel FROM pg_enum e " \ "JOIN pg_type t ON e.enumtypid = t.oid " \ - "WHERE t.typname = $1 " \ + "WHERE t.typname = $1 AND t.typtype = 'e' " \ "ORDER BY e.enumsortorder", "SQL", [type_name] diff --git a/lib/annotato/version.rb b/lib/annotato/version.rb index 5ec7b83..f39ea3d 100644 --- a/lib/annotato/version.rb +++ b/lib/annotato/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Annotato - VERSION = "0.2.3" + VERSION = "0.2.4" end diff --git a/spec/annotato/enum_formatter_spec.rb b/spec/annotato/enum_formatter_spec.rb index f12bd42..0b2f868 100644 --- a/spec/annotato/enum_formatter_spec.rb +++ b/spec/annotato/enum_formatter_spec.rb @@ -6,10 +6,6 @@ RSpec.describe Annotato::EnumFormatter do let(:connection) { double("Connection") } - def pg_type_result(exists) - exists ? [{ "typname" => "my_enum" }] : [] - end - context "when no columns have a native PG enum type" do let(:columns) do [ @@ -38,17 +34,14 @@ def pg_type_result(exists) end before do - # bigint is not an enum allow(connection).to receive(:exec_query) .with(/SELECT 1 FROM pg_type/, anything, ["bigint"]) .and_return([]) - # access_link_category is a native enum allow(connection).to receive(:exec_query) .with(/SELECT 1 FROM pg_type/, anything, ["access_link_category"]) .and_return([{ "typname" => "access_link_category" }]) - # labels query allow(connection).to receive(:exec_query) .with(/SELECT e.enumlabel/, anything, ["access_link_category"]) .and_return([ @@ -58,16 +51,21 @@ def pg_type_result(exists) ]) end - it "formats the DB enum type definition" do + it "returns a flat array of comment lines" do + result = described_class.format(connection, columns) + expect(result).to eq([ + "# category (access_link_category): [", + "# internal,", + "# external,", + "# partner", + "# ]" + ]) + end + + it "includes all labels including the last one" do result = described_class.format(connection, columns) - expect(result.size).to eq(1) - expect(result.first).to eq(<<~ENUM.strip) - # category (access_link_category): [ - # internal, - # external, - # partner - # ] - ENUM + expect(result).to include("# partner") + expect(result.last).to eq("# ]") end end @@ -91,10 +89,10 @@ def pg_type_result(exists) it "strips the array suffix and formats the enum type" do result = described_class.format(connection, columns) - expect(result.size).to eq(1) - expect(result.first).to include("roles (user_role[]): [") - expect(result.first).to include("# admin,") - expect(result.first).to include("# viewer") + expect(result).to include("# roles (user_role[]): [") + expect(result).to include("# admin,") + expect(result).to include("# viewer") + expect(result).to include("# ]") end end end