Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
annotato (0.2.1)
annotato (0.2.2)
rails (>= 6.0)

GEM
Expand Down
16 changes: 12 additions & 4 deletions lib/annotato/column_formatter.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# frozen_string_literal: true

require "json"
require_relative "wrap_helper"

module Annotato
class ColumnFormatter
extend WrapHelper

# Main entry: returns an array of comment lines per column, flattened.
def self.format(model, connection)
table_name = model.table_name
Expand Down Expand Up @@ -57,10 +60,15 @@ def self.format(model, connection)
lines << closing
lines
else
# single-line comment
line = left
line += " #{opts.join(', ')}" unless opts.empty?
[line.rstrip]
# single-line comment; wrap options onto continuation line if too long
opts_str = opts.join(", ")
line = opts_str.empty? ? left : "#{left} #{opts_str}"
if line.length > WrapHelper::MAX_LINE && !opts_str.empty?
cont_indent = " " * (left.length + 1)
[left, "# #{cont_indent}#{opts_str}"]
else
[line.rstrip]
end
end
end
end
Expand Down
27 changes: 22 additions & 5 deletions lib/annotato/trigger_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,28 @@
module Annotato
class TriggerFormatter
def self.format(conn, table_name)
conn.exec_query(
"SELECT tgname FROM pg_trigger WHERE tgrelid = $1::regclass AND NOT tgisinternal",
"SQL",
[table_name]
).map { |r| "# #{r['tgname']}" }
rows = case conn.adapter_name.downcase
when "postgresql"
conn.exec_query(
"SELECT tgname AS name FROM pg_trigger WHERE tgrelid = $1::regclass AND NOT tgisinternal",
"SQL", [table_name]
)
when "mysql2", "mysql", "trilogy"
conn.exec_query(
"SELECT TRIGGER_NAME AS name FROM information_schema.TRIGGERS " \
"WHERE EVENT_OBJECT_SCHEMA = DATABASE() AND EVENT_OBJECT_TABLE = ?",
"SQL", [table_name]
)
when "sqlite3"
conn.exec_query(
"SELECT name FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?",
"SQL", [table_name]
)
else
return []
end

rows.map { |r| "# #{r['name']}" }
end
end
end
2 changes: 1 addition & 1 deletion lib/annotato/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Annotato
VERSION = "0.2.1"
VERSION = "0.2.2"
end
2 changes: 1 addition & 1 deletion lib/annotato/wrap_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Annotato
module WrapHelper
MAX_LINE = 100
MAX_LINE = 120

# Produces one or more lines, each already prefixed with "# "
# First line: "# where (..."
Expand Down
1 change: 1 addition & 0 deletions spec/annotato/annotation_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class << self

before do
allow(model).to receive(:table_name).and_return(table_name)
allow(connection).to receive(:adapter_name).and_return("PostgreSQL")
ActiveRecord::Base.connection = connection
end

Expand Down
2 changes: 1 addition & 1 deletion spec/annotato/check_constraint_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
result = described_class.format(connection, "users")

expect(result).to include("# chk_positive_age\n# (age > 0)")
expect(result).to include("# chk_valid_status\n# (((((status)::text = 'deleted'::text) AND\n# (deleted_at IS NOT NULL)) OR (((status)::text <> 'deleted'::text) AND\n# (deleted_at IS NULL) AND (active = true))))")
expect(result).to include("# chk_valid_status\n# (((((status)::text = 'deleted'::text) AND (deleted_at IS NOT NULL)) OR (((status)::text <> 'deleted'::text) AND\n# (deleted_at IS NULL) AND (active = true))))")
end
end
22 changes: 21 additions & 1 deletion spec/annotato/column_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,26 @@

# Closing line should be correctly indented
closing_line = multiline_lines.find { |line| line.include?("]),") }
expect(closing_line).to match(/^#{"#" + ' ' * (indent_start-1)}\]\),/)
expect(closing_line).to match(/^#{"#" + ' ' * (indent_start - 1)}\]\),/)
end

it "wraps options onto a continuation line when the column line exceeds MAX_LINE" do
# left = "# " (3) + name (79) + " :" (2) + type (22) = 106 chars
# 106 + " not null, unique" (17) = 123 > MAX_LINE (120) → must wrap
long_name = "a_very_long_column_name_that_will_definitely_push_the_line_over_the_rubocop_limit"
long_type = "character varying(255)"
col = double("Column", name: long_name, sql_type: long_type, default: nil, null: false)
allow(col).to receive(:respond_to?).with(:comment).and_return(false)
allow(connection).to receive(:columns).with("users").and_return([col])
allow(connection).to receive(:indexes).with("users").and_return([
double(name: "idx", columns: [long_name], unique: true)
])

result = described_class.format(model, connection)

expect(result.size).to eq(2)
expect(result[0]).not_to include("not null")
expect(result[0].length).to be <= Annotato::WrapHelper::MAX_LINE
expect(result[1]).to include("not null, unique")
end
end
66 changes: 46 additions & 20 deletions spec/annotato/trigger_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,54 @@
RSpec.describe Annotato::TriggerFormatter do
let(:connection) { double("Connection") }

it "formats triggers correctly" do
result_set = [
{ "tgname" => "audit_trigger_row" },
{ "tgname" => "update_timestamp" }
]
allow(connection).to receive(:exec_query)
.with(/pg_trigger/, "SQL", ["users"])
.and_return(result_set)

result = described_class.format(connection, "users")
expect(result).to include("# audit_trigger_row")
expect(result).to include("# update_timestamp")
shared_examples "formats trigger names" do |adapter, sql_pattern, bind_params|
before { allow(connection).to receive(:adapter_name).and_return(adapter) }

it "queries with correct SQL and formats results" do
allow(connection).to receive(:exec_query)
.with(sql_pattern, "SQL", bind_params)
.and_return([{ "name" => "audit_trigger" }, { "name" => "update_timestamp" }])

result = described_class.format(connection, "users")
expect(result).to eq(["# audit_trigger", "# update_timestamp"])
end

it "uses a parameterized query (no string interpolation)" do
expect(connection).to receive(:exec_query)
.with(sql_pattern, "SQL", bind_params)
.and_return([])

described_class.format(connection, "users")
end
end

context "PostgreSQL adapter" do
include_examples "formats trigger names",
"PostgreSQL",
"SELECT tgname AS name FROM pg_trigger WHERE tgrelid = $1::regclass AND NOT tgisinternal",
["users"]
end

context "MySQL2 adapter" do
include_examples "formats trigger names",
"Mysql2",
/EVENT_OBJECT_TABLE/,
["users"]
end

context "SQLite3 adapter" do
include_examples "formats trigger names",
"SQLite3",
"SELECT name FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?",
["users"]
end

it "uses a parameterized query (no string interpolation)" do
expect(connection).to receive(:exec_query)
.with(
"SELECT tgname FROM pg_trigger WHERE tgrelid = $1::regclass AND NOT tgisinternal",
"SQL",
["orders"]
).and_return([])
context "unsupported adapter" do
before { allow(connection).to receive(:adapter_name).and_return("OracleEnhanced") }

described_class.format(connection, "orders")
it "returns empty array without querying" do
expect(connection).not_to receive(:exec_query)
expect(described_class.format(connection, "users")).to eq([])
end
end
end
Loading