annotaterb 4.16.0 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91c5e0b20f8c7f5a3160bd2eac0f1e96f9b05e9fd72ddd08fc18422d63472510
4
- data.tar.gz: e7bfda05d5943c0b2c978dc6863151aad9c1934b6ff5551ec88292ab96dfc57d
3
+ metadata.gz: 0276a5044aa1b444f73aac5612004ce9a8c05c6d47634fb2e8798c24a442fed5
4
+ data.tar.gz: 58cd27cb187989b06c459979ac0152db9dfdb54d671b9348980c244caa7981a6
5
5
  SHA512:
6
- metadata.gz: 42f9849dd393c04606dcbd7ada4749bfb617180fe3992d19adb16f17f00a9e4af4ecae34d2a9305b5a2ab5b359c4762a87c83cd43c395302e14387b43538ccb2
7
- data.tar.gz: 5cc6acd44ac8ae6c4aeb5895e776adf3ac75044fc67e4c2682527256bda5a36915670dbae92e3a478f55d883907c5c480e28c769b7f40c219c5d4a06fc64750f
6
+ metadata.gz: ac0f491e7022299fc96029950c5edf2b0e05aa38e9f3198ec37acbd04275888be493f4202f31295e8db0ad7737cc03d2c6b52e5fbb51c24418ef92f40da77a13
7
+ data.tar.gz: 914216a38c137682acb6e2b58154c3af47dba3b0ebb424b69194a59c3939cfdd01c00d0f75a10bb51800f2ea6ce3f4dc97f3cdcd592ff7f77e7cafc906cd660f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.17.0](https://github.com/drwl/annotaterb/tree/v4.17.0) (2025-07-14)
4
+
5
+ [Full Changelog](https://github.com/drwl/annotaterb/compare/v4.16.0...v4.17.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Place column comments at the end of the line \(feature suggestion\) [\#164](https://github.com/drwl/annotaterb/issues/164)
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Model annotation chokes on an empty file [\#182](https://github.com/drwl/annotaterb/issues/182)
14
+
15
+ **Closed issues:**
16
+
17
+ - uninitialized constant Zeitwerk::VERSION [\#216](https://github.com/drwl/annotaterb/issues/216)
18
+ - Incorrect annotation of fixture files when different models share the same table name in different databases [\#206](https://github.com/drwl/annotaterb/issues/206)
19
+ - Should active record and active support be in the gemspec? [\#197](https://github.com/drwl/annotaterb/issues/197)
20
+
21
+ **Merged pull requests:**
22
+
23
+ - Bump version to v4.17.0 [\#234](https://github.com/drwl/annotaterb/pull/234) ([drwl](https://github.com/drwl))
24
+ - Fix NoMethodError when processing empty files [\#232](https://github.com/drwl/annotaterb/pull/232) ([tanukiti1987](https://github.com/tanukiti1987))
25
+ - Refactor column ignore to use `match?` [\#231](https://github.com/drwl/annotaterb/pull/231) ([tagliala](https://github.com/tagliala))
26
+ - Fix standard configuration [\#230](https://github.com/drwl/annotaterb/pull/230) ([tagliala](https://github.com/tagliala))
27
+ - Generate changelog for v4.16.0 [\#229](https://github.com/drwl/annotaterb/pull/229) ([drwl](https://github.com/drwl))
28
+ - show included columns in indexes [\#211](https://github.com/drwl/annotaterb/pull/211) ([pineman](https://github.com/pineman))
29
+ - fix: use model name for file retrieval when not connected to the primary DB. [\#207](https://github.com/drwl/annotaterb/pull/207) ([OdenTakashi](https://github.com/OdenTakashi))
30
+ - Place column comments at the end of the line [\#199](https://github.com/drwl/annotaterb/pull/199) ([Adeynack](https://github.com/Adeynack))
31
+
32
+ ## [v4.16.0](https://github.com/drwl/annotaterb/tree/v4.16.0) (2025-06-18)
33
+
34
+ [Full Changelog](https://github.com/drwl/annotaterb/compare/v4.15.0...v4.16.0)
35
+
36
+ **Implemented enhancements:**
37
+
38
+ - Feature Request: Add Option to Place Annotations Above Nested Classes or Modules. [\#186](https://github.com/drwl/annotaterb/issues/186)
39
+
40
+ **Closed issues:**
41
+
42
+ - Misleading pattern examples in documentation for `additional_file_patterns` [\#221](https://github.com/drwl/annotaterb/issues/221)
43
+ - Permission denied for table pg\_index [\#209](https://github.com/drwl/annotaterb/issues/209)
44
+ - Performance regression relative to pre-fork? [\#205](https://github.com/drwl/annotaterb/issues/205)
45
+ - Add back ruby configuration option? [\#203](https://github.com/drwl/annotaterb/issues/203)
46
+ - Enable frozen mode when CI environment variable is set [\#171](https://github.com/drwl/annotaterb/issues/171)
47
+
48
+ **Merged pull requests:**
49
+
50
+ - Bump version to v4.16.0 [\#228](https://github.com/drwl/annotaterb/pull/228) ([drwl](https://github.com/drwl))
51
+ - chore: add --with-column-comments readme documentation [\#227](https://github.com/drwl/annotaterb/pull/227) ([jonmcelroy-appfolio](https://github.com/jonmcelroy-appfolio))
52
+ - Drop Ruby 2.7 support and improve CI [\#226](https://github.com/drwl/annotaterb/pull/226) ([drwl](https://github.com/drwl))
53
+ - Pass .annotaterb.yml through ERB [\#225](https://github.com/drwl/annotaterb/pull/225) ([fxn](https://github.com/fxn))
54
+ - feat: Add `--nested-position` option for placing annotations above nested classes. [\#223](https://github.com/drwl/annotaterb/pull/223) ([yamat47](https://github.com/yamat47))
55
+ - Fix for: Misleading pattern examples in documentation for additional\_file\_patterns [\#222](https://github.com/drwl/annotaterb/pull/222) ([skliarov](https://github.com/skliarov))
56
+ - Move activerecord dependency into gemspec [\#220](https://github.com/drwl/annotaterb/pull/220) ([drwl](https://github.com/drwl))
57
+ - Generate changelog for v4.15.0 [\#219](https://github.com/drwl/annotaterb/pull/219) ([drwl](https://github.com/drwl))
58
+ - chore: add rake task to automatically deploy to rubygems [\#183](https://github.com/drwl/annotaterb/pull/183) ([OdenTakashi](https://github.com/OdenTakashi))
59
+
3
60
  ## [v4.15.0](https://github.com/drwl/annotaterb/tree/v4.15.0) (2025-05-30)
4
61
 
5
62
  [Full Changelog](https://github.com/drwl/annotaterb/compare/v4.14.1...v4.15.0)
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  ## AnnotateRb
2
+
2
3
  ### forked from the [Annotate aka AnnotateModels gem](https://github.com/ctran/annotate_models)
3
4
 
4
5
  A Ruby Gem that adds annotations to your Rails models and route files.
5
6
 
6
- ----------
7
+ ---
8
+
7
9
  [![CI](https://github.com/drwl/annotaterb/actions/workflows/ci.yml/badge.svg)](https://github.com/drwl/annotaterb/actions/workflows/ci.yml)
8
10
  [![Gem Version](https://badge.fury.io/rb/annotaterb.svg)](https://badge.fury.io/rb/annotaterb)
9
11
 
@@ -32,7 +34,9 @@ The schema comment looks like this:
32
34
  class Task < ApplicationRecord
33
35
  ...
34
36
  ```
35
- ----------
37
+
38
+ ---
39
+
36
40
  ## Installation
37
41
 
38
42
  ```sh
@@ -46,11 +50,12 @@ group :development do
46
50
  ...
47
51
 
48
52
  gem "annotaterb"
49
-
53
+
50
54
  ...
51
55
  ```
52
56
 
53
57
  ### Automatically annotate models
58
+
54
59
  For Rails projects, model files can get automatically annotated after migration tasks. To do this, run the following command:
55
60
 
56
61
  ```sh
@@ -66,6 +71,7 @@ $ ANNOTATERB_SKIP_ON_DB_TASKS=1 bin/rails db:migrate
66
71
  ```
67
72
 
68
73
  ### Added Rails generators
74
+
69
75
  The following Rails generator commands get added.
70
76
 
71
77
  ```sh
@@ -83,18 +89,23 @@ AnnotateRb:
83
89
  ```
84
90
 
85
91
  `bin/rails g annotate_rb:config`
92
+
86
93
  - Generates a new configuration file, `.annotaterb.yml`, using defaults from the gem.
87
94
 
88
95
  `bin/rails g annotate_rb:hook`
96
+
89
97
  - Installs the Rake file to automatically annotate Rails models on a database task (e.g. AnnotateRb will automatically run after running `bin/rails db:migrate`).
90
98
 
91
99
  `bin/rails g annotate_rb:install`
100
+
92
101
  - Runs the `config` and `hook` generator commands
93
102
 
94
103
  `bin/rails g annotate_rb:update_config`
104
+
95
105
  - Appends to `.annotaterb.yml` any configuration key-value pairs that are used by the Gem. This is useful when there's a drift between the config file values and the gem defaults (i.e. when new features get added).
96
106
 
97
107
  ## Migrating from the annotate gem
108
+
98
109
  Refer to the [migration guide](MIGRATION_GUIDE.md).
99
110
 
100
111
  ## Usage
@@ -103,7 +114,7 @@ AnnotateRb has a CLI that you can use to add or remove annotations.
103
114
 
104
115
  ```sh
105
116
  # To show the CLI options
106
- $ bundle exec annotaterb
117
+ $ bundle exec annotaterb
107
118
 
108
119
  Usage: annotaterb [command] [options]
109
120
 
@@ -127,6 +138,7 @@ Annotate model options:
127
138
  Complete foreign key names in the annotation
128
139
  -i, --show-indexes List the table's database indexes in the annotation
129
140
  -s, --simple-indexes Concat the column's related indexes in the annotation
141
+ -c, --show-check-constraints List the table's check constraints in the annotation
130
142
  --hide-limit-column-types VALUES
131
143
  don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)
132
144
  --hide-default-column-types VALUES
@@ -134,7 +146,15 @@ Annotate model options:
134
146
  --ignore-unknown-models don't display warnings for bad model files
135
147
  -I, --ignore-columns REGEX don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`
136
148
  --with-comment include database comments in model annotations
137
- --with-column-comments include column comments in model annotations
149
+ --without-comment include database comments in model annotations
150
+ --with-column-comments include column comments in model annotations
151
+ --without-column-comments exclude column comments in model annotations
152
+ --position-of-column-comments VALUE
153
+ set the position, in the annotation block, of the column comment
154
+ --with-table-comments include table comments in model annotations
155
+ --without-table-comments exclude table comments in model annotations
156
+ --classes-default-to-s class Custom classes to be represented with `to_s`, may be used multiple times
157
+ --nested-position Place annotations directly above nested classes or modules instead of at the top of the file.
138
158
 
139
159
  Annotate routes options:
140
160
  Usage: annotaterb routes [options]
@@ -157,6 +177,7 @@ Additional options that work for annotating models and routes
157
177
  --ignore-model-subdirects Ignore subdirectories of the models directory
158
178
  --sort Sort columns alphabetically, rather than in creation order
159
179
  --classified-sort Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns
180
+ --grouped-polymorphic Group polymorphic associations together in the annotation when using --classified-sort
160
181
  -R, --require path Additional file to require before loading models, may be used multiple times
161
182
  -e [tests,fixtures,factories,serializers],
162
183
  --exclude Do not annotate fixtures, test files, factories, and/or serializers
@@ -176,6 +197,8 @@ Additional options that work for annotating models and routes
176
197
  Place the annotations at the top (before) or the bottom (after) of the routes.rb file
177
198
  --ps, --position-in-serializer [before|top|after|bottom]
178
199
  Place the annotations at the top (before) or the bottom (after) of the serializer files
200
+ --pa, --position-in-additional-file-patterns [before|top|after|bottom]
201
+ Place the annotations at the top (before) or the bottom (after) of files captured in additional file patterns
179
202
  --force Force new annotations even if there are no changes.
180
203
  --debug Prints the options and outputs messages to make it easier to debug.
181
204
  --frozen Do not allow to change annotations. Exits non-zero if there are going to be changes to files.
@@ -185,6 +208,7 @@ Additional options that work for annotating models and routes
185
208
  ## Configuration
186
209
 
187
210
  ### Storing default options
211
+
188
212
  Previously in the [Annotate](https://github.com/ctran/annotate_models) you could pass options through the CLI or store them as environment variables. Annotaterb removes dependency on the environment variables and instead can read values from a `.annotaterb.yml` file stored in the Rails project root.
189
213
 
190
214
  ```yml
@@ -198,6 +222,7 @@ Annotaterb reads first the configuration file, if it exists, passes its content
198
222
  For further details visit the [section in the migration guide](MIGRATION_GUIDE.md#automatic-annotations-after-running-database-migration-commands).
199
223
 
200
224
  ### How to skip annotating a particular model
225
+
201
226
  If you want to always skip annotations on a particular model, add this string
202
227
  anywhere in the file:
203
228
 
@@ -211,6 +236,10 @@ migrations were run).
211
236
  If you prefer to sort alphabetically so that the results of annotation are
212
237
  consistent regardless of what order migrations are executed in, use `--sort`.
213
238
 
239
+ You can also sort columns by type, then alphabetically using `--classified-sort`
240
+ and `--grouped-polymorphic`: first goes id, then the rest columns, then the
241
+ timestamp columns and then the association columns.
242
+
214
243
  ## License
215
244
 
216
245
  Released under the same license as Ruby. No Support. No Warranty.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.16.0
1
+ 4.18.0
@@ -63,6 +63,9 @@ module AnnotateRb
63
63
  # When nested_position is enabled, finds the most deeply nested class declaration
64
64
  # to place annotations directly above nested classes instead of at the file top.
65
65
  def determine_annotation_position(parsed)
66
+ # Handle empty files where no classes/modules are found
67
+ return [nil, 0] if parsed.starts.empty?
68
+
66
69
  return parsed.starts.first unless @options[:nested_position]
67
70
 
68
71
  class_entries = parsed.starts.select { |name, _line| parsed.type_map[name] == :class }
@@ -90,6 +93,17 @@ module AnnotateRb
90
93
  def content_annotated_after(parsed, content_without_annotations)
91
94
  _constant_name, line_number_after = parsed.ends.last
92
95
 
96
+ # Handle empty files where no classes/modules are found
97
+ if line_number_after.nil?
98
+ content_lines = content_without_annotations.lines
99
+ # For empty files, append annotations at the end
100
+ content_with_annotations_written_after = []
101
+ content_with_annotations_written_after << content_lines
102
+ content_with_annotations_written_after << $/ unless content_lines.empty?
103
+ content_with_annotations_written_after << @new_wrapped_annotations.lines
104
+ return content_with_annotations_written_after.join
105
+ end
106
+
93
107
  content_with_annotations_written_after = []
94
108
  content_with_annotations_written_after << content_without_annotations.lines[0..line_number_after]
95
109
  content_with_annotations_written_after << $/
@@ -59,17 +59,7 @@ module AnnotateRb
59
59
  end
60
60
 
61
61
  def build
62
- if @options.get_state(:current_version).nil?
63
- migration_version = begin
64
- ActiveRecord::Migrator.current_version
65
- rescue
66
- 0
67
- end
68
-
69
- @options.set_state(:current_version, migration_version)
70
- end
71
-
72
- version = @options.get_state(:current_version)
62
+ version = migration_version_for_model(@model)
73
63
  table_name = @model.table_name
74
64
  table_comment = @model.connection.try(:table_comment, @model.table_name)
75
65
  max_size = @model.max_schema_info_width
@@ -78,6 +68,30 @@ module AnnotateRb
78
68
  version: version, table_name: table_name, table_comment: table_comment,
79
69
  max_size: max_size, model: @model).build
80
70
  end
71
+
72
+ private
73
+
74
+ def migration_version_for_model(model)
75
+ return 0 unless @options[:include_version]
76
+
77
+ # Multi-database support: Cache migration versions per database connection to handle
78
+ # different schema versions across primary/secondary databases correctly.
79
+ # Example: primary → "current_version_primary", secondary → "current_version_secondary"
80
+ connection_pool_name = model.connection.pool.db_config.name
81
+ cache_key = "current_version_#{connection_pool_name}".to_sym
82
+
83
+ if @options.get_state(cache_key).nil?
84
+ migration_version = begin
85
+ model.connection.migration_context.current_version
86
+ rescue
87
+ 0
88
+ end
89
+
90
+ @options.set_state(cache_key, migration_version)
91
+ end
92
+
93
+ @options.get_state(cache_key)
94
+ end
81
95
  end
82
96
  end
83
97
  end
@@ -16,29 +16,9 @@ module AnnotateRb
16
16
 
17
17
  begin
18
18
  klass = ModelClassGetter.call(@file, @options)
19
+ return false unless klass.respond_to?(:descends_from_active_record?) && klass.descends_from_active_record? && klass.table_exists?
19
20
 
20
- klass_is_a_class = klass.is_a?(Class)
21
- # Methods such as #superclass only exist on a class. Because of how the code is structured, `klass` could be a
22
- # module that does not support the #superclass method, so we want to return early.
23
- return false if !klass_is_a_class
24
-
25
- klass_inherits_active_record_base = klass < ActiveRecord::Base
26
- klass_is_not_abstract = klass.respond_to?(:abstract_class?) && !klass.abstract_class?
27
- klass_table_exists = klass.respond_to?(:table_exists?) && klass.table_exists?
28
-
29
- not_sure_this_conditional = (!@options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name))
30
-
31
- annotate_conditions = [
32
- klass_is_a_class,
33
- klass_inherits_active_record_base,
34
- not_sure_this_conditional,
35
- klass_is_not_abstract,
36
- klass_table_exists
37
- ]
38
-
39
- to_annotate = annotate_conditions.all?
40
-
41
- return to_annotate
21
+ return @options[:exclude_sti_subclasses] ? klass.base_class? : true
42
22
  rescue BadModelFileError => e
43
23
  unless @options[:ignore_unknown_models]
44
24
  warn "Unable to process #{@file}: #{e.message}"
@@ -55,9 +35,9 @@ module AnnotateRb
55
35
  private
56
36
 
57
37
  def file_contains_skip_annotation
58
- file_string = File.exist?(@file) ? File.read(@file) : ""
38
+ return false unless File.exist?(@file)
59
39
 
60
- /#{SKIP_ANNOTATION_PREFIX}.*/o.match?(file_string)
40
+ /#{SKIP_ANNOTATION_PREFIX}.*/o.match?(File.read(@file))
61
41
  end
62
42
  end
63
43
  end
@@ -11,8 +11,7 @@ module AnnotateRb
11
11
  # - "# status(a/b/c) :string not null"
12
12
  # - "# created_at :datetime not null"
13
13
  # - "# updated_at :datetime not null"
14
- COLUMN_PATTERN = /^#[\t ]+[\w*.`\[\]():]+(?:\(.*?\))?[\t ]+.+$/
15
-
14
+ COLUMN_PATTERN = /^#[\t ]+[[\p{L}\p{N}_]*.`\[\]():]+(?:\(.*?\))?[\t ]+.+$/
16
15
  class << self
17
16
  def call(file_content, annotation_block)
18
17
  new(file_content, annotation_block).generate
@@ -12,42 +12,23 @@ module AnnotateRb
12
12
  end
13
13
 
14
14
  def build
15
- is_primary_key = is_column_primary_key?(@model, @column.name)
16
-
17
- table_indices = @model.retrieve_indexes_from_table
18
- column_indices = table_indices.select { |ind| ind.columns.include?(@column.name) }
19
- column_defaults = @model.column_defaults
20
-
21
- column_attributes = AttributesBuilder.new(@column, @options, is_primary_key, column_indices, column_defaults).build
22
- formatted_column_type = TypeBuilder.new(@column, @options, column_defaults).build
15
+ column_attributes = @model.built_attributes[@column.name]
16
+ formatted_column_type = TypeBuilder.new(@column, @options, @model.column_defaults).build
23
17
 
24
18
  display_column_comments = @options[:with_comment] && @options[:with_column_comments]
25
- col_name = if display_column_comments && @model.with_comments? && @column.comment
26
- "#{@column.name}(#{@column.comment.gsub(/\n/, '\\n')})"
27
- else
28
- @column.name
29
- end
30
-
31
- _component = ColumnComponent.new(col_name, @max_size, formatted_column_type, column_attributes)
32
- end
33
-
34
- private
35
-
36
- # TODO: Simplify this conditional
37
- def is_column_primary_key?(model, column_name)
38
- if model.primary_key
39
- if model.primary_key.is_a?(Array)
40
- # If the model has multiple primary keys, check if this column is one of them
41
- if model.primary_key.collect(&:to_sym).include?(column_name.to_sym)
42
- return true
43
- end
44
- elsif column_name.to_sym == model.primary_key.to_sym
45
- # If model has 1 primary key, check if this column is it
46
- return true
47
- end
48
- end
49
-
50
- false
19
+ display_column_comments &&= @model.with_comments? && @column.comment
20
+ position_of_column_comment = @options[:position_of_column_comment] || Options::FLAG_OPTIONS[:position_of_column_comment] if display_column_comments
21
+
22
+ max_attributes_size = @model.built_attributes.values.map { |v| v.join(", ").length }.max
23
+
24
+ _component = ColumnComponent.new(
25
+ column: @column,
26
+ max_name_size: @max_size,
27
+ type: formatted_column_type,
28
+ attributes: column_attributes,
29
+ position_of_column_comment: position_of_column_comment,
30
+ max_attributes_size: max_attributes_size
31
+ )
51
32
  end
52
33
  end
53
34
  end
@@ -6,19 +6,31 @@ module AnnotateRb
6
6
  class ColumnComponent < Components::Base
7
7
  MD_TYPE_ALLOWANCE = 18
8
8
  BARE_TYPE_ALLOWANCE = 16
9
+ MIN_SPACES_BEFORE_COMMENT = 4
9
10
 
10
- attr_reader :name, :max_size, :type, :attributes
11
+ attr_reader :column, :max_name_size, :type, :attributes, :position_of_column_comment, :max_attributes_size
11
12
 
12
- def initialize(name, max_size, type, attributes)
13
- @name = name
14
- @max_size = max_size
13
+ def initialize(column:, max_name_size:, type:, attributes:, position_of_column_comment:, max_attributes_size:)
14
+ @column = column
15
+ @max_name_size = max_name_size
15
16
  @type = type
16
17
  @attributes = attributes
18
+ @position_of_column_comment = position_of_column_comment
19
+ @max_attributes_size = max_attributes_size
20
+ end
21
+
22
+ def name
23
+ case position_of_column_comment
24
+ when :with_name
25
+ "#{column.name}(#{column.comment.gsub(/\n/, '\\n')})"
26
+ else
27
+ column.name
28
+ end
17
29
  end
18
30
 
19
31
  def to_rdoc
20
32
  # standard:disable Lint/FormatParameterMismatch
21
- format("# %-#{max_size}.#{max_size}s<tt>%s</tt>",
33
+ format("# %-#{max_name_size}.#{max_name_size}s<tt>%s</tt>",
22
34
  "*#{name}*::",
23
35
  attributes.unshift(type).join(", ")).rstrip
24
36
  # standard:enable Lint/FormatParameterMismatch
@@ -40,24 +52,35 @@ module AnnotateRb
40
52
  end
41
53
 
42
54
  def to_markdown
43
- name_remainder = max_size - name.length - non_ascii_length(name)
55
+ joined_attributes = attributes.join(", ").rstrip
56
+ name_remainder = max_name_size - name.length - non_ascii_length(name)
44
57
  type_remainder = (MD_TYPE_ALLOWANCE - 2) - type.length
58
+ attributes_remainder = max_attributes_size + 1 - joined_attributes.length
59
+ comment_rightmost = (position_of_column_comment != :rightmost_column) ? "" : " | `#{@column.comment}`"
45
60
 
46
61
  # standard:disable Lint/FormatParameterMismatch
47
- format("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`",
62
+ format(
63
+ "# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`%#{attributes_remainder}s", # %s",
48
64
  name,
49
65
  " ",
50
66
  type,
51
67
  " ",
52
- attributes.join(", ").rstrip).gsub("``", " ").rstrip
68
+ joined_attributes,
69
+ comment_rightmost.to_s
70
+ ).gsub("``", " ").rstrip
53
71
  # standard:enable Lint/FormatParameterMismatch
54
72
  end
55
73
 
56
74
  def to_default
57
- format("# %s:%s %s",
58
- mb_chars_ljust(name, max_size),
75
+ comment_rightmost = (position_of_column_comment == :rightmost_column) ? @column.comment : ""
76
+ joined_attributes = attributes.join(", ")
77
+ format(
78
+ "# %s:%s %s %s",
79
+ mb_chars_ljust(name, max_name_size),
59
80
  mb_chars_ljust(type, BARE_TYPE_ALLOWANCE),
60
- attributes.join(", ")).rstrip
81
+ mb_chars_ljust(joined_attributes, max_attributes_size.to_i + MIN_SPACES_BEFORE_COMMENT),
82
+ comment_rightmost
83
+ ).rstrip
61
84
  end
62
85
 
63
86
  private
@@ -18,7 +18,7 @@ module AnnotateRb
18
18
  max_size = indexes.map { |index| index.name.size }.max + 1
19
19
 
20
20
  indexes = indexes.sort_by(&:name).map do |index|
21
- IndexComponent.new(index, max_size)
21
+ IndexComponent.new(index, max_size, @options)
22
22
  end
23
23
 
24
24
  _annotation = Annotation.new(indexes)
@@ -4,11 +4,12 @@ module AnnotateRb
4
4
  module ModelAnnotator
5
5
  module IndexAnnotation
6
6
  class IndexComponent < Components::Base
7
- attr_reader :index, :max_size
7
+ attr_reader :index, :max_size, :options
8
8
 
9
- def initialize(index, max_size)
9
+ def initialize(index, max_size, options)
10
10
  @index = index
11
11
  @max_size = max_size
12
+ @options = options
12
13
  end
13
14
 
14
15
  def to_default
@@ -34,11 +35,22 @@ module AnnotateRb
34
35
  ""
35
36
  end
36
37
 
38
+ include_info = ""
39
+ if options[:show_indexes_include]
40
+ value = index.try(:include)
41
+ include_info = if value.present? && value.any?
42
+ " INCLUDE (#{value.join(",")})"
43
+ else
44
+ ""
45
+ end
46
+ end
47
+
37
48
  # standard:disable Lint/FormatParameterMismatch
38
49
  sprintf(
39
- "# %-#{max_size}.#{max_size}s %s%s%s%s%s",
50
+ "# %-#{max_size}.#{max_size}s %s%s%s%s%s%s",
40
51
  index.name,
41
52
  "(#{columns_info.join(",")})",
53
+ include_info,
42
54
  unique_info,
43
55
  nulls_not_distinct_info,
44
56
  where_info,
@@ -70,8 +82,19 @@ module AnnotateRb
70
82
  ""
71
83
  end
72
84
 
85
+ include_info = ""
86
+ if options[:show_indexes_include]
87
+ value = index.try(:include)
88
+ include_info = if value.present? && value.any?
89
+ " _include_ (#{value.join(",")})"
90
+ else
91
+ ""
92
+ end
93
+ end
94
+
73
95
  details = sprintf(
74
- "%s%s%s%s",
96
+ "%s%s%s%s%s",
97
+ include_info,
75
98
  unique_info,
76
99
  nulls_not_distinct_info,
77
100
  where_info,
@@ -23,12 +23,12 @@ module AnnotateRb
23
23
  ignore_columns = @options[:ignore_columns]
24
24
  if ignore_columns
25
25
  cols = cols.reject do |col|
26
- col.name.match(/#{ignore_columns}/)
26
+ col.name.match?(/#{ignore_columns}/)
27
27
  end
28
28
  end
29
29
 
30
30
  cols = cols.sort_by(&:name) if @options[:sort]
31
- cols = classified_sort(cols) if @options[:classified_sort]
31
+ cols = classified_sort(cols, @options[:grouped_polymorphic]) if @options[:classified_sort]
32
32
 
33
33
  cols
34
34
  end
@@ -91,7 +91,8 @@ module AnnotateRb
91
91
  begin
92
92
  cols = columns
93
93
 
94
- if with_comments?
94
+ position_of_column_comment = @options.with_default_fallback(:position_of_column_comment)
95
+ if with_comments? && position_of_column_comment == :with_name
95
96
  column_widths = cols.map do |column|
96
97
  column.name.size + (column.comment ? Helper.width(column.comment) : 0)
97
98
  end
@@ -108,6 +109,35 @@ module AnnotateRb
108
109
  end
109
110
  end
110
111
 
112
+ # TODO: Simplify this conditional
113
+ def is_column_primary_key?(column_name)
114
+ if primary_key
115
+ if primary_key.is_a?(Array)
116
+ # If the model has multiple primary keys, check if this column is one of them
117
+ if primary_key.collect(&:to_sym).include?(column_name.to_sym)
118
+ return true
119
+ end
120
+ elsif column_name.to_sym == primary_key.to_sym
121
+ # If model has 1 primary key, check if this column is it
122
+ return true
123
+ end
124
+ end
125
+
126
+ false
127
+ end
128
+
129
+ def built_attributes
130
+ @built_attributes ||= begin
131
+ table_indices = retrieve_indexes_from_table
132
+ columns.map do |column|
133
+ is_primary_key = is_column_primary_key?(column.name)
134
+ column_indices = table_indices.select { |ind| ind.columns.include?(column.name) }
135
+ built = ColumnAnnotation::AttributesBuilder.new(column, @options, is_primary_key, column_indices, column_defaults).build
136
+ [column.name, built]
137
+ end.to_h
138
+ end
139
+ end
140
+
111
141
  def retrieve_indexes_from_table
112
142
  @indexes_from_table ||= _retrieve_indexes_from_table
113
143
  end
@@ -143,7 +173,11 @@ module AnnotateRb
143
173
  raw_columns.map(&:comment).any? { |comment| !comment.nil? }
144
174
  end
145
175
 
146
- def classified_sort(cols)
176
+ def position_of_column_comment
177
+ @position_of_column_comment ||= @options[:position_of_column_comment]
178
+ end
179
+
180
+ def classified_sort(cols, grouped_polymorphic)
147
181
  rest_cols = []
148
182
  timestamps = []
149
183
  associations = []
@@ -152,6 +186,8 @@ module AnnotateRb
152
186
  # specs don't load defaults, so ensure we have defaults here
153
187
  timestamp_columns = @options[:timestamp_columns] || DEFAULT_TIMESTAMP_COLUMNS
154
188
 
189
+ col_names = cols.map(&:name)
190
+
155
191
  cols.each do |c|
156
192
  if c.name.eql?("id")
157
193
  id = c
@@ -159,6 +195,9 @@ module AnnotateRb
159
195
  timestamps << c
160
196
  elsif c.name[-3, 3].eql?("_id")
161
197
  associations << c
198
+ elsif c.name[-5, 5].eql?("_type") && col_names.include?(c.name.sub(/_type$/, "_id")) && grouped_polymorphic
199
+ # This is a polymorphic association's type column
200
+ associations << c
162
201
  else
163
202
  rest_cols << c
164
203
  end
@@ -42,7 +42,11 @@ module AnnotateRb
42
42
  klass.reset_column_information
43
43
  annotation = Annotation::AnnotationBuilder.new(klass, @options).build
44
44
  model_name = klass.name.underscore
45
- table_name = klass.table_name
45
+
46
+ # In multi-database configurations, it is possible for different models to have the same table name but live
47
+ # in different databases. Here we are opting to use the table name to find related files only when the model
48
+ # is using the primary connection.
49
+ table_name = klass.table_name if klass.connection_specification_name == ActiveRecord::Base.name
46
50
 
47
51
  model_instruction = SingleFileAnnotatorInstruction.new(file, annotation, :position_in_class, @options)
48
52
  instructions << model_instruction
@@ -41,6 +41,7 @@ module AnnotateRb
41
41
  format_rdoc: false, # ModelAnnotator
42
42
  format_yard: false, # ModelAnnotator
43
43
  frozen: false, # ModelAnnotator, but should be used by both
44
+ grouped_polymorphic: false, # ModelAnnotator
44
45
  ignore_model_sub_dir: false, # ModelAnnotator
45
46
  ignore_unknown_models: false, # ModelAnnotator
46
47
  include_version: false, # ModelAnnotator
@@ -48,6 +49,7 @@ module AnnotateRb
48
49
  show_check_constraints: false, # ModelAnnotator
49
50
  show_foreign_keys: true, # ModelAnnotator
50
51
  show_indexes: true, # ModelAnnotator
52
+ show_indexes_include: false, # ModelAnnotator
51
53
  show_virtual_columns: false, # ModelAnnotator
52
54
  simple_indexes: false, # ModelAnnotator
53
55
  sort: false, # ModelAnnotator
@@ -55,7 +57,8 @@ module AnnotateRb
55
57
  trace: false, # ModelAnnotator, but is part of Core
56
58
  with_comment: true, # ModelAnnotator
57
59
  with_column_comments: nil, # ModelAnnotator
58
- with_table_comments: nil # ModelAnnotator
60
+ with_table_comments: nil, # ModelAnnotator
61
+ position_of_column_comment: :with_name # ModelAnnotator
59
62
  }.freeze
60
63
 
61
64
  OTHER_OPTIONS = {
@@ -111,6 +114,7 @@ module AnnotateRb
111
114
  :format_rdoc,
112
115
  :format_yard,
113
116
  :frozen,
117
+ :grouped_polymorphic,
114
118
  :ignore_model_sub_dir,
115
119
  :ignore_unknown_models,
116
120
  :include_version,
@@ -118,13 +122,15 @@ module AnnotateRb
118
122
  :show_complete_foreign_keys,
119
123
  :show_foreign_keys,
120
124
  :show_indexes,
125
+ :show_indexes_include,
121
126
  :simple_indexes,
122
127
  :sort,
123
128
  :timestamp,
124
129
  :trace,
125
130
  :with_comment,
126
131
  :with_column_comments,
127
- :with_table_comments
132
+ :with_table_comments,
133
+ :position_of_column_comment
128
134
  ].freeze
129
135
 
130
136
  OTHER_OPTION_KEYS = [
@@ -205,10 +211,15 @@ module AnnotateRb
205
211
  # Set column and table comments to default to :with_comment, if not set
206
212
  @options[:with_column_comments] = @options[:with_comment] if @options[:with_column_comments].nil?
207
213
  @options[:with_table_comments] = @options[:with_comment] if @options[:with_table_comments].nil?
214
+ @options[:position_of_column_comment] = @options[:position_of_column_comment].to_sym
208
215
 
209
216
  self
210
217
  end
211
218
 
219
+ def with_default_fallback(key)
220
+ @options[key] || DEFAULT_OPTIONS[key]
221
+ end
222
+
212
223
  def set_state(key, value, overwrite = false)
213
224
  if @state.key?(key) && !overwrite
214
225
  val = @state[key]
@@ -246,6 +246,11 @@ module AnnotateRb
246
246
  @options[:with_column_comments] = false
247
247
  end
248
248
 
249
+ option_parser.on("--position-of-column-comments VALUE",
250
+ "set the position, in the annotation block, of the column comment") do |value|
251
+ @options[:position_of_column_comments] = value.to_sym
252
+ end
253
+
249
254
  option_parser.on("--with-table-comments",
250
255
  "include table comments in model annotations") do
251
256
  @options[:with_table_comments] = true
@@ -402,6 +407,11 @@ module AnnotateRb
402
407
  @options[:classified_sort] = true
403
408
  end
404
409
 
410
+ option_parser.on("--grouped-polymorphic",
411
+ "Group polymorphic associations together in the annotation when using --classified-sort") do
412
+ @options[:grouped_polymorphic] = true
413
+ end
414
+
405
415
  option_parser.on("-R",
406
416
  "--require path",
407
417
  "Additional file to require before loading models, may be used multiple times") do |path|
@@ -3,7 +3,7 @@
3
3
  module AnnotateRb
4
4
  module RouteAnnotator
5
5
  module Helper
6
- MAGIC_COMMENT_MATCHER = /(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/.freeze
6
+ MAGIC_COMMENT_MATCHER = /(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/
7
7
 
8
8
  class << self
9
9
  def routes_file_exist?
@@ -3,9 +3,23 @@
3
3
  module AnnotateRb
4
4
  class Runner
5
5
  class << self
6
+ attr_reader :runner
7
+
6
8
  def run(args)
7
- new.run(args)
9
+ self.runner = new
10
+
11
+ runner.run(args)
12
+
13
+ self.runner = nil
8
14
  end
15
+
16
+ def running?
17
+ !!runner
18
+ end
19
+
20
+ private
21
+
22
+ attr_writer :runner
9
23
  end
10
24
 
11
25
  def run(args)
@@ -18,7 +18,7 @@ if defined?(Rails::Application) && Rails.version.split(".").first.to_i >= 6
18
18
 
19
19
  # If there's multiple databases, this appends database specific rake tasks to `migration_tasks`
20
20
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database_name|
21
- migration_tasks.concat(%w[db:migrate db:migrate:up db:migrate:down].map { |task| "#{task}:#{database_name}" })
21
+ migration_tasks.concat(%w[db:migrate db:migrate:up db:migrate:down db:rollback].map { |task| "#{task}:#{database_name}" })
22
22
  end
23
23
  end
24
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: annotaterb
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.16.0
4
+ version: 4.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew W. Lee
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-18 00:00:00.000000000 Z
11
+ date: 2025-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord