Skip to content

Commit d9c9214

Browse files
authored
Fix classified_sort to group polymorphic association columns together (#238)
This PR fixes the `classified_sort` option to properly group polymorphic association columns together. Previously, polymorphic `_type` columns were sorted with regular columns while their corresponding `_id` columns were in the associations section. I modified the `classified_sort` method in `ModelWrapper` to: 1. Collect all column names in a first pass 2. Check if columns ending with `_type` have a corresponding `_id` column 3. If found, treat the `_type` column as an association column ## Example Before this fix: ```ruby # id :uuid not null, primary key # amount_cents :decimal(10, 6) # artifact_type :string # <-- separated from artifact_id # model :string not null # provider :string not null # usage :jsonb not null # created_at :datetime not null # updated_at :datetime not null # account_id :uuid not null # artifact_id :uuid # <-- in associations section # user_id :uuid ``` After this fix: ```ruby # id :uuid not null, primary key # amount_cents :decimal(10, 6) # model :string not null # provider :string not null # usage :jsonb not null # created_at :datetime not null # updated_at :datetime not null # account_id :uuid not null # artifact_id :uuid # <-- grouped together # artifact_type :string # <-- grouped together # user_id :uuid ``` Fixes #236
1 parent 954a048 commit d9c9214

File tree

5 files changed

+118
-7
lines changed

5 files changed

+118
-7
lines changed

README.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
## AnnotateRb
2+
23
### forked from the [Annotate aka AnnotateModels gem](https://github.com/ctran/annotate_models)
34

45
A Ruby Gem that adds annotations to your Rails models and route files.
56

6-
----------
7+
---
8+
79
[![CI](https://github.com/drwl/annotaterb/actions/workflows/ci.yml/badge.svg)](https://github.com/drwl/annotaterb/actions/workflows/ci.yml)
810
[![Gem Version](https://badge.fury.io/rb/annotaterb.svg)](https://badge.fury.io/rb/annotaterb)
911

@@ -32,7 +34,9 @@ The schema comment looks like this:
3234
class Task < ApplicationRecord
3335
...
3436
```
35-
----------
37+
38+
---
39+
3640
## Installation
3741

3842
```sh
@@ -46,11 +50,12 @@ group :development do
4650
...
4751

4852
gem "annotaterb"
49-
53+
5054
...
5155
```
5256

5357
### Automatically annotate models
58+
5459
For Rails projects, model files can get automatically annotated after migration tasks. To do this, run the following command:
5560

5661
```sh
@@ -66,6 +71,7 @@ $ ANNOTATERB_SKIP_ON_DB_TASKS=1 bin/rails db:migrate
6671
```
6772

6873
### Added Rails generators
74+
6975
The following Rails generator commands get added.
7076

7177
```sh
@@ -83,18 +89,23 @@ AnnotateRb:
8389
```
8490

8591
`bin/rails g annotate_rb:config`
92+
8693
- Generates a new configuration file, `.annotaterb.yml`, using defaults from the gem.
8794

8895
`bin/rails g annotate_rb:hook`
96+
8997
- 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`).
9098

9199
`bin/rails g annotate_rb:install`
100+
92101
- Runs the `config` and `hook` generator commands
93102

94103
`bin/rails g annotate_rb:update_config`
104+
95105
- 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).
96106

97107
## Migrating from the annotate gem
108+
98109
Refer to the [migration guide](MIGRATION_GUIDE.md).
99110

100111
## Usage
@@ -103,7 +114,7 @@ AnnotateRb has a CLI that you can use to add or remove annotations.
103114

104115
```sh
105116
# To show the CLI options
106-
$ bundle exec annotaterb
117+
$ bundle exec annotaterb
107118

108119
Usage: annotaterb [command] [options]
109120

@@ -127,14 +138,23 @@ Annotate model options:
127138
Complete foreign key names in the annotation
128139
-i, --show-indexes List the table's database indexes in the annotation
129140
-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
130142
--hide-limit-column-types VALUES
131143
don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)
132144
--hide-default-column-types VALUES
133145
don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)
134146
--ignore-unknown-models don't display warnings for bad model files
135147
-I, --ignore-columns REGEX don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`
136148
--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.
138158
139159
Annotate routes options:
140160
Usage: annotaterb routes [options]
@@ -157,6 +177,7 @@ Additional options that work for annotating models and routes
157177
--ignore-model-subdirects Ignore subdirectories of the models directory
158178
--sort Sort columns alphabetically, rather than in creation order
159179
--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
160181
-R, --require path Additional file to require before loading models, may be used multiple times
161182
-e [tests,fixtures,factories,serializers],
162183
--exclude Do not annotate fixtures, test files, factories, and/or serializers
@@ -176,6 +197,8 @@ Additional options that work for annotating models and routes
176197
Place the annotations at the top (before) or the bottom (after) of the routes.rb file
177198
--ps, --position-in-serializer [before|top|after|bottom]
178199
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
179202
--force Force new annotations even if there are no changes.
180203
--debug Prints the options and outputs messages to make it easier to debug.
181204
--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
185208
## Configuration
186209
187210
### Storing default options
211+
188212
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.
189213
190214
```yml
@@ -198,6 +222,7 @@ Annotaterb reads first the configuration file, if it exists, passes its content
198222
For further details visit the [section in the migration guide](MIGRATION_GUIDE.md#automatic-annotations-after-running-database-migration-commands).
199223
200224
### How to skip annotating a particular model
225+
201226
If you want to always skip annotations on a particular model, add this string
202227
anywhere in the file:
203228
@@ -211,6 +236,10 @@ migrations were run).
211236
If you prefer to sort alphabetically so that the results of annotation are
212237
consistent regardless of what order migrations are executed in, use `--sort`.
213238
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+
214243
## License
215244
216245
Released under the same license as Ruby. No Support. No Warranty.

lib/annotate_rb/model_annotator/model_wrapper.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def columns
2828
end
2929

3030
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]
3232

3333
cols
3434
end
@@ -177,7 +177,7 @@ def position_of_column_comment
177177
@position_of_column_comment ||= @options[:position_of_column_comment]
178178
end
179179

180-
def classified_sort(cols)
180+
def classified_sort(cols, grouped_polymorphic)
181181
rest_cols = []
182182
timestamps = []
183183
associations = []
@@ -186,13 +186,18 @@ def classified_sort(cols)
186186
# specs don't load defaults, so ensure we have defaults here
187187
timestamp_columns = @options[:timestamp_columns] || DEFAULT_TIMESTAMP_COLUMNS
188188

189+
col_names = cols.map(&:name)
190+
189191
cols.each do |c|
190192
if c.name.eql?("id")
191193
id = c
192194
elsif timestamp_columns.include?(c.name)
193195
timestamps << c
194196
elsif c.name[-3, 3].eql?("_id")
195197
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
196201
else
197202
rest_cols << c
198203
end

lib/annotate_rb/options.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def from(options = {}, state = {})
4141
format_rdoc: false, # ModelAnnotator
4242
format_yard: false, # ModelAnnotator
4343
frozen: false, # ModelAnnotator, but should be used by both
44+
grouped_polymorphic: false, # ModelAnnotator
4445
ignore_model_sub_dir: false, # ModelAnnotator
4546
ignore_unknown_models: false, # ModelAnnotator
4647
include_version: false, # ModelAnnotator
@@ -113,6 +114,7 @@ def from(options = {}, state = {})
113114
:format_rdoc,
114115
:format_yard,
115116
:frozen,
117+
:grouped_polymorphic,
116118
:ignore_model_sub_dir,
117119
:ignore_unknown_models,
118120
:include_version,

lib/annotate_rb/parser.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ def add_options_to_parser(option_parser) # rubocop:disable Metrics/MethodLength
407407
@options[:classified_sort] = true
408408
end
409409

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+
410415
option_parser.on("-R",
411416
"--require path",
412417
"Additional file to require before loading models, may be used multiple times") do |path|

spec/lib/annotate_rb/model_annotator/annotation/annotation_builder_spec.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,76 @@
236236
end
237237
end
238238
end
239+
240+
context "when polymorphic associations are present" do
241+
let(:columns) do
242+
[
243+
mock_column("id", :uuid),
244+
mock_column("account_id", :uuid),
245+
mock_column("user_id", :uuid),
246+
mock_column("artifact_type", :string),
247+
mock_column("artifact_id", :uuid),
248+
mock_column("provider", :string),
249+
mock_column("model", :string),
250+
mock_column("created_at", :datetime),
251+
mock_column("updated_at", :datetime)
252+
]
253+
end
254+
255+
context "when grouped_polymorphic is false" do
256+
let(:options) do
257+
AnnotateRb::Options.new(classified_sort: true, grouped_polymorphic: false)
258+
end
259+
260+
it "places polymorphic _type columns together with their _id columns in associations section" do
261+
expected = <<~EOS
262+
# == Schema Information
263+
#
264+
# Table name: users
265+
#
266+
# id :uuid not null, primary key
267+
# artifact_type :string not null
268+
# model :string not null
269+
# provider :string not null
270+
# created_at :datetime not null
271+
# updated_at :datetime not null
272+
# account_id :uuid not null
273+
# artifact_id :uuid not null
274+
# user_id :uuid not null
275+
#
276+
EOS
277+
278+
is_expected.to eq expected
279+
end
280+
end
281+
282+
context "when grouped_polymorphic is true" do
283+
let(:options) do
284+
AnnotateRb::Options.new(classified_sort: true, grouped_polymorphic: true)
285+
end
286+
287+
it "sorts polymorphic _type columns with the other columns" do
288+
expected = <<~EOS
289+
# == Schema Information
290+
#
291+
# Table name: users
292+
#
293+
# id :uuid not null, primary key
294+
# model :string not null
295+
# provider :string not null
296+
# created_at :datetime not null
297+
# updated_at :datetime not null
298+
# account_id :uuid not null
299+
# artifact_id :uuid not null
300+
# artifact_type :string not null
301+
# user_id :uuid not null
302+
#
303+
EOS
304+
305+
is_expected.to eq expected
306+
end
307+
end
308+
end
239309
end
240310

241311
context "when geometry columns are included" do

0 commit comments

Comments
 (0)