annotaterb 4.1.1 → 4.2.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 +4 -4
- data/CHANGELOG.md +68 -0
- data/VERSION +1 -1
- data/exe/annotaterb +7 -7
- data/lib/annotate_rb/active_record_patch.rb +2 -0
- data/lib/annotate_rb/commands/annotate_models.rb +0 -1
- data/lib/annotate_rb/commands/annotate_routes.rb +0 -1
- data/lib/annotate_rb/commands/print_help.rb +0 -1
- data/lib/annotate_rb/commands/print_version.rb +0 -1
- data/lib/annotate_rb/commands.rb +4 -4
- data/lib/annotate_rb/config_finder.rb +1 -1
- data/lib/annotate_rb/config_loader.rb +2 -2
- data/lib/annotate_rb/core.rb +3 -3
- data/lib/annotate_rb/helper.rb +16 -0
- data/lib/annotate_rb/model_annotator/{annotation_generator.rb → annotation_builder.rb} +18 -14
- data/lib/annotate_rb/model_annotator/annotation_decider.rb +10 -12
- data/lib/annotate_rb/model_annotator/annotation_diff.rb +1 -1
- data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +9 -9
- data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +3 -3
- data/lib/annotate_rb/model_annotator/annotator.rb +12 -53
- data/lib/annotate_rb/model_annotator/column_annotation/annotation_builder.rb +135 -0
- data/lib/annotate_rb/model_annotator/column_annotation/attributes_builder.rb +104 -0
- data/lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb +103 -0
- data/lib/annotate_rb/model_annotator/column_annotation/type_builder.rb +54 -0
- data/lib/annotate_rb/model_annotator/column_annotation.rb +12 -0
- data/lib/annotate_rb/model_annotator/file_builder.rb +58 -0
- data/lib/annotate_rb/model_annotator/file_components.rb +78 -0
- data/lib/annotate_rb/model_annotator/file_name_resolver.rb +3 -3
- data/lib/annotate_rb/model_annotator/foreign_key_annotation/annotation_builder.rb +57 -0
- data/lib/annotate_rb/model_annotator/foreign_key_annotation.rb +9 -0
- data/lib/annotate_rb/model_annotator/index_annotation/annotation_builder.rb +113 -0
- data/lib/annotate_rb/model_annotator/index_annotation.rb +9 -0
- data/lib/annotate_rb/model_annotator/magic_comment_parser.rb +32 -0
- data/lib/annotate_rb/model_annotator/model_class_getter.rb +6 -6
- data/lib/annotate_rb/model_annotator/model_files_getter.rb +12 -10
- data/lib/annotate_rb/model_annotator/model_wrapper.rb +44 -37
- data/lib/annotate_rb/model_annotator/pattern_getter.rb +142 -10
- data/lib/annotate_rb/model_annotator/project_annotation_remover.rb +65 -0
- data/lib/annotate_rb/model_annotator/project_annotator.rb +63 -0
- data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +16 -18
- data/lib/annotate_rb/model_annotator/single_file_annotation_remover.rb +37 -0
- data/lib/annotate_rb/model_annotator/single_file_annotator.rb +49 -0
- data/lib/annotate_rb/model_annotator/{file_annotator_instruction.rb → single_file_annotator_instruction.rb} +2 -2
- data/lib/annotate_rb/model_annotator/single_file_remove_annotation_instruction.rb +15 -0
- data/lib/annotate_rb/model_annotator.rb +25 -26
- data/lib/annotate_rb/options.rb +20 -25
- data/lib/annotate_rb/parser.rb +150 -142
- data/lib/annotate_rb/rake_bootstrapper.rb +8 -8
- data/lib/annotate_rb/route_annotator/annotation_processor.rb +7 -8
- data/lib/annotate_rb/route_annotator/annotator.rb +2 -2
- data/lib/annotate_rb/route_annotator/base_processor.rb +3 -3
- data/lib/annotate_rb/route_annotator/header_generator.rb +15 -15
- data/lib/annotate_rb/route_annotator/helper.rb +9 -9
- data/lib/annotate_rb/route_annotator/removal_processor.rb +4 -4
- data/lib/annotate_rb/route_annotator.rb +6 -6
- data/lib/annotate_rb/runner.rb +0 -4
- data/lib/annotate_rb/tasks/annotate_models_migrate.rake +5 -5
- data/lib/annotate_rb.rb +19 -19
- data/lib/generators/annotate_rb/install/install_generator.rb +3 -2
- data/lib/generators/annotate_rb/install/templates/annotate_rb.rake +1 -1
- metadata +22 -16
- data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +0 -92
- data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +0 -102
- data/lib/annotate_rb/model_annotator/column_type_builder.rb +0 -51
- data/lib/annotate_rb/model_annotator/column_wrapper.rb +0 -84
- data/lib/annotate_rb/model_annotator/constants.rb +0 -22
- data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +0 -25
- data/lib/annotate_rb/model_annotator/file_annotator.rb +0 -77
- data/lib/annotate_rb/model_annotator/file_patterns.rb +0 -129
- data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +0 -55
- data/lib/annotate_rb/model_annotator/helper.rb +0 -107
- data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +0 -74
- data/lib/annotate_rb/model_annotator/model_file_annotator.rb +0 -55
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class AttributesBuilder
|
7
|
+
# Don't show default value for these column types
|
8
|
+
NO_DEFAULT_COL_TYPES = %w[json jsonb hstore].freeze
|
9
|
+
|
10
|
+
def initialize(column, options, is_primary_key, column_indices)
|
11
|
+
@column = ColumnWrapper.new(column)
|
12
|
+
@options = options
|
13
|
+
@is_primary_key = is_primary_key
|
14
|
+
@column_indices = column_indices
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the list of attributes that should be included in the annotation for
|
18
|
+
# a given column.
|
19
|
+
def build
|
20
|
+
column_type = @column.column_type_string
|
21
|
+
attrs = []
|
22
|
+
|
23
|
+
unless @column.default.nil? || hide_default?
|
24
|
+
schema_default = "default(#{@column.default_string})"
|
25
|
+
|
26
|
+
attrs << schema_default
|
27
|
+
end
|
28
|
+
|
29
|
+
if @column.unsigned?
|
30
|
+
attrs << "unsigned"
|
31
|
+
end
|
32
|
+
|
33
|
+
if !@column.null
|
34
|
+
attrs << "not null"
|
35
|
+
end
|
36
|
+
|
37
|
+
if @is_primary_key
|
38
|
+
attrs << "primary key"
|
39
|
+
end
|
40
|
+
|
41
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
42
|
+
is_decimal_type = column_type == "decimal"
|
43
|
+
|
44
|
+
if !is_decimal_type && !is_special_type
|
45
|
+
if @column.limit && !@options[:format_yard]
|
46
|
+
if @column.limit.is_a?(Array)
|
47
|
+
attrs << "(#{@column.limit.join(", ")})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check out if we got an array column
|
53
|
+
if @column.array?
|
54
|
+
attrs << "is an Array"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check out if we got a geometric column
|
58
|
+
# and print the type and SRID
|
59
|
+
if @column.geometry_type?
|
60
|
+
attrs << "#{@column.geometry_type}, #{@column.srid}"
|
61
|
+
elsif @column.geometric_type? && @column.geometric_type.present?
|
62
|
+
attrs << "#{@column.geometric_type.to_s.downcase}, #{@column.srid}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if the column has indices and print "indexed" if true
|
66
|
+
# If the index includes another column, print it too.
|
67
|
+
if @options[:simple_indexes]
|
68
|
+
# Note: there used to be a klass.table_exists? call here, but removed it as it seemed unnecessary.
|
69
|
+
|
70
|
+
sorted_column_indices&.each do |index|
|
71
|
+
indexed_columns = index.columns.reject { |i| i == @column.name }
|
72
|
+
|
73
|
+
attrs << if indexed_columns.empty?
|
74
|
+
"indexed"
|
75
|
+
else
|
76
|
+
"indexed => [#{indexed_columns.join(", ")}]"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
attrs
|
82
|
+
end
|
83
|
+
|
84
|
+
def sorted_column_indices
|
85
|
+
# Not sure why there were & safe accessors here, but keeping in for time being.
|
86
|
+
sorted_indices = @column_indices&.sort_by(&:name)
|
87
|
+
|
88
|
+
_sorted_indices = sorted_indices.reject { |ind| ind.columns.is_a?(String) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def hide_default?
|
92
|
+
excludes =
|
93
|
+
if @options[:hide_default_column_types].blank?
|
94
|
+
NO_DEFAULT_COL_TYPES
|
95
|
+
else
|
96
|
+
@options[:hide_default_column_types].split(",")
|
97
|
+
end
|
98
|
+
|
99
|
+
excludes.include?(@column.column_type_string)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class ColumnWrapper
|
7
|
+
def initialize(column)
|
8
|
+
@column = column
|
9
|
+
end
|
10
|
+
|
11
|
+
def default
|
12
|
+
# Note: Used to be klass.column_defaults[name], where name is the column name.
|
13
|
+
# Looks to be identical, but keeping note here in case there are differences.
|
14
|
+
_column_default = @column.default
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_string
|
18
|
+
quote(@column.default)
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
@column.type
|
23
|
+
end
|
24
|
+
|
25
|
+
def column_type_string
|
26
|
+
if (@column.respond_to?(:bigint?) && @column.bigint?) || /\Abigint\b/ =~ @column.sql_type
|
27
|
+
"bigint"
|
28
|
+
else
|
29
|
+
(@column.type || @column.sql_type).to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def unsigned?
|
34
|
+
@column.respond_to?(:unsigned?) && @column.unsigned?
|
35
|
+
end
|
36
|
+
|
37
|
+
def null
|
38
|
+
@column.null
|
39
|
+
end
|
40
|
+
|
41
|
+
def precision
|
42
|
+
@column.precision
|
43
|
+
end
|
44
|
+
|
45
|
+
def scale
|
46
|
+
@column.scale
|
47
|
+
end
|
48
|
+
|
49
|
+
def limit
|
50
|
+
@column.limit
|
51
|
+
end
|
52
|
+
|
53
|
+
def geometry_type?
|
54
|
+
@column.respond_to?(:geometry_type)
|
55
|
+
end
|
56
|
+
|
57
|
+
def geometry_type
|
58
|
+
# TODO: Check if we need to check if it responds before accessing the geometry type
|
59
|
+
@column.geometry_type
|
60
|
+
end
|
61
|
+
|
62
|
+
def geometric_type?
|
63
|
+
@column.respond_to?(:geometric_type)
|
64
|
+
end
|
65
|
+
|
66
|
+
def geometric_type
|
67
|
+
# TODO: Check if we need to check if it responds before accessing the geometric type
|
68
|
+
@column.geometric_type
|
69
|
+
end
|
70
|
+
|
71
|
+
def srid
|
72
|
+
# TODO: Check if we need to check if it responds before accessing the srid
|
73
|
+
@column.srid
|
74
|
+
end
|
75
|
+
|
76
|
+
def array?
|
77
|
+
@column.respond_to?(:array) && @column.array
|
78
|
+
end
|
79
|
+
|
80
|
+
def name
|
81
|
+
@column.name
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Simple quoting for the default column value
|
87
|
+
def quote(value)
|
88
|
+
case value
|
89
|
+
when NilClass then "NULL"
|
90
|
+
when TrueClass then "TRUE"
|
91
|
+
when FalseClass then "FALSE"
|
92
|
+
when Float, Integer then value.to_s
|
93
|
+
# BigDecimals need to be output in a non-normalized form and quoted.
|
94
|
+
when BigDecimal then value.to_s("F")
|
95
|
+
when Array then value.map { |v| quote(v) }
|
96
|
+
else
|
97
|
+
value.inspect
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class TypeBuilder
|
7
|
+
# Don't show limit (#) on these column types
|
8
|
+
# Example: show "integer" instead of "integer(4)"
|
9
|
+
NO_LIMIT_COL_TYPES = %w[integer bigint boolean].freeze
|
10
|
+
|
11
|
+
def initialize(column, options)
|
12
|
+
@column = ColumnWrapper.new(column)
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the formatted column type as a string.
|
17
|
+
def build
|
18
|
+
column_type = @column.column_type_string
|
19
|
+
|
20
|
+
formatted_column_type = column_type
|
21
|
+
|
22
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
23
|
+
is_decimal_type = column_type == "decimal"
|
24
|
+
|
25
|
+
if is_decimal_type
|
26
|
+
formatted_column_type = "decimal(#{@column.precision}, #{@column.scale})"
|
27
|
+
elsif is_special_type
|
28
|
+
# Do nothing. Kept as a code fragment in case we need to do something here.
|
29
|
+
elsif @column.limit && !@options[:format_yard]
|
30
|
+
# Unsure if Column#limit will ever be an array. May be safe to remove.
|
31
|
+
if !@column.limit.is_a?(Array) && !hide_limit?
|
32
|
+
formatted_column_type = column_type + "(#{@column.limit})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
formatted_column_type
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def hide_limit?
|
42
|
+
excludes =
|
43
|
+
if @options[:hide_limit_column_types].blank?
|
44
|
+
NO_LIMIT_COL_TYPES
|
45
|
+
else
|
46
|
+
@options[:hide_limit_column_types].split(",")
|
47
|
+
end
|
48
|
+
|
49
|
+
excludes.include?(@column.column_type_string)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
autoload :AttributesBuilder, "annotate_rb/model_annotator/column_annotation/attributes_builder"
|
7
|
+
autoload :TypeBuilder, "annotate_rb/model_annotator/column_annotation/type_builder"
|
8
|
+
autoload :ColumnWrapper, "annotate_rb/model_annotator/column_annotation/column_wrapper"
|
9
|
+
autoload :AnnotationBuilder, "annotate_rb/model_annotator/column_annotation/annotation_builder"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Generates the text file content with annotations, these are then to be written to filesystem.
|
6
|
+
class FileBuilder
|
7
|
+
def initialize(file_components, new_annotations, annotation_position, options)
|
8
|
+
@file_components = file_components
|
9
|
+
@new_annotations = new_annotations
|
10
|
+
@annotation_position = annotation_position
|
11
|
+
@options = options
|
12
|
+
|
13
|
+
@new_wrapped_annotations = wrapped_content(new_annotations)
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate_content_with_new_annotations
|
17
|
+
# Need to keep `.to_s` for now since the it can be either a String or Symbol
|
18
|
+
annotation_write_position = @options[@annotation_position].to_s
|
19
|
+
|
20
|
+
_content = if %w[after bottom].include?(annotation_write_position)
|
21
|
+
@file_components.magic_comments + (@file_components.pure_file_content.rstrip + "\n\n" + @new_wrapped_annotations)
|
22
|
+
elsif @file_components.magic_comments.empty?
|
23
|
+
@file_components.magic_comments + @new_wrapped_annotations + @file_components.pure_file_content.lstrip
|
24
|
+
else
|
25
|
+
@file_components.magic_comments + "\n" + @new_wrapped_annotations + @file_components.pure_file_content.lstrip
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_existing_annotations
|
30
|
+
return "" if !@file_components.has_annotations?
|
31
|
+
|
32
|
+
annotation_pattern = AnnotationPatternGenerator.call(@options)
|
33
|
+
|
34
|
+
new_annotation = @file_components.space_before_annotation + @new_wrapped_annotations + @file_components.space_after_annotation
|
35
|
+
|
36
|
+
_content = @file_components.current_file_content.sub(annotation_pattern, new_annotation)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def wrapped_content(content)
|
42
|
+
wrapper_open = if @options[:wrapper_open]
|
43
|
+
"# #{@options[:wrapper_open]}\n"
|
44
|
+
else
|
45
|
+
""
|
46
|
+
end
|
47
|
+
|
48
|
+
wrapper_close = if @options[:wrapper_close]
|
49
|
+
"# #{@options[:wrapper_close]}\n"
|
50
|
+
else
|
51
|
+
""
|
52
|
+
end
|
53
|
+
|
54
|
+
_wrapped_info_block = "#{wrapper_open}#{content}#{wrapper_close}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class FileComponents
|
6
|
+
SKIP_ANNOTATION_STRING = "# -*- SkipSchemaAnnotations"
|
7
|
+
SOME_PATTERN = /\A(?<start>\s*).*?\n(?<end>\s*)\z/m # Unsure what this pattern is
|
8
|
+
|
9
|
+
def initialize(file_content, new_annotations, options)
|
10
|
+
@file_content = file_content
|
11
|
+
@diff = AnnotationDiffGenerator.new(file_content, new_annotations).generate
|
12
|
+
@options = options
|
13
|
+
@annotation_pattern = AnnotationPatternGenerator.call(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_file_content
|
17
|
+
@file_content
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: Rename method once it's clear what this actually does
|
21
|
+
def space_before_annotation
|
22
|
+
return @space_before_annotation if defined?(@space_before_annotation)
|
23
|
+
|
24
|
+
match = current_annotations.match(SOME_PATTERN)
|
25
|
+
@space_before_annotation = if match
|
26
|
+
match[:start]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: Rename method once it's clear what this actually does
|
31
|
+
def space_after_annotation
|
32
|
+
return @space_after_annotation if defined?(@space_after_annotation)
|
33
|
+
|
34
|
+
match = current_annotations.match(SOME_PATTERN)
|
35
|
+
@space_after_annotation = if match
|
36
|
+
match[:end]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def pure_file_content
|
41
|
+
@pure_file_content ||=
|
42
|
+
begin
|
43
|
+
content_without_magic_comments = @file_content.gsub(MagicCommentParser::MAGIC_COMMENTS_REGEX, "")
|
44
|
+
content_without_annotations = content_without_magic_comments.sub(@annotation_pattern, "")
|
45
|
+
|
46
|
+
content_without_annotations
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def magic_comments
|
51
|
+
@magic_comments ||= MagicCommentParser.call(@file_content)
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_skip_string?
|
55
|
+
@has_skip_string ||= @file_content.include?(SKIP_ANNOTATION_STRING)
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_annotations?
|
59
|
+
@has_annotations ||= @diff.current_columns.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
def annotations_changed?
|
63
|
+
@has_annotations_changed ||= @diff.changed?
|
64
|
+
end
|
65
|
+
|
66
|
+
def current_annotations
|
67
|
+
@current_annotations ||=
|
68
|
+
if has_annotations?
|
69
|
+
# `#has_annotations?` uses a different regex pattern than the one in `@annotation_pattern`,
|
70
|
+
# this could lead to unexpected behavior
|
71
|
+
@file_content.match(@annotation_pattern).to_s
|
72
|
+
else
|
73
|
+
""
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -6,9 +6,9 @@ module AnnotateRb
|
|
6
6
|
class << self
|
7
7
|
def call(filename_template, model_name, table_name)
|
8
8
|
filename_template
|
9
|
-
.gsub(
|
10
|
-
.gsub(
|
11
|
-
.gsub(
|
9
|
+
.gsub("%MODEL_NAME%", model_name)
|
10
|
+
.gsub("%PLURALIZED_MODEL_NAME%", model_name.pluralize)
|
11
|
+
.gsub("%TABLE_NAME%", table_name || model_name.pluralize)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ForeignKeyAnnotation
|
6
|
+
class AnnotationBuilder
|
7
|
+
def initialize(model, options)
|
8
|
+
@model = model
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
fk_info = if @options[:format_markdown]
|
14
|
+
"#\n# ### Foreign Keys\n#\n"
|
15
|
+
else
|
16
|
+
"#\n# Foreign Keys\n#\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
return "" unless @model.connection.respond_to?(:supports_foreign_keys?) &&
|
20
|
+
@model.connection.supports_foreign_keys? && @model.connection.respond_to?(:foreign_keys)
|
21
|
+
|
22
|
+
foreign_keys = @model.connection.foreign_keys(@model.table_name)
|
23
|
+
return "" if foreign_keys.empty?
|
24
|
+
|
25
|
+
format_name = lambda do |fk|
|
26
|
+
return fk.options[:column] if fk.name.blank?
|
27
|
+
|
28
|
+
@options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, "...")
|
29
|
+
end
|
30
|
+
|
31
|
+
max_size = foreign_keys.map(&format_name).map(&:size).max + 1
|
32
|
+
foreign_keys.sort_by { |fk| [format_name.call(fk), fk.column] }.each do |fk|
|
33
|
+
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
|
34
|
+
constraints_info = ""
|
35
|
+
constraints_info += "ON DELETE => #{fk.on_delete} " if fk.on_delete
|
36
|
+
constraints_info += "ON UPDATE => #{fk.on_update} " if fk.on_update
|
37
|
+
constraints_info = constraints_info.strip
|
38
|
+
|
39
|
+
fk_info += if @options[:format_markdown]
|
40
|
+
format("# * `%s`%s:\n# * **`%s`**\n",
|
41
|
+
format_name.call(fk),
|
42
|
+
constraints_info.blank? ? "" : " (_#{constraints_info}_)",
|
43
|
+
ref_info)
|
44
|
+
else
|
45
|
+
format("# %-#{max_size}.#{max_size}s %s %s",
|
46
|
+
format_name.call(fk),
|
47
|
+
"(#{ref_info})",
|
48
|
+
constraints_info).rstrip + "\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
fk_info
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module IndexAnnotation
|
6
|
+
class AnnotationBuilder
|
7
|
+
INDEX_CLAUSES = {
|
8
|
+
unique: {
|
9
|
+
default: "UNIQUE",
|
10
|
+
markdown: "_unique_"
|
11
|
+
},
|
12
|
+
where: {
|
13
|
+
default: "WHERE",
|
14
|
+
markdown: "_where_"
|
15
|
+
},
|
16
|
+
using: {
|
17
|
+
default: "USING",
|
18
|
+
markdown: "_using_"
|
19
|
+
}
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def initialize(model, options)
|
23
|
+
@model = model
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
|
27
|
+
def build
|
28
|
+
index_info = if @options[:format_markdown]
|
29
|
+
"#\n# ### Indexes\n#\n"
|
30
|
+
else
|
31
|
+
"#\n# Indexes\n#\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
indexes = @model.retrieve_indexes_from_table
|
35
|
+
return "" if indexes.empty?
|
36
|
+
|
37
|
+
max_size = indexes.collect { |index| index.name.size }.max + 1
|
38
|
+
indexes.sort_by(&:name).each do |index|
|
39
|
+
index_info += if @options[:format_markdown]
|
40
|
+
final_index_string_in_markdown(index)
|
41
|
+
else
|
42
|
+
final_index_string(index, max_size)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
index_info
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def index_using_info(index, format = :default)
|
52
|
+
value = index.try(:using) && index.using.try(:to_sym)
|
53
|
+
if !value.blank? && value != :btree
|
54
|
+
" #{INDEX_CLAUSES[:using][format]} #{value}"
|
55
|
+
else
|
56
|
+
""
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def index_where_info(index, format = :default)
|
61
|
+
value = index.try(:where).try(:to_s)
|
62
|
+
if value.blank?
|
63
|
+
""
|
64
|
+
else
|
65
|
+
" #{INDEX_CLAUSES[:where][format]} #{value}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def index_unique_info(index, format = :default)
|
70
|
+
index.unique ? " #{INDEX_CLAUSES[:unique][format]}" : ""
|
71
|
+
end
|
72
|
+
|
73
|
+
def final_index_string_in_markdown(index)
|
74
|
+
details = format(
|
75
|
+
"%s%s%s",
|
76
|
+
index_unique_info(index, :markdown),
|
77
|
+
index_where_info(index, :markdown),
|
78
|
+
index_using_info(index, :markdown)
|
79
|
+
).strip
|
80
|
+
details = " (#{details})" unless details.blank?
|
81
|
+
|
82
|
+
format(
|
83
|
+
"# * `%s`%s:\n# * **`%s`**\n",
|
84
|
+
index.name,
|
85
|
+
details,
|
86
|
+
index_columns_info(index).join("`**\n# * **`")
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def final_index_string(index, max_size)
|
91
|
+
format(
|
92
|
+
"# %-#{max_size}.#{max_size}s %s%s%s%s",
|
93
|
+
index.name,
|
94
|
+
"(#{index_columns_info(index).join(",")})",
|
95
|
+
index_unique_info(index),
|
96
|
+
index_where_info(index),
|
97
|
+
index_using_info(index)
|
98
|
+
).rstrip + "\n"
|
99
|
+
end
|
100
|
+
|
101
|
+
def index_columns_info(index)
|
102
|
+
Array(index.columns).map do |col|
|
103
|
+
if index.try(:orders) && index.orders[col.to_s]
|
104
|
+
"#{col} #{index.orders[col.to_s].upcase}"
|
105
|
+
else
|
106
|
+
col.to_s.gsub("\r", '\r').gsub("\n", '\n')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Extracts magic comments strings and returns them
|
6
|
+
class MagicCommentParser
|
7
|
+
MAGIC_COMMENTS = [
|
8
|
+
HASH_ENCODING = /(^#\s*encoding:.*(?:\n|r\n))/,
|
9
|
+
HASH_CODING = /(^# coding:.*(?:\n|\r\n))/,
|
10
|
+
HASH_FROZEN_STRING = /(^#\s*frozen_string_literal:.+(?:\n|\r\n))/,
|
11
|
+
STAR_ENCODING = /(^# -\*- encoding\s?:.*(?:\n|\r\n))/,
|
12
|
+
STAR_CODING = /(^# -\*- coding:.*(?:\n|\r\n))/,
|
13
|
+
STAR_FROZEN_STRING = /(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/,
|
14
|
+
SORBET_TYPED_STRING = /(^#\s*typed:.*(?:\n|r\n))/.freeze
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
MAGIC_COMMENTS_REGEX = Regexp.union(*MAGIC_COMMENTS).freeze
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def call(content)
|
21
|
+
magic_comments = content.scan(MAGIC_COMMENTS_REGEX).flatten.compact
|
22
|
+
|
23
|
+
if magic_comments.any?
|
24
|
+
magic_comments.join
|
25
|
+
else
|
26
|
+
""
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -8,8 +8,8 @@ module AnnotateRb
|
|
8
8
|
# Check for namespaced models in subdirectories as well as models
|
9
9
|
# in subdirectories without namespacing.
|
10
10
|
def call(file, options)
|
11
|
-
model_path = file.gsub(/\.rb$/,
|
12
|
-
options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/,
|
11
|
+
model_path = file.gsub(/\.rb$/, "")
|
12
|
+
options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/, "").gsub(/^\//, "") }
|
13
13
|
|
14
14
|
begin
|
15
15
|
get_loaded_model(model_path, file) || raise(BadModelFileError.new)
|
@@ -18,8 +18,8 @@ module AnnotateRb
|
|
18
18
|
file_path = File.expand_path(file)
|
19
19
|
if File.file?(file_path) && Kernel.require(file_path)
|
20
20
|
retry
|
21
|
-
elsif model_path
|
22
|
-
model_path = model_path.split(
|
21
|
+
elsif /\//.match?(model_path)
|
22
|
+
model_path = model_path.split("/")[1..-1].join("/").to_s
|
23
23
|
retry
|
24
24
|
else
|
25
25
|
raise
|
@@ -39,7 +39,7 @@ module AnnotateRb
|
|
39
39
|
absolute_file = File.expand_path(file)
|
40
40
|
model_paths =
|
41
41
|
$LOAD_PATH.select { |path| absolute_file.include?(path) }
|
42
|
-
|
42
|
+
.map { |path| absolute_file.sub(path, "").sub(/\.rb$/, "").sub(/^\//, "") }
|
43
43
|
model_paths
|
44
44
|
.map { |path| get_loaded_model_by_path(path) }
|
45
45
|
.find { |loaded_model| !loaded_model.nil? }
|
@@ -51,7 +51,7 @@ module AnnotateRb
|
|
51
51
|
rescue StandardError, LoadError
|
52
52
|
# Revert to the old way but it is not really robust
|
53
53
|
ObjectSpace.each_object(::Class)
|
54
|
-
|
54
|
+
.select do |c|
|
55
55
|
Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
|
56
56
|
c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
|
57
57
|
c.ancestors.include?(::ActiveRecord::Base)
|