Skip to content

Place column comments at the end of the line #988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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