diff --git a/lib/annotate/annotate_models.rb b/lib/annotate/annotate_models.rb index 1b9911ee3..de6218ea5 100644 --- a/lib/annotate/annotate_models.rb +++ b/lib/annotate/annotate_models.rb @@ -149,7 +149,7 @@ def get_schema_info(klass, header, options = {}) cols.each do |col| 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 = if with_column_comments?(klass, options) && col.comment "#{col.name}(#{col.comment.gsub(/\n/, "\\n")})" else col.name @@ -182,13 +182,16 @@ def get_schema_info(klass, header, options = {}) end def get_schema_header_text(klass, options = {}) + table_comment = with_table_comments?(klass, options) ? "(#{klass.connection.table_comment(klass.table_name)})" : '' + table_name = klass.table_name.to_s + table_comment + info = "#\n" if options[:format_markdown] - info << "# Table name: `#{klass.table_name}`\n" + info << "# Table name: `#{table_name}`\n" info << "#\n" info << "# ### Columns\n" else - info << "# Table name: #{klass.table_name}\n" + info << "# Table name: #{table_name}\n" end info << "#\n" end @@ -756,16 +759,22 @@ def classified_sort(cols) private - def with_comments?(klass, options) + def with_column_comments?(klass, options) options[:with_comment] && klass.columns.first.respond_to?(:comment) && klass.columns.any? { |col| !col.comment.nil? } end + def with_table_comments?(klass, options) + options[:with_comment] && + klass.connection.respond_to?(:table_comment) && + klass.connection.table_comment(klass.table_name).present? + end + def max_schema_info_width(klass, options) cols = columns(klass, options) - if with_comments?(klass, options) + if with_column_comments?(klass, options) max_size = cols.map do |column| column.name.size + (column.comment ? width(column.comment) : 0) end.max || 0 diff --git a/spec/integration/rails_5.2.4.1/Gemfile.lock b/spec/integration/rails_5.2.4.1/Gemfile.lock index 46eead470..376897094 100644 --- a/spec/integration/rails_5.2.4.1/Gemfile.lock +++ b/spec/integration/rails_5.2.4.1/Gemfile.lock @@ -8,43 +8,43 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.1) - actionpack (= 5.2.4.1) + actioncable (5.2.5) + actionpack (= 5.2.5) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) + actionmailer (5.2.5) + actionpack (= 5.2.5) + actionview (= 5.2.5) + activejob (= 5.2.5) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.1) - actionview (= 5.2.4.1) - activesupport (= 5.2.4.1) + actionpack (5.2.5) + actionview (= 5.2.5) + activesupport (= 5.2.5) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.1) - activesupport (= 5.2.4.1) + actionview (5.2.5) + activesupport (= 5.2.5) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.4.1) - activesupport (= 5.2.4.1) + activejob (5.2.5) + activesupport (= 5.2.5) globalid (>= 0.3.6) - activemodel (5.2.4.1) - activesupport (= 5.2.4.1) - activerecord (5.2.4.1) - activemodel (= 5.2.4.1) - activesupport (= 5.2.4.1) + activemodel (5.2.5) + activesupport (= 5.2.5) + activerecord (5.2.5) + activemodel (= 5.2.5) + activesupport (= 5.2.5) arel (>= 9.0) - activestorage (5.2.4.1) - actionpack (= 5.2.4.1) - activerecord (= 5.2.4.1) - marcel (~> 0.3.1) - activesupport (5.2.4.1) + activestorage (5.2.5) + actionpack (= 5.2.5) + activerecord (= 5.2.5) + marcel (~> 1.0.0) + activesupport (5.2.5) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -55,11 +55,11 @@ GEM io-like (~> 0.3.0) arel (9.0.0) bindex (0.8.1) - bootsnap (1.4.5) + bootsnap (1.7.3) msgpack (~> 1.0) builder (3.2.4) - byebug (11.1.1) - capybara (3.31.0) + byebug (11.1.3) + capybara (3.32.2) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -78,75 +78,73 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.8) crass (1.0.6) - erubi (1.9.0) + erubi (1.10.0) execjs (2.7.0) - ffi (1.12.2) + ffi (1.15.0) globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.8.2) + i18n (1.8.10) concurrent-ruby (~> 1.0) - io-like (0.3.0) - jbuilder (2.9.1) - activesupport (>= 4.2.0) + io-like (0.3.1) + jbuilder (2.11.2) + activesupport (>= 5.0.0) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - loofah (2.4.0) + loofah (2.9.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mimemagic (0.3.4) - mini_mime (1.0.2) + marcel (1.0.1) + method_source (1.0.0) + mini_mime (1.0.3) mini_portile2 (2.4.0) - minitest (5.14.0) - msgpack (1.3.2) - nio4r (2.5.2) - nokogiri (1.10.8) + minitest (5.14.4) + msgpack (1.4.2) + nio4r (2.5.7) + nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - public_suffix (4.0.3) - puma (4.3.3) + public_suffix (4.0.6) + puma (4.3.7) nio4r (~> 2.0) - rack (2.1.2) + rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.4.1) - actioncable (= 5.2.4.1) - actionmailer (= 5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) - activemodel (= 5.2.4.1) - activerecord (= 5.2.4.1) - activestorage (= 5.2.4.1) - activesupport (= 5.2.4.1) + rails (5.2.5) + actioncable (= 5.2.5) + actionmailer (= 5.2.5) + actionpack (= 5.2.5) + actionview (= 5.2.5) + activejob (= 5.2.5) + activemodel (= 5.2.5) + activerecord (= 5.2.5) + activestorage (= 5.2.5) + activesupport (= 5.2.5) bundler (>= 1.3.0) - railties (= 5.2.4.1) + railties (= 5.2.5) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (5.2.4.1) - actionpack (= 5.2.4.1) - activesupport (= 5.2.4.1) + railties (5.2.5) + actionpack (= 5.2.5) + activesupport (= 5.2.5) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) - rake (13.0.1) - rb-fsevent (0.10.3) + rake (13.0.3) + rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) - regexp_parser (1.6.0) + regexp_parser (1.8.2) ruby_dep (1.5.0) - rubyzip (2.2.0) + rubyzip (2.3.0) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) @@ -164,18 +162,18 @@ GEM sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.4.2) - thor (1.0.1) + thor (1.1.0) thread_safe (0.3.6) tilt (2.0.10) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (1.2.6) + tzinfo (1.2.9) thread_safe (~> 0.1) uglifier (4.2.0) execjs (>= 0.3.0, < 3) @@ -184,9 +182,9 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - websocket-driver (0.7.1) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) + websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) @@ -213,4 +211,4 @@ DEPENDENCIES web-console (>= 3.3.0) BUNDLED WITH - 2.1.2 + 2.2.15 diff --git a/spec/integration/rails_6.0.2.1/Gemfile.lock b/spec/integration/rails_6.0.2.1/Gemfile.lock index 06ea963e4..fc06ea604 100644 --- a/spec/integration/rails_6.0.2.1/Gemfile.lock +++ b/spec/integration/rails_6.0.2.1/Gemfile.lock @@ -79,13 +79,13 @@ GEM regexp_parser (~> 1.5) xpath (~> 3.2) childprocess (3.0.0) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.8) crass (1.0.6) - erubi (1.9.0) + erubi (1.10.0) ffi (1.12.2) globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.8.2) + i18n (1.8.10) concurrent-ruby (~> 1.0) jbuilder (2.9.1) activesupport (>= 4.2.0) @@ -93,7 +93,7 @@ GEM rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - loofah (2.4.0) + loofah (2.9.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -101,18 +101,22 @@ GEM marcel (0.3.3) mimemagic (~> 0.3.2) method_source (0.9.2) - mimemagic (0.3.4) + mimemagic (0.3.10) + nokogiri (~> 1) + rake mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.0) + mini_portile2 (2.5.0) + minitest (5.14.4) msgpack (1.3.1) nio4r (2.5.2) - nokogiri (1.10.8) - mini_portile2 (~> 2.4.0) + nokogiri (1.11.2) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) public_suffix (4.0.3) puma (4.3.3) nio4r (~> 2.0) - rack (2.1.2) + racc (1.5.2) + rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) rails (6.0.2.1) @@ -141,7 +145,7 @@ GEM method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) - rake (13.0.1) + rake (13.0.3) rb-fsevent (0.10.3) rb-inotify (0.10.1) ffi (~> 1.0) @@ -161,7 +165,7 @@ GEM sqlite3 (1.4.2) thor (1.0.1) thread_safe (0.3.6) - tzinfo (1.2.6) + tzinfo (1.2.9) thread_safe (~> 0.1) web-console (4.0.1) actionview (>= 6.0.0) @@ -177,7 +181,7 @@ GEM websocket-extensions (0.1.4) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.2.2) + zeitwerk (2.4.2) PLATFORMS ruby @@ -198,4 +202,4 @@ DEPENDENCIES webdrivers BUNDLED WITH - 2.1.2 + 2.2.15 diff --git a/spec/lib/annotate/annotate_models_spec.rb b/spec/lib/annotate/annotate_models_spec.rb index e2d6f41ab..ae5e33199 100644 --- a/spec/lib/annotate/annotate_models_spec.rb +++ b/spec/lib/annotate/annotate_models_spec.rb @@ -41,22 +41,26 @@ def mock_foreign_key(name, from_column, to_table, to_column = 'id', constraints on_update: constraints[:on_update]) end - def mock_connection(indexes = [], foreign_keys = []) - double('Conn', - indexes: indexes, - foreign_keys: foreign_keys, - supports_foreign_keys?: true) + def mock_connection(options) + default_options = { + indexes: [], + foreign_keys: [], + supports_foreign_keys?: true, + table_comment: nil + } + + double('Conn', default_options.merge(options)) end - def mock_class(table_name, primary_key, columns, indexes = [], foreign_keys = []) + def mock_class(table_name, primary_key, columns, connection_options: {}) options = { - connection: mock_connection(indexes, foreign_keys), - table_exists?: true, - table_name: table_name, - primary_key: primary_key, - column_names: columns.map { |col| col.name.to_s }, - columns: columns, - column_defaults: Hash[columns.map { |col| [col.name, col.default] }], + connection: mock_connection(connection_options), + table_exists?: true, + table_name: table_name, + primary_key: primary_key, + column_names: columns.map { |col| col.name.to_s }, + columns: columns, + column_defaults: Hash[columns.map { |col| [col.name, col.default] }], table_name_prefix: '' } @@ -217,7 +221,14 @@ def mock_column(name, type, options = {}) end let :klass do - mock_class(:users, primary_key, columns, indexes, foreign_keys) + mock_class(:users, + primary_key, + columns, + connection_options: { + indexes: indexes, + foreign_keys: foreign_keys, + table_comment: table_comment + }) end let :indexes do @@ -228,6 +239,10 @@ def mock_column(name, type, options = {}) [] end + let :table_comment do + nil + end + context 'when option is not present' do let :options do {} @@ -400,7 +415,13 @@ def mock_column(name, type, options = {}) end let :klass do - mock_class(:posts, primary_key, columns, indexes, foreign_keys).tap do |mock_klass| + mock_class(:posts, + primary_key, + columns, + connection_options: { + indexes: indexes, + foreign_keys: foreign_keys + }).tap do |mock_klass| allow(mock_klass).to receive(:translation_class).and_return(translation_klass) end end @@ -1061,6 +1082,33 @@ def mock_column(name, type, options = {}) { with_comment: 'yes' } end + context 'when table have comments' do + let :table_comment do + 'users table comment' + end + + let :columns do + [ + mock_column(:id, :integer, limit: 8), + ] + end + + let :expected_result do + <<~EOS + # Schema Info + # + # Table name: users(users table comment) + # + # id :integer not null, primary key + # + EOS + end + + it 'works with option "with_comment"' do + is_expected.to eq expected_result + end + end + context 'when columns have comments' do let :columns do [ @@ -1092,6 +1140,41 @@ def mock_column(name, type, options = {}) end end + context 'when both table and columns have comments' do + let :table_comment do + 'users table comment' + end + + 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(users table comment) + # + # id(ID) :integer not null, primary key + # active(Active) :boolean not null + # name(Name) :string(50) not null + # notes(Notes) :text(55) not null + # no_comment :text(20) not null + # + EOS + end + + it 'works with option "with_comment' do + is_expected.to eq expected_result + end + end + context 'when columns have multibyte comments' do let :columns do [ @@ -2566,14 +2649,16 @@ def annotate_one_file(options = {}) mock_column(:id, :integer), mock_column(:foreign_thing_id, :integer) ], - [], - [ - mock_foreign_key('fk_rails_cf2568e89e', - 'foreign_thing_id', - 'foreign_things', - 'id', - on_delete: :cascade) - ]) + connection_options: { + indexes: [], + foreign_keys: [ + mock_foreign_key('fk_rails_cf2568e89e', + 'foreign_thing_id', + 'foreign_things', + 'id', + on_delete: :cascade) + ] + }) @schema_info = AnnotateModels.get_schema_info(klass, '== Schema Info', show_foreign_keys: true) annotate_one_file end @@ -2585,14 +2670,16 @@ def annotate_one_file(options = {}) mock_column(:id, :integer), mock_column(:foreign_thing_id, :integer) ], - [], - [ - mock_foreign_key('fk_rails_cf2568e89e', - 'foreign_thing_id', - 'foreign_things', - 'id', - on_delete: :restrict) - ]) + connection_options: { + indexes: [], + foreign_keys: [ + mock_foreign_key('fk_rails_cf2568e89e', + 'foreign_thing_id', + 'foreign_things', + 'id', + on_delete: :restrict) + ] + }) @schema_info = AnnotateModels.get_schema_info(klass, '== Schema Info', show_foreign_keys: true) annotate_one_file expect(File.read(@model_file_name)).to eq("#{@schema_info}#{@file_content}")