annotaterb 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +4 -5
  4. data/VERSION +1 -1
  5. data/exe/annotaterb +7 -7
  6. data/lib/annotate_rb/active_record_patch.rb +2 -0
  7. data/lib/annotate_rb/commands/annotate_models.rb +0 -1
  8. data/lib/annotate_rb/commands/annotate_routes.rb +0 -1
  9. data/lib/annotate_rb/commands/print_help.rb +0 -1
  10. data/lib/annotate_rb/commands/print_version.rb +0 -1
  11. data/lib/annotate_rb/commands.rb +4 -4
  12. data/lib/annotate_rb/config_finder.rb +1 -1
  13. data/lib/annotate_rb/config_loader.rb +2 -2
  14. data/lib/annotate_rb/core.rb +3 -3
  15. data/lib/annotate_rb/helper.rb +16 -0
  16. data/lib/annotate_rb/model_annotator/{annotation_generator.rb → annotation_builder.rb} +18 -14
  17. data/lib/annotate_rb/model_annotator/annotation_decider.rb +10 -12
  18. data/lib/annotate_rb/model_annotator/annotation_diff.rb +19 -0
  19. data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +44 -0
  20. data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +3 -3
  21. data/lib/annotate_rb/model_annotator/annotator.rb +12 -53
  22. data/lib/annotate_rb/model_annotator/column_annotation/annotation_builder.rb +135 -0
  23. data/lib/annotate_rb/model_annotator/column_annotation/attributes_builder.rb +104 -0
  24. data/lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb +103 -0
  25. data/lib/annotate_rb/model_annotator/column_annotation/type_builder.rb +54 -0
  26. data/lib/annotate_rb/model_annotator/column_annotation.rb +12 -0
  27. data/lib/annotate_rb/model_annotator/file_builder.rb +58 -0
  28. data/lib/annotate_rb/model_annotator/file_components.rb +78 -0
  29. data/lib/annotate_rb/model_annotator/file_name_resolver.rb +3 -3
  30. data/lib/annotate_rb/model_annotator/foreign_key_annotation/annotation_builder.rb +57 -0
  31. data/lib/annotate_rb/model_annotator/foreign_key_annotation.rb +9 -0
  32. data/lib/annotate_rb/model_annotator/index_annotation/annotation_builder.rb +113 -0
  33. data/lib/annotate_rb/model_annotator/index_annotation.rb +9 -0
  34. data/lib/annotate_rb/model_annotator/magic_comment_parser.rb +32 -0
  35. data/lib/annotate_rb/model_annotator/model_class_getter.rb +6 -6
  36. data/lib/annotate_rb/model_annotator/model_files_getter.rb +12 -10
  37. data/lib/annotate_rb/model_annotator/model_wrapper.rb +44 -37
  38. data/lib/annotate_rb/model_annotator/pattern_getter.rb +142 -10
  39. data/lib/annotate_rb/model_annotator/project_annotation_remover.rb +65 -0
  40. data/lib/annotate_rb/model_annotator/project_annotator.rb +63 -0
  41. data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +16 -18
  42. data/lib/annotate_rb/model_annotator/single_file_annotation_remover.rb +37 -0
  43. data/lib/annotate_rb/model_annotator/single_file_annotator.rb +49 -0
  44. data/lib/annotate_rb/model_annotator/{file_annotator_instruction.rb → single_file_annotator_instruction.rb} +2 -2
  45. data/lib/annotate_rb/model_annotator/single_file_remove_annotation_instruction.rb +15 -0
  46. data/lib/annotate_rb/model_annotator.rb +25 -24
  47. data/lib/annotate_rb/options.rb +20 -25
  48. data/lib/annotate_rb/parser.rb +150 -142
  49. data/lib/annotate_rb/rake_bootstrapper.rb +8 -8
  50. data/lib/annotate_rb/route_annotator/annotation_processor.rb +7 -8
  51. data/lib/annotate_rb/route_annotator/annotator.rb +2 -2
  52. data/lib/annotate_rb/route_annotator/base_processor.rb +3 -3
  53. data/lib/annotate_rb/route_annotator/header_generator.rb +15 -15
  54. data/lib/annotate_rb/route_annotator/helper.rb +9 -9
  55. data/lib/annotate_rb/route_annotator/removal_processor.rb +4 -4
  56. data/lib/annotate_rb/route_annotator.rb +6 -6
  57. data/lib/annotate_rb/runner.rb +0 -4
  58. data/lib/annotate_rb/tasks/annotate_models_migrate.rake +5 -5
  59. data/lib/annotate_rb.rb +19 -19
  60. data/lib/generators/annotate_rb/install/install_generator.rb +3 -2
  61. data/lib/generators/annotate_rb/install/templates/annotate_rb.rake +1 -1
  62. metadata +24 -16
  63. data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +0 -92
  64. data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +0 -102
  65. data/lib/annotate_rb/model_annotator/column_type_builder.rb +0 -51
  66. data/lib/annotate_rb/model_annotator/column_wrapper.rb +0 -84
  67. data/lib/annotate_rb/model_annotator/constants.rb +0 -22
  68. data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +0 -25
  69. data/lib/annotate_rb/model_annotator/file_annotator.rb +0 -83
  70. data/lib/annotate_rb/model_annotator/file_patterns.rb +0 -129
  71. data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +0 -55
  72. data/lib/annotate_rb/model_annotator/helper.rb +0 -107
  73. data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +0 -74
  74. data/lib/annotate_rb/model_annotator/model_file_annotator.rb +0 -55
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ module ColumnAnnotation
6
+ class AnnotationBuilder
7
+ BARE_TYPE_ALLOWANCE = 16
8
+ MD_TYPE_ALLOWANCE = 18
9
+
10
+ def initialize(column, model, max_size, options)
11
+ @column = column
12
+ @model = model
13
+ @max_size = max_size
14
+ @options = options
15
+ end
16
+
17
+ def build
18
+ result = ""
19
+
20
+ is_primary_key = is_column_primary_key?(@model, @column.name)
21
+
22
+ table_indices = @model.retrieve_indexes_from_table
23
+ column_indices = table_indices.select { |ind| ind.columns.include?(@column.name) }
24
+
25
+ column_attributes = AttributesBuilder.new(@column, @options, is_primary_key, column_indices).build
26
+ formatted_column_type = TypeBuilder.new(@column, @options).build
27
+
28
+ col_name = if @model.with_comments? && @column.comment
29
+ "#{@column.name}(#{@column.comment.gsub(/\n/, '\\n')})"
30
+ else
31
+ @column.name
32
+ end
33
+
34
+ result += if @options[:format_rdoc]
35
+ format_rdoc(col_name, @max_size, formatted_column_type, column_attributes)
36
+ elsif @options[:format_yard]
37
+ format_yard(col_name, @max_size, formatted_column_type, column_attributes)
38
+ elsif @options[:format_markdown]
39
+ format_markdown(col_name, @max_size, formatted_column_type, column_attributes)
40
+ else
41
+ format_default(col_name, @max_size, formatted_column_type, column_attributes)
42
+ end
43
+
44
+ result
45
+ end
46
+
47
+ private
48
+
49
+ def non_ascii_length(string)
50
+ string.to_s.chars.count { |element| !element.ascii_only? }
51
+ end
52
+
53
+ def mb_chars_ljust(string, length)
54
+ string = string.to_s
55
+ padding = length - Helper.width(string)
56
+ if padding.positive?
57
+ string + (" " * padding)
58
+ else
59
+ string[0..(length - 1)]
60
+ end
61
+ end
62
+
63
+ def map_col_type_to_ruby_classes(col_type)
64
+ case col_type
65
+ when "integer" then Integer.to_s
66
+ when "float" then Float.to_s
67
+ when "decimal" then BigDecimal.to_s
68
+ when "datetime", "timestamp", "time" then Time.to_s
69
+ when "date" then Date.to_s
70
+ when "text", "string", "binary", "inet", "uuid" then String.to_s
71
+ when "json", "jsonb" then Hash.to_s
72
+ when "boolean" then "Boolean"
73
+ end
74
+ end
75
+
76
+ def format_rdoc(col_name, max_size, formatted_column_type, column_attributes)
77
+ format("# %-#{max_size}.#{max_size}s<tt>%s</tt>",
78
+ "*#{col_name}*::",
79
+ column_attributes.unshift(formatted_column_type).join(", ")).rstrip + "\n"
80
+ end
81
+
82
+ def format_yard(col_name, _max_size, formatted_column_type, _column_attributes)
83
+ res = ""
84
+ res += sprintf("# @!attribute #{col_name}") + "\n"
85
+
86
+ ruby_class = if @column.respond_to?(:array) && @column.array
87
+ "Array<#{map_col_type_to_ruby_classes(formatted_column_type)}>"
88
+ else
89
+ map_col_type_to_ruby_classes(formatted_column_type)
90
+ end
91
+
92
+ res += sprintf("# @return [#{ruby_class}]") + "\n"
93
+
94
+ res
95
+ end
96
+
97
+ def format_markdown(col_name, max_size, formatted_column_type, column_attributes)
98
+ name_remainder = max_size - col_name.length - non_ascii_length(col_name)
99
+ type_remainder = (MD_TYPE_ALLOWANCE - 2) - formatted_column_type.length
100
+
101
+ format("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`",
102
+ col_name,
103
+ " ",
104
+ formatted_column_type,
105
+ " ",
106
+ column_attributes.join(", ").rstrip).gsub("``", " ").rstrip + "\n"
107
+ end
108
+
109
+ def format_default(col_name, max_size, formatted_column_type, column_attributes)
110
+ format("# %s:%s %s",
111
+ mb_chars_ljust(col_name, max_size),
112
+ mb_chars_ljust(formatted_column_type, BARE_TYPE_ALLOWANCE),
113
+ column_attributes.join(", ")).rstrip + "\n"
114
+ end
115
+
116
+ # TODO: Simplify this conditional
117
+ def is_column_primary_key?(model, column_name)
118
+ if model.primary_key
119
+ if model.primary_key.is_a?(Array)
120
+ # If the model has multiple primary keys, check if this column is one of them
121
+ if model.primary_key.collect(&:to_sym).include?(column_name.to_sym)
122
+ return true
123
+ end
124
+ elsif column_name.to_sym == model.primary_key.to_sym
125
+ # If model has 1 primary key, check if this column is it
126
+ return true
127
+ end
128
+ end
129
+
130
+ false
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -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('%MODEL_NAME%', model_name)
10
- .gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize)
11
- .gsub('%TABLE_NAME%', table_name || model_name.pluralize)
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ module ForeignKeyAnnotation
6
+ autoload :AnnotationBuilder, "annotate_rb/model_annotator/foreign_key_annotation/annotation_builder"
7
+ end
8
+ end
9
+ end