Skip to content

Commit e60a666

Browse files
authored
Place column comments at the end of the line (#988)
1 parent 3a78787 commit e60a666

File tree

7 files changed

+198
-7
lines changed

7 files changed

+198
-7
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ you can do so with a simple environment variable, instead of editing the
250250
don't show default for given column types, separated by commas (e.g. `json,jsonb,hstore`)
251251
--ignore-unknown-models don't display warnings for bad model files
252252
--with-comment include database comments in model annotations
253+
--with-comment-column include database comments in model annotations, as its own column, after all others
253254

254255
### Option: `additional_file_patterns`
255256

lib/annotate/annotate_models.rb

+40-6
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,37 @@ def get_schema_info(klass, header, options = {}) # rubocop:disable Metrics/Metho
146146

147147
if options[:format_markdown]
148148
info << sprintf( "# %-#{max_size + md_names_overhead}.#{max_size + md_names_overhead}s | %-#{md_type_allowance}.#{md_type_allowance}s | %s\n", 'Name', 'Type', 'Attributes' )
149+
149150
info << "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
150151
end
151152

152153
cols = columns(klass, options)
153-
cols.each do |col|
154+
with_comments = with_comments?(klass, options)
155+
with_comments_column = with_comments_column?(klass, options)
156+
157+
# Precalculate Values
158+
cols_meta = cols.map do |col|
159+
col_comment = with_comments || with_comments_column ? col.comment&.gsub(/\n/, "\\n") : nil
154160
col_type = get_col_type(col)
155161
attrs = get_attributes(col, col_type, klass, options)
156-
col_name = if with_comments?(klass, options) && col.comment
157-
"#{col.name}(#{col.comment.gsub(/\n/, "\\n")})"
162+
col_name = if with_comments && col_comment
163+
"#{col.name}(#{col_comment})"
158164
else
159165
col.name
160166
end
167+
simple_formatted_attrs = attrs.join(", ")
168+
[col.name, { col_type: col_type, attrs: attrs, col_name: col_name, simple_formatted_attrs: simple_formatted_attrs, col_comment: col_comment }]
169+
end.to_h
170+
171+
# Output annotation
172+
bare_max_attrs_length = cols_meta.map { |_, m| m[:simple_formatted_attrs].length }.max
173+
174+
cols.each do |col|
175+
col_type = cols_meta[col.name][:col_type]
176+
attrs = cols_meta[col.name][:attrs]
177+
col_name = cols_meta[col.name][:col_name]
178+
simple_formatted_attrs = cols_meta[col.name][:simple_formatted_attrs]
179+
col_comment = cols_meta[col.name][:col_comment]
161180

162181
if options[:format_rdoc]
163182
info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col_name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
@@ -169,8 +188,10 @@ def get_schema_info(klass, header, options = {}) # rubocop:disable Metrics/Metho
169188
name_remainder = max_size - col_name.length - non_ascii_length(col_name)
170189
type_remainder = (md_type_allowance - 2) - col_type.length
171190
info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col_name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n"
191+
elsif with_comments_column
192+
info << format_default(col_name, max_size, col_type, bare_type_allowance, simple_formatted_attrs, bare_max_attrs_length, col_comment)
172193
else
173-
info << format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
194+
info << format_default(col_name, max_size, col_type, bare_type_allowance, simple_formatted_attrs)
174195
end
175196
end
176197

@@ -798,6 +819,12 @@ def with_comments?(klass, options)
798819
klass.columns.any? { |col| !col.comment.nil? }
799820
end
800821

822+
def with_comments_column?(klass, options)
823+
options[:with_comment_column] &&
824+
klass.columns.first.respond_to?(:comment) &&
825+
klass.columns.any? { |col| !col.comment.nil? }
826+
end
827+
801828
def max_schema_info_width(klass, options)
802829
cols = columns(klass, options)
803830

@@ -814,8 +841,15 @@ def max_schema_info_width(klass, options)
814841
max_size
815842
end
816843

817-
def format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
818-
sprintf("# %s:%s %s", mb_chars_ljust(col_name, max_size), mb_chars_ljust(col_type, bare_type_allowance), attrs.join(", ")).rstrip + "\n"
844+
# rubocop:disable Metrics/ParameterLists
845+
def format_default(col_name, max_size, col_type, bare_type_allowance, simple_formatted_attrs, bare_max_attrs_length = 0, col_comment = nil)
846+
sprintf(
847+
"# %s:%s %s %s",
848+
mb_chars_ljust(col_name, max_size),
849+
mb_chars_ljust(col_type, bare_type_allowance),
850+
mb_chars_ljust(simple_formatted_attrs, bare_max_attrs_length),
851+
col_comment
852+
).rstrip + "\n"
819853
end
820854

821855
def width(string)

lib/annotate/constants.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module Constants
1818
:trace, :timestamp, :exclude_serializers, :classified_sort,
1919
:show_foreign_keys, :show_complete_foreign_keys,
2020
:exclude_scaffolds, :exclude_controllers, :exclude_helpers,
21-
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment,
21+
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment, :with_comment_column,
2222
:show_check_constraints
2323
].freeze
2424

lib/annotate/parser.rb

+5
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ def add_options_to_parser(option_parser) # rubocop:disable Metrics/MethodLength,
304304
"include database comments in model annotations") do
305305
env['with_comment'] = 'true'
306306
end
307+
308+
option_parser.on('--with-comment-column',
309+
"include database comments in model annotations, as its own column, after all others") do
310+
env['with_comment_column'] = 'true'
311+
end
307312
end
308313
end
309314
end

spec/lib/annotate/annotate_models_spec.rb

+140
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,146 @@ def mock_column(name, type, options = {})
13081308
end
13091309
end
13101310
end
1311+
1312+
context 'when "with_comment_column" is specified in options' do
1313+
let :options do
1314+
{ with_comment_column: 'yes' }
1315+
end
1316+
1317+
context 'when columns have comments' do
1318+
let :columns do
1319+
[
1320+
mock_column(:id, :integer, limit: 8, comment: 'ID'),
1321+
mock_column(:active, :boolean, limit: 1, comment: 'Active'),
1322+
mock_column(:name, :string, limit: 50, comment: 'Name'),
1323+
mock_column(:notes, :text, limit: 55, comment: 'Notes'),
1324+
mock_column(:no_comment, :text, limit: 20, comment: nil)
1325+
]
1326+
end
1327+
1328+
let :expected_result do
1329+
<<~EOS
1330+
# Schema Info
1331+
#
1332+
# Table name: users
1333+
#
1334+
# id :integer not null, primary key ID
1335+
# active :boolean not null Active
1336+
# name :string(50) not null Name
1337+
# notes :text(55) not null Notes
1338+
# no_comment :text(20) not null
1339+
#
1340+
EOS
1341+
end
1342+
1343+
it 'works with option "with_comment_column"' do
1344+
is_expected.to eq expected_result
1345+
end
1346+
end
1347+
1348+
context 'when columns have multibyte comments' do
1349+
let :columns do
1350+
[
1351+
mock_column(:id, :integer, limit: 8, comment: 'ID'),
1352+
mock_column(:active, :boolean, limit: 1, comment: 'ACTIVE'),
1353+
mock_column(:name, :string, limit: 50, comment: 'NAME'),
1354+
mock_column(:notes, :text, limit: 55, comment: 'NOTES'),
1355+
mock_column(:cyrillic, :text, limit: 30, comment: 'Кириллица'),
1356+
mock_column(:japanese, :text, limit: 60, comment: '熊本大学 イタリア 宝島'),
1357+
mock_column(:arabic, :text, limit: 20, comment: 'لغة'),
1358+
mock_column(:no_comment, :text, limit: 20, comment: nil),
1359+
mock_column(:location, :geometry_collection, limit: nil, comment: nil)
1360+
]
1361+
end
1362+
1363+
let :expected_result do
1364+
<<~EOS
1365+
# Schema Info
1366+
#
1367+
# Table name: users
1368+
#
1369+
# id :integer not null, primary key ID
1370+
# active :boolean not null ACTIVE
1371+
# name :string(50) not null NAME
1372+
# notes :text(55) not null NOTES
1373+
# cyrillic :text(30) not null Кириллица
1374+
# japanese :text(60) not null 熊本大学 イタリア 宝島
1375+
# arabic :text(20) not null لغة
1376+
# no_comment :text(20) not null
1377+
# location :geometry_collect not null
1378+
#
1379+
EOS
1380+
end
1381+
1382+
it 'works with option "with_comment_column"' do
1383+
is_expected.to eq expected_result
1384+
end
1385+
end
1386+
1387+
context 'when columns have multiline comments' do
1388+
let :columns do
1389+
[
1390+
mock_column(:id, :integer, limit: 8, comment: 'ID'),
1391+
mock_column(:notes, :text, limit: 55, comment: "Notes.\nMay include things like notes."),
1392+
mock_column(:no_comment, :text, limit: 20, comment: nil)
1393+
]
1394+
end
1395+
1396+
let :expected_result do
1397+
<<~EOS
1398+
# Schema Info
1399+
#
1400+
# Table name: users
1401+
#
1402+
# id :integer not null, primary key ID
1403+
# notes :text(55) not null Notes.\\nMay include things like notes.
1404+
# no_comment :text(20) not null
1405+
#
1406+
EOS
1407+
end
1408+
1409+
it 'works with option "with_comment_column"' do
1410+
is_expected.to eq expected_result
1411+
end
1412+
end
1413+
1414+
context 'when geometry columns are included' do
1415+
let :columns do
1416+
[
1417+
mock_column(:id, :integer, limit: 8),
1418+
mock_column(:active, :boolean, default: false, null: false),
1419+
mock_column(:geometry, :geometry,
1420+
geometric_type: 'Geometry', srid: 4326,
1421+
limit: { srid: 4326, type: 'geometry' }),
1422+
mock_column(:location, :geography,
1423+
geometric_type: 'Point', srid: 0,
1424+
limit: { srid: 0, type: 'geometry' }),
1425+
mock_column(:non_srid, :geography,
1426+
geometric_type: 'Point',
1427+
limit: { type: 'geometry' })
1428+
]
1429+
end
1430+
1431+
let :expected_result do
1432+
<<~EOS
1433+
# Schema Info
1434+
#
1435+
# Table name: users
1436+
#
1437+
# id :integer not null, primary key
1438+
# active :boolean default(FALSE), not null
1439+
# geometry :geometry not null, geometry, 4326
1440+
# location :geography not null, point, 0
1441+
# non_srid :geography not null, point
1442+
#
1443+
EOS
1444+
end
1445+
1446+
it 'works with option "with_comment_column"' do
1447+
is_expected.to eq expected_result
1448+
end
1449+
end
1450+
end
13111451
end
13121452
end
13131453
end

spec/lib/annotate/parser_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -560,5 +560,15 @@ module Annotate # rubocop:disable Metrics/ModuleLength
560560
Parser.parse([option])
561561
end
562562
end
563+
564+
describe '--with-comment-column' do
565+
let(:option) { '--with-comment-column' }
566+
let(:env_key) { 'with_comment_column' }
567+
let(:set_value) { 'true' }
568+
it 'sets the ENV variable' do
569+
expect(ENV).to receive(:[]=).with(env_key, set_value)
570+
Parser.parse([option])
571+
end
572+
end
563573
end
564574
end

spec/spec_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@
3636

3737
RSpec.configure do |config|
3838
config.order = 'random'
39+
config.filter_run_when_matching :focus
3940
end

0 commit comments

Comments
 (0)