Skip to content

Commit

Permalink
Place column comments at the end of the line (#988)
Browse files Browse the repository at this point in the history
  • Loading branch information
Adeynack authored Jun 24, 2023
1 parent 3a78787 commit e60a666
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ you can do so with a simple environment variable, instead of editing the
don't show default for given column types, separated by commas (e.g. `json,jsonb,hstore`)
--ignore-unknown-models don't display warnings for bad model files
--with-comment include database comments in model annotations
--with-comment-column include database comments in model annotations, as its own column, after all others

### Option: `additional_file_patterns`

Expand Down
46 changes: 40 additions & 6 deletions lib/annotate/annotate_models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,18 +146,37 @@ def get_schema_info(klass, header, options = {}) # rubocop:disable Metrics/Metho

if options[:format_markdown]
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' )

info << "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
end

cols = columns(klass, options)
cols.each do |col|
with_comments = with_comments?(klass, options)
with_comments_column = with_comments_column?(klass, options)

# Precalculate Values
cols_meta = cols.map do |col|
col_comment = with_comments || with_comments_column ? col.comment&.gsub(/\n/, "\\n") : nil
col_type = get_col_type(col)
attrs = get_attributes(col, col_type, klass, options)
col_name = if with_comments?(klass, options) && col.comment
"#{col.name}(#{col.comment.gsub(/\n/, "\\n")})"
col_name = if with_comments && col_comment
"#{col.name}(#{col_comment})"
else
col.name
end
simple_formatted_attrs = attrs.join(", ")
[col.name, { col_type: col_type, attrs: attrs, col_name: col_name, simple_formatted_attrs: simple_formatted_attrs, col_comment: col_comment }]
end.to_h

# Output annotation
bare_max_attrs_length = cols_meta.map { |_, m| m[:simple_formatted_attrs].length }.max

cols.each do |col|
col_type = cols_meta[col.name][:col_type]
attrs = cols_meta[col.name][:attrs]
col_name = cols_meta[col.name][:col_name]
simple_formatted_attrs = cols_meta[col.name][:simple_formatted_attrs]
col_comment = cols_meta[col.name][:col_comment]

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

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

def with_comments_column?(klass, options)
options[:with_comment_column] &&
klass.columns.first.respond_to?(:comment) &&
klass.columns.any? { |col| !col.comment.nil? }
end

def max_schema_info_width(klass, options)
cols = columns(klass, options)

Expand All @@ -814,8 +841,15 @@ def max_schema_info_width(klass, options)
max_size
end

def format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
sprintf("# %s:%s %s", mb_chars_ljust(col_name, max_size), mb_chars_ljust(col_type, bare_type_allowance), attrs.join(", ")).rstrip + "\n"
# rubocop:disable Metrics/ParameterLists
def format_default(col_name, max_size, col_type, bare_type_allowance, simple_formatted_attrs, bare_max_attrs_length = 0, col_comment = nil)
sprintf(
"# %s:%s %s %s",
mb_chars_ljust(col_name, max_size),
mb_chars_ljust(col_type, bare_type_allowance),
mb_chars_ljust(simple_formatted_attrs, bare_max_attrs_length),
col_comment
).rstrip + "\n"
end

def width(string)
Expand Down
2 changes: 1 addition & 1 deletion lib/annotate/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module Constants
:trace, :timestamp, :exclude_serializers, :classified_sort,
:show_foreign_keys, :show_complete_foreign_keys,
:exclude_scaffolds, :exclude_controllers, :exclude_helpers,
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment,
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment, :with_comment_column,
:show_check_constraints
].freeze

Expand Down
5 changes: 5 additions & 0 deletions lib/annotate/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ def add_options_to_parser(option_parser) # rubocop:disable Metrics/MethodLength,
"include database comments in model annotations") do
env['with_comment'] = 'true'
end

option_parser.on('--with-comment-column',
"include database comments in model annotations, as its own column, after all others") do
env['with_comment_column'] = 'true'
end
end
end
end
140 changes: 140 additions & 0 deletions spec/lib/annotate/annotate_models_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,146 @@ def mock_column(name, type, options = {})
end
end
end

context 'when "with_comment_column" is specified in options' do
let :options do
{ with_comment_column: 'yes' }
end

context 'when columns have comments' do
let :columns do
[
mock_column(:id, :integer, limit: 8, comment: 'ID'),
mock_column(:active, :boolean, limit: 1, comment: 'Active'),
mock_column(:name, :string, limit: 50, comment: 'Name'),
mock_column(:notes, :text, limit: 55, comment: 'Notes'),
mock_column(:no_comment, :text, limit: 20, comment: nil)
]
end

let :expected_result do
<<~EOS
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key ID
# active :boolean not null Active
# name :string(50) not null Name
# notes :text(55) not null Notes
# no_comment :text(20) not null
#
EOS
end

it 'works with option "with_comment_column"' do
is_expected.to eq expected_result
end
end

context 'when columns have multibyte comments' do
let :columns do
[
mock_column(:id, :integer, limit: 8, comment: 'ID'),
mock_column(:active, :boolean, limit: 1, comment: 'ACTIVE'),
mock_column(:name, :string, limit: 50, comment: 'NAME'),
mock_column(:notes, :text, limit: 55, comment: 'NOTES'),
mock_column(:cyrillic, :text, limit: 30, comment: 'Кириллица'),
mock_column(:japanese, :text, limit: 60, comment: '熊本大学 イタリア 宝島'),
mock_column(:arabic, :text, limit: 20, comment: 'لغة'),
mock_column(:no_comment, :text, limit: 20, comment: nil),
mock_column(:location, :geometry_collection, limit: nil, comment: nil)
]
end

let :expected_result do
<<~EOS
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key ID
# active :boolean not null ACTIVE
# name :string(50) not null NAME
# notes :text(55) not null NOTES
# cyrillic :text(30) not null Кириллица
# japanese :text(60) not null 熊本大学 イタリア 宝島
# arabic :text(20) not null لغة
# no_comment :text(20) not null
# location :geometry_collect not null
#
EOS
end

it 'works with option "with_comment_column"' do
is_expected.to eq expected_result
end
end

context 'when columns have multiline comments' do
let :columns do
[
mock_column(:id, :integer, limit: 8, comment: 'ID'),
mock_column(:notes, :text, limit: 55, comment: "Notes.\nMay include things like notes."),
mock_column(:no_comment, :text, limit: 20, comment: nil)
]
end

let :expected_result do
<<~EOS
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key ID
# notes :text(55) not null Notes.\\nMay include things like notes.
# no_comment :text(20) not null
#
EOS
end

it 'works with option "with_comment_column"' do
is_expected.to eq expected_result
end
end

context 'when geometry columns are included' do
let :columns do
[
mock_column(:id, :integer, limit: 8),
mock_column(:active, :boolean, default: false, null: false),
mock_column(:geometry, :geometry,
geometric_type: 'Geometry', srid: 4326,
limit: { srid: 4326, type: 'geometry' }),
mock_column(:location, :geography,
geometric_type: 'Point', srid: 0,
limit: { srid: 0, type: 'geometry' }),
mock_column(:non_srid, :geography,
geometric_type: 'Point',
limit: { type: 'geometry' })
]
end

let :expected_result do
<<~EOS
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key
# active :boolean default(FALSE), not null
# geometry :geometry not null, geometry, 4326
# location :geography not null, point, 0
# non_srid :geography not null, point
#
EOS
end

it 'works with option "with_comment_column"' do
is_expected.to eq expected_result
end
end
end
end
end
end
Expand Down
10 changes: 10 additions & 0 deletions spec/lib/annotate/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -560,5 +560,15 @@ module Annotate # rubocop:disable Metrics/ModuleLength
Parser.parse([option])
end
end

describe '--with-comment-column' do
let(:option) { '--with-comment-column' }
let(:env_key) { 'with_comment_column' }
let(:set_value) { 'true' }
it 'sets the ENV variable' do
expect(ENV).to receive(:[]=).with(env_key, set_value)
Parser.parse([option])
end
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@

RSpec.configure do |config|
config.order = 'random'
config.filter_run_when_matching :focus
end

0 comments on commit e60a666

Please sign in to comment.