annotaterb 4.0.0 → 4.1.1
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 +4 -4
- data/README.md +4 -5
- data/VERSION +1 -1
- data/lib/annotate_rb/model_annotator/annotation_decider.rb +62 -0
- data/lib/annotate_rb/model_annotator/annotation_diff.rb +19 -0
- data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +44 -0
- data/lib/annotate_rb/model_annotator/annotation_generator.rb +94 -0
- data/lib/annotate_rb/model_annotator/annotator.rb +27 -31
- data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +92 -0
- data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +102 -0
- data/lib/annotate_rb/model_annotator/column_type_builder.rb +51 -0
- data/lib/annotate_rb/model_annotator/column_wrapper.rb +84 -0
- data/lib/annotate_rb/model_annotator/constants.rb +2 -2
- data/lib/annotate_rb/model_annotator/file_annotator.rb +9 -11
- data/lib/annotate_rb/model_annotator/file_annotator_instruction.rb +17 -0
- data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +55 -0
- data/lib/annotate_rb/model_annotator/helper.rb +75 -22
- data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +74 -0
- data/lib/annotate_rb/model_annotator/model_file_annotator.rb +21 -84
- data/lib/annotate_rb/model_annotator/model_files_getter.rb +4 -2
- data/lib/annotate_rb/model_annotator/model_wrapper.rb +155 -0
- data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +137 -0
- data/lib/annotate_rb/model_annotator.rb +13 -1
- data/lib/annotate_rb/options.rb +16 -9
- data/lib/annotate_rb/parser.rb +0 -14
- data/lib/annotate_rb.rb +0 -1
- metadata +15 -4
- data/lib/annotate_rb/env.rb +0 -30
- data/lib/annotate_rb/model_annotator/schema_info.rb +0 -480
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8888511eb71e6d0c45eadf9f562767868adf7cb9f2c5c58d6eb3458a65dfdc31
|
4
|
+
data.tar.gz: 51d25d56e5068979a09d4562e7018b0272f8f73e952941a4f66faece7408cc11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b878bfc48836e52fe762c547870b67619427772e54efd28835e8699371dc60fdb5511649d9f2bf39431541a9b07f8e32188469526dd109553f71badedd6ec45
|
7
|
+
data.tar.gz: df69b71c4c10cc046ebd6cf0a21391262b90e0bcffe19556b59214d7658623fa323a7adbf24e711726bb685377e47596d9b95fed9c2425f22f4877264eed97d8
|
data/README.md
CHANGED
@@ -60,10 +60,7 @@ $ bin/rails g annotate_rb:install
|
|
60
60
|
This will copy a rake task into your Rails project's `lib/tasks` directory that will hook into the Rails project rake tasks, automatically running AnnotateRb after database migration rake tasks.
|
61
61
|
|
62
62
|
## Migrating from the annotate gem
|
63
|
-
|
64
|
-
|
65
|
-
* Remove the following files `lib/tasks/annotate_models.rake`, `lib/tasks/annotate_models_migrate.rake`, `lib/tasks/annotate_routes.rake`.
|
66
|
-
* Run the generator install command above.
|
63
|
+
Refer to the [migration guide](MIGRATION_GUIDE.md).
|
67
64
|
|
68
65
|
## Usage
|
69
66
|
|
@@ -160,7 +157,9 @@ Previously in the [Annotate](https://github.com/ctran/annotate_models) you could
|
|
160
157
|
position: after
|
161
158
|
```
|
162
159
|
|
163
|
-
Annotaterb reads first from the configuration file, if it exists, then merges it with any options passed into the CLI.
|
160
|
+
Annotaterb reads first from the configuration file, if it exists, then merges it with any options passed into the CLI.
|
161
|
+
|
162
|
+
For further details visit the [section in the migration guide](MIGRATION_GUIDE.md#automatic-annotations-after-running-database-migration-commands).
|
164
163
|
|
165
164
|
### How to skip annotating a particular model
|
166
165
|
If you want to always skip annotations on a particular model, add this string
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.
|
1
|
+
4.1.1
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Class that encapsulates the logic to decide whether to annotate a model file and its related files or not.
|
6
|
+
class AnnotationDecider
|
7
|
+
def initialize(file, options)
|
8
|
+
@file = file
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def annotate?
|
13
|
+
return false if file_contains_skip_annotation
|
14
|
+
|
15
|
+
begin
|
16
|
+
klass = ModelClassGetter.call(@file, @options)
|
17
|
+
|
18
|
+
klass_is_a_class = klass.is_a?(Class)
|
19
|
+
klass_inherits_active_record_base = klass < ActiveRecord::Base
|
20
|
+
klass_is_not_abstract = klass.respond_to?(:abstract_class) && !klass.abstract_class?
|
21
|
+
klass_table_exists = klass.respond_to?(:abstract_class) && klass.table_exists?
|
22
|
+
|
23
|
+
not_sure_this_conditional = (!@options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name))
|
24
|
+
|
25
|
+
annotate_conditions = [
|
26
|
+
klass_is_a_class,
|
27
|
+
klass_inherits_active_record_base,
|
28
|
+
not_sure_this_conditional,
|
29
|
+
klass_is_not_abstract,
|
30
|
+
klass_table_exists
|
31
|
+
]
|
32
|
+
|
33
|
+
to_annotate = annotate_conditions.all?
|
34
|
+
|
35
|
+
return to_annotate
|
36
|
+
rescue BadModelFileError => e
|
37
|
+
unless @options[:ignore_unknown_models]
|
38
|
+
$stderr.puts "Unable to annotate #{@file}: #{e.message}"
|
39
|
+
$stderr.puts "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
$stderr.puts "Unable to annotate #{@file}: #{e.message}"
|
43
|
+
$stderr.puts "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
44
|
+
end
|
45
|
+
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def file_contains_skip_annotation
|
52
|
+
file_string = File.exist?(@file) ? File.read(@file) : ''
|
53
|
+
|
54
|
+
if /#{Constants::SKIP_ANNOTATION_PREFIX}.*/ =~ file_string
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Plain old Ruby object for holding the differences
|
6
|
+
class AnnotationDiff
|
7
|
+
attr_reader :current_columns, :new_columns
|
8
|
+
|
9
|
+
def initialize(current_columns, new_columns)
|
10
|
+
@current_columns = current_columns.dup.freeze
|
11
|
+
@new_columns = new_columns.dup.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def changed?
|
15
|
+
@changed ||= @current_columns != @new_columns
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Compares the current file content and new annotation block and generates the column annotation differences
|
6
|
+
class AnnotationDiffGenerator
|
7
|
+
HEADER_PATTERN = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/.freeze
|
8
|
+
COLUMN_PATTERN = /^#[\t ]+[\w\*\.`\[\]():]+[\t ]+.+$/.freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def call(file_content, annotation_block)
|
12
|
+
new(file_content, annotation_block).generate
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [String] file_content The current file content of the model file we intend to annotate
|
17
|
+
# @param [String] annotation_block The annotation block we intend to write to the model file
|
18
|
+
def initialize(file_content, annotation_block)
|
19
|
+
@file_content = file_content
|
20
|
+
@annotation_block = annotation_block
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate
|
24
|
+
# Ignore the Schema version line because it changes with each migration
|
25
|
+
current_annotations = @file_content.match(HEADER_PATTERN).to_s
|
26
|
+
new_annotations = @annotation_block.match(HEADER_PATTERN).to_s
|
27
|
+
|
28
|
+
if current_annotations.present?
|
29
|
+
current_annotation_columns = current_annotations.scan(COLUMN_PATTERN).sort
|
30
|
+
else
|
31
|
+
current_annotation_columns = []
|
32
|
+
end
|
33
|
+
|
34
|
+
if new_annotations.present?
|
35
|
+
new_annotation_columns = new_annotations.scan(COLUMN_PATTERN).sort
|
36
|
+
else
|
37
|
+
new_annotation_columns = []
|
38
|
+
end
|
39
|
+
|
40
|
+
_result = AnnotationDiff.new(current_annotation_columns, new_annotation_columns)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class AnnotationGenerator
|
6
|
+
# Annotate Models plugin use this header
|
7
|
+
PREFIX = '== Schema Information'.freeze
|
8
|
+
PREFIX_MD = '## Schema Information'.freeze
|
9
|
+
|
10
|
+
END_MARK = '== Schema Information End'.freeze
|
11
|
+
|
12
|
+
MD_NAMES_OVERHEAD = 6
|
13
|
+
MD_TYPE_ALLOWANCE = 18
|
14
|
+
|
15
|
+
def initialize(klass, options = {})
|
16
|
+
@model = ModelWrapper.new(klass, options)
|
17
|
+
@options = options
|
18
|
+
@info = "" # TODO: Make array and build string that way
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate
|
22
|
+
@info = "# #{header}\n"
|
23
|
+
@info += schema_header_text
|
24
|
+
|
25
|
+
max_size = @model.max_schema_info_width
|
26
|
+
|
27
|
+
if @options[:format_markdown]
|
28
|
+
@info += format("# %-#{max_size + MD_NAMES_OVERHEAD}.#{max_size + MD_NAMES_OVERHEAD}s | %-#{MD_TYPE_ALLOWANCE}.#{MD_TYPE_ALLOWANCE}s | %s\n",
|
29
|
+
'Name',
|
30
|
+
'Type',
|
31
|
+
'Attributes')
|
32
|
+
@info += "# #{'-' * (max_size + MD_NAMES_OVERHEAD)} | #{'-' * MD_TYPE_ALLOWANCE} | #{'-' * 27}\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
@info += @model.columns.map do |col|
|
36
|
+
ColumnAnnotationBuilder.new(col, @model, max_size, @options).build
|
37
|
+
end.join
|
38
|
+
|
39
|
+
if @options[:show_indexes] && @model.table_exists?
|
40
|
+
@info += IndexAnnotationBuilder.new(@model, @options).build
|
41
|
+
end
|
42
|
+
|
43
|
+
if @options[:show_foreign_keys] && @model.table_exists?
|
44
|
+
@info += ForeignKeyAnnotationBuilder.new(@model, @options).build
|
45
|
+
end
|
46
|
+
|
47
|
+
@info += schema_footer_text
|
48
|
+
|
49
|
+
@info
|
50
|
+
end
|
51
|
+
|
52
|
+
def header
|
53
|
+
header = @options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
|
54
|
+
version = ActiveRecord::Migrator.current_version rescue 0
|
55
|
+
|
56
|
+
if @options[:include_version] && version > 0
|
57
|
+
header += "\n# Schema version: #{version}"
|
58
|
+
end
|
59
|
+
|
60
|
+
header
|
61
|
+
end
|
62
|
+
|
63
|
+
def schema_header_text
|
64
|
+
info = []
|
65
|
+
info << "#"
|
66
|
+
|
67
|
+
if @options[:format_markdown]
|
68
|
+
info << "# Table name: `#{@model.table_name}`"
|
69
|
+
info << "#"
|
70
|
+
info << "# ### Columns"
|
71
|
+
else
|
72
|
+
info << "# Table name: #{@model.table_name}"
|
73
|
+
end
|
74
|
+
info << "#\n" # We want the last line break
|
75
|
+
|
76
|
+
info.join("\n")
|
77
|
+
end
|
78
|
+
|
79
|
+
def schema_footer_text
|
80
|
+
info = []
|
81
|
+
|
82
|
+
if @options[:format_rdoc]
|
83
|
+
info << "#--"
|
84
|
+
info << "# #{END_MARK}"
|
85
|
+
info << "#++\n"
|
86
|
+
else
|
87
|
+
info << "#\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
info.join("\n")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,31 +1,20 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnnotateRb
|
4
4
|
module ModelAnnotator
|
5
5
|
class Annotator
|
6
|
-
# Annotate Models plugin use this header
|
7
|
-
PREFIX = '== Schema Information'.freeze
|
8
|
-
PREFIX_MD = '## Schema Information'.freeze
|
9
|
-
|
10
|
-
MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*(?:\n|r\n))|(^# coding:.*(?:\n|\r\n))|(^# -\*- coding:.*(?:\n|\r\n))|(^# -\*- encoding\s?:.*(?:\n|\r\n))|(^#\s*frozen_string_literal:.+(?:\n|\r\n))|(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/).freeze
|
11
|
-
|
12
6
|
class << self
|
13
|
-
# We're passed a name of things that might be
|
14
|
-
# ActiveRecord models. If we can find the class, and
|
15
|
-
# if its a subclass of ActiveRecord::Base,
|
16
|
-
# then pass it to the associated block
|
17
7
|
def do_annotations(options = {})
|
18
|
-
header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
|
19
|
-
version = ActiveRecord::Migrator.current_version rescue 0
|
20
|
-
if options[:include_version] && version > 0
|
21
|
-
header << "\n# Schema version: #{version}"
|
22
|
-
end
|
23
|
-
|
24
8
|
annotated = []
|
25
|
-
model_files_to_annotate = ModelFilesGetter.call(options)
|
26
9
|
|
27
|
-
|
28
|
-
|
10
|
+
model_files_to_consider = ModelFilesGetter.call(options)
|
11
|
+
|
12
|
+
model_files_to_consider.each do |path, filename|
|
13
|
+
file = File.join(path, filename)
|
14
|
+
|
15
|
+
if AnnotationDecider.new(file, options).annotate?
|
16
|
+
ModelFileAnnotator.call(annotated, file, options)
|
17
|
+
end
|
29
18
|
end
|
30
19
|
|
31
20
|
if annotated.empty?
|
@@ -37,34 +26,41 @@ module AnnotateRb
|
|
37
26
|
|
38
27
|
def remove_annotations(options = {})
|
39
28
|
deannotated = []
|
40
|
-
|
41
|
-
ModelFilesGetter.call(options)
|
42
|
-
|
29
|
+
|
30
|
+
model_files_to_consider = ModelFilesGetter.call(options)
|
31
|
+
|
32
|
+
model_files_to_consider.each do |path, filename|
|
33
|
+
deannotated_klass = false
|
34
|
+
file = File.join(path, filename)
|
35
|
+
|
43
36
|
begin
|
44
37
|
klass = ModelClassGetter.call(file, options)
|
45
38
|
if klass < ActiveRecord::Base && !klass.abstract_class?
|
46
39
|
model_name = klass.name.underscore
|
47
40
|
table_name = klass.table_name
|
48
|
-
model_file_name = file
|
49
|
-
deannotated_klass = true if FileAnnotationRemover.call(model_file_name, options)
|
50
41
|
|
51
|
-
|
42
|
+
if FileAnnotationRemover.call(file, options)
|
43
|
+
deannotated_klass = true
|
44
|
+
end
|
45
|
+
|
46
|
+
related_files = RelatedFilesListBuilder.new(file, model_name, table_name, options).build
|
52
47
|
|
53
|
-
|
54
|
-
.map { |f| FileNameResolver.call(f, model_name, table_name) }
|
55
|
-
.each do |f|
|
48
|
+
related_files.each do |f, _position_key|
|
56
49
|
if File.exist?(f)
|
57
50
|
FileAnnotationRemover.call(f, options)
|
58
|
-
deannotated_klass = true
|
59
51
|
end
|
60
52
|
end
|
61
53
|
end
|
62
|
-
|
54
|
+
|
55
|
+
if deannotated_klass
|
56
|
+
deannotated << klass
|
57
|
+
end
|
63
58
|
rescue StandardError => e
|
64
59
|
$stderr.puts "Unable to deannotate #{File.join(file)}: #{e.message}"
|
65
60
|
$stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
|
66
61
|
end
|
67
62
|
end
|
63
|
+
|
68
64
|
puts "Removed annotations from: #{deannotated.join(', ')}"
|
69
65
|
end
|
70
66
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnAnnotationBuilder
|
6
|
+
BARE_TYPE_ALLOWANCE = 16
|
7
|
+
MD_TYPE_ALLOWANCE = 18
|
8
|
+
|
9
|
+
def initialize(column, model, max_size, options)
|
10
|
+
@column = column
|
11
|
+
@model = model
|
12
|
+
@max_size = max_size
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def build
|
17
|
+
result = ''
|
18
|
+
|
19
|
+
is_primary_key = is_column_primary_key?(@model, @column.name)
|
20
|
+
|
21
|
+
table_indices = @model.retrieve_indexes_from_table
|
22
|
+
column_indices = table_indices.select { |ind| ind.columns.include?(@column.name) }
|
23
|
+
|
24
|
+
column_attributes = ColumnAttributesBuilder.new(@column, @options, is_primary_key, column_indices).build
|
25
|
+
formatted_column_type = ColumnTypeBuilder.new(@column, @options).build
|
26
|
+
|
27
|
+
col_name = if @model.with_comments? && @column.comment
|
28
|
+
"#{@column.name}(#{@column.comment.gsub(/\n/, '\\n')})"
|
29
|
+
else
|
30
|
+
@column.name
|
31
|
+
end
|
32
|
+
|
33
|
+
if @options[:format_rdoc]
|
34
|
+
result += format("# %-#{@max_size}.#{@max_size}s<tt>%s</tt>",
|
35
|
+
"*#{col_name}*::",
|
36
|
+
column_attributes.unshift(formatted_column_type).join(', ')).rstrip + "\n"
|
37
|
+
elsif @options[:format_yard]
|
38
|
+
result += sprintf("# @!attribute #{col_name}") + "\n"
|
39
|
+
|
40
|
+
if @column.respond_to?(:array) && @column.array
|
41
|
+
ruby_class = "Array<#{Helper.map_col_type_to_ruby_classes(formatted_column_type)}>"
|
42
|
+
else
|
43
|
+
ruby_class = Helper.map_col_type_to_ruby_classes(formatted_column_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
result += sprintf("# @return [#{ruby_class}]") + "\n"
|
47
|
+
elsif @options[:format_markdown]
|
48
|
+
name_remainder = @max_size - col_name.length - Helper.non_ascii_length(col_name)
|
49
|
+
type_remainder = (MD_TYPE_ALLOWANCE - 2) - formatted_column_type.length
|
50
|
+
result += format("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`",
|
51
|
+
col_name,
|
52
|
+
' ',
|
53
|
+
formatted_column_type,
|
54
|
+
' ',
|
55
|
+
column_attributes.join(', ').rstrip).gsub('``', ' ').rstrip + "\n"
|
56
|
+
else
|
57
|
+
result += format_default(col_name, @max_size, formatted_column_type, column_attributes)
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def format_default(col_name, max_size, col_type, attrs)
|
66
|
+
format('# %s:%s %s',
|
67
|
+
Helper.mb_chars_ljust(col_name, max_size),
|
68
|
+
Helper.mb_chars_ljust(col_type, BARE_TYPE_ALLOWANCE),
|
69
|
+
attrs.join(', ')).rstrip + "\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO: Simplify this conditional
|
73
|
+
def is_column_primary_key?(model, column_name)
|
74
|
+
if model.primary_key
|
75
|
+
if model.primary_key.is_a?(Array)
|
76
|
+
# If the model has multiple primary keys, check if this column is one of them
|
77
|
+
if model.primary_key.collect(&:to_sym).include?(column_name.to_sym)
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# If model has 1 primary key, check if this column is it
|
82
|
+
if column_name.to_sym == model.primary_key.to_sym
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnAttributesBuilder
|
6
|
+
# Don't show default value for these column types
|
7
|
+
NO_DEFAULT_COL_TYPES = %w[json jsonb hstore].freeze
|
8
|
+
|
9
|
+
def initialize(column, options, is_primary_key, column_indices)
|
10
|
+
@column = ColumnWrapper.new(column)
|
11
|
+
@options = options
|
12
|
+
@is_primary_key = is_primary_key
|
13
|
+
@column_indices = column_indices
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get the list of attributes that should be included in the annotation for
|
17
|
+
# a given column.
|
18
|
+
def build
|
19
|
+
column_type = @column.column_type_string
|
20
|
+
attrs = []
|
21
|
+
|
22
|
+
unless @column.default.nil? || hide_default?
|
23
|
+
schema_default = "default(#{@column.default_string})"
|
24
|
+
|
25
|
+
attrs << schema_default
|
26
|
+
end
|
27
|
+
|
28
|
+
if @column.unsigned?
|
29
|
+
attrs << 'unsigned'
|
30
|
+
end
|
31
|
+
|
32
|
+
if !@column.null
|
33
|
+
attrs << 'not null'
|
34
|
+
end
|
35
|
+
|
36
|
+
if @is_primary_key
|
37
|
+
attrs << 'primary key'
|
38
|
+
end
|
39
|
+
|
40
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
41
|
+
is_decimal_type = column_type == 'decimal'
|
42
|
+
|
43
|
+
if !is_decimal_type && !is_special_type
|
44
|
+
if @column.limit && !@options[:format_yard]
|
45
|
+
if @column.limit.is_a?(Array)
|
46
|
+
attrs << "(#{@column.limit.join(', ')})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check out if we got an array column
|
52
|
+
if @column.array?
|
53
|
+
attrs << 'is an Array'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check out if we got a geometric column
|
57
|
+
# and print the type and SRID
|
58
|
+
if @column.geometry_type?
|
59
|
+
attrs << "#{@column.geometry_type}, #{@column.srid}"
|
60
|
+
elsif @column.geometric_type? && @column.geometric_type.present?
|
61
|
+
attrs << "#{@column.geometric_type.to_s.downcase}, #{@column.srid}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if the column has indices and print "indexed" if true
|
65
|
+
# If the index includes another column, print it too.
|
66
|
+
if @options[:simple_indexes]
|
67
|
+
# Note: there used to be a klass.table_exists? call here, but removed it as it seemed unnecessary.
|
68
|
+
|
69
|
+
sorted_column_indices&.each do |index|
|
70
|
+
indexed_columns = index.columns.reject { |i| i == @column.name }
|
71
|
+
|
72
|
+
if indexed_columns.empty?
|
73
|
+
attrs << 'indexed'
|
74
|
+
else
|
75
|
+
attrs << "indexed => [#{indexed_columns.join(', ')}]"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
attrs
|
81
|
+
end
|
82
|
+
|
83
|
+
def sorted_column_indices
|
84
|
+
# Not sure why there were & safe accessors here, but keeping in for time being.
|
85
|
+
sorted_indices = @column_indices&.sort_by(&:name)
|
86
|
+
|
87
|
+
_sorted_indices = sorted_indices.reject { |ind| ind.columns.is_a?(String) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def hide_default?
|
91
|
+
excludes =
|
92
|
+
if @options[:hide_default_column_types].blank?
|
93
|
+
NO_DEFAULT_COL_TYPES
|
94
|
+
else
|
95
|
+
@options[:hide_default_column_types].split(',')
|
96
|
+
end
|
97
|
+
|
98
|
+
excludes.include?(@column.column_type_string)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnTypeBuilder
|
6
|
+
# Don't show limit (#) on these column types
|
7
|
+
# Example: show "integer" instead of "integer(4)"
|
8
|
+
NO_LIMIT_COL_TYPES = %w[integer bigint boolean].freeze
|
9
|
+
|
10
|
+
def initialize(column, options)
|
11
|
+
@column = ColumnWrapper.new(column)
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the formatted column type as a string.
|
16
|
+
def build
|
17
|
+
column_type = @column.column_type_string
|
18
|
+
|
19
|
+
formatted_column_type = column_type
|
20
|
+
|
21
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
22
|
+
is_decimal_type = column_type == 'decimal'
|
23
|
+
|
24
|
+
if is_decimal_type
|
25
|
+
formatted_column_type = "decimal(#{@column.precision}, #{@column.scale})"
|
26
|
+
elsif is_special_type
|
27
|
+
# Do nothing. Kept as a code fragment in case we need to do something here.
|
28
|
+
else
|
29
|
+
if @column.limit && !@options[:format_yard]
|
30
|
+
if !@column.limit.is_a?(Array) && !hide_limit?
|
31
|
+
formatted_column_type = column_type + "(#{@column.limit})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
formatted_column_type
|
37
|
+
end
|
38
|
+
|
39
|
+
def hide_limit?
|
40
|
+
excludes =
|
41
|
+
if @options[:hide_limit_column_types].blank?
|
42
|
+
NO_LIMIT_COL_TYPES
|
43
|
+
else
|
44
|
+
@options[:hide_limit_column_types].split(',')
|
45
|
+
end
|
46
|
+
|
47
|
+
excludes.include?(@column.column_type_string)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnWrapper
|
6
|
+
def initialize(column)
|
7
|
+
@column = column
|
8
|
+
end
|
9
|
+
|
10
|
+
def default
|
11
|
+
# Note: Used to be klass.column_defaults[name], where name is the column name.
|
12
|
+
# Looks to be identical, but keeping note here in case there are differences.
|
13
|
+
_column_default = @column.default
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_string
|
17
|
+
Helper.quote(@column.default)
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
@column.type
|
22
|
+
end
|
23
|
+
|
24
|
+
def column_type_string
|
25
|
+
if (@column.respond_to?(:bigint?) && @column.bigint?) || /\Abigint\b/ =~ @column.sql_type
|
26
|
+
'bigint'
|
27
|
+
else
|
28
|
+
(@column.type || @column.sql_type).to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def unsigned?
|
33
|
+
@column.respond_to?(:unsigned?) && @column.unsigned?
|
34
|
+
end
|
35
|
+
|
36
|
+
def null
|
37
|
+
@column.null
|
38
|
+
end
|
39
|
+
|
40
|
+
def precision
|
41
|
+
@column.precision
|
42
|
+
end
|
43
|
+
|
44
|
+
def scale
|
45
|
+
@column.scale
|
46
|
+
end
|
47
|
+
|
48
|
+
def limit
|
49
|
+
@column.limit
|
50
|
+
end
|
51
|
+
|
52
|
+
def geometry_type?
|
53
|
+
@column.respond_to?(:geometry_type)
|
54
|
+
end
|
55
|
+
|
56
|
+
def geometry_type
|
57
|
+
# TODO: Check if we need to check if it responds before accessing the geometry type
|
58
|
+
@column.geometry_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def geometric_type?
|
62
|
+
@column.respond_to?(:geometric_type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def geometric_type
|
66
|
+
# TODO: Check if we need to check if it responds before accessing the geometric type
|
67
|
+
@column.geometric_type
|
68
|
+
end
|
69
|
+
|
70
|
+
def srid
|
71
|
+
# TODO: Check if we need to check if it responds before accessing the srid
|
72
|
+
@column.srid
|
73
|
+
end
|
74
|
+
|
75
|
+
def array?
|
76
|
+
@column.respond_to?(:array) && @column.array
|
77
|
+
end
|
78
|
+
|
79
|
+
def name
|
80
|
+
@column.name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|