annotaterb 4.0.0.beta.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +91 -1
  3. data/VERSION +1 -1
  4. data/lib/annotate_rb/model_annotator/annotation_decider.rb +62 -0
  5. data/lib/annotate_rb/model_annotator/annotation_generator.rb +94 -0
  6. data/lib/annotate_rb/model_annotator/annotator.rb +27 -31
  7. data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +92 -0
  8. data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +102 -0
  9. data/lib/annotate_rb/model_annotator/column_type_builder.rb +51 -0
  10. data/lib/annotate_rb/model_annotator/column_wrapper.rb +84 -0
  11. data/lib/annotate_rb/model_annotator/constants.rb +2 -2
  12. data/lib/annotate_rb/model_annotator/file_annotator.rb +6 -2
  13. data/lib/annotate_rb/model_annotator/file_annotator_instruction.rb +17 -0
  14. data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +55 -0
  15. data/lib/annotate_rb/model_annotator/helper.rb +75 -22
  16. data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +74 -0
  17. data/lib/annotate_rb/model_annotator/model_file_annotator.rb +21 -84
  18. data/lib/annotate_rb/model_annotator/model_files_getter.rb +4 -2
  19. data/lib/annotate_rb/model_annotator/model_wrapper.rb +155 -0
  20. data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +137 -0
  21. data/lib/annotate_rb/model_annotator.rb +11 -1
  22. data/lib/annotate_rb/options.rb +16 -9
  23. data/lib/annotate_rb/parser.rb +1 -15
  24. data/lib/annotate_rb.rb +0 -1
  25. data/lib/generators/annotate_rb/install/USAGE +7 -0
  26. data/lib/generators/annotate_rb/install/install_generator.rb +14 -0
  27. metadata +18 -9
  28. data/lib/annotate_rb/env.rb +0 -30
  29. data/lib/annotate_rb/model_annotator/schema_info.rb +0 -480
  30. data/lib/generators/annotate_rb/USAGE +0 -4
  31. data/lib/generators/annotate_rb/install_generator.rb +0 -15
  32. /data/lib/generators/annotate_rb/{templates/auto_annotate_models.rake → install/templates/annotate_rb.rake} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ca3a25c118bfa3f5910e4fe455de3d9333c353d409f434cffde12694f87cff5
4
- data.tar.gz: 417dcf943056ec0f44ba7eb973c3ad2dc99cb435315d41a13d1fc9f53bb87b9e
3
+ metadata.gz: 1c77ca5a809b63c94e69b937732b8f8f531807cc332627791b649576e8ed583e
4
+ data.tar.gz: c2fb3ac84bad2e0cd108c801ca162a6a17b34ba800af8f5e885e03c8338d636d
5
5
  SHA512:
6
- metadata.gz: 2d34ca80e5686ef6cadfe0a2624aecd891fde3628653e68fa8b86613ac6382a4d7bdcf8e0c7504988dd653023be3d1190c5a072d46890b9c874ecaf1abbc20f7
7
- data.tar.gz: 2ae8ca4a18c83437b379f7823291c094a87db9465742bf2c1c94c99fcfe1cb833f894367774a44ff2a9e7c8f41f9670f8baeedff1d5832bbbf2c49a6ced8ac80
6
+ metadata.gz: fb88fa8b511e0ccbbadce0db7d0ae941234967eb7358fea1146e48309939b1b35e51f8db9f15d544a6af4b3827decb65e51f4546d73feb2ab2f404c67f3c0f05
7
+ data.tar.gz: 562cd430feba785e8f53ab90f79dfa1c3765f46a8e048bbe379955c8f696dd4e01ce167245cf17e31de4704dc0e22da859cba2f6f00c87d10782e637c2faad65
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  ## AnnotateRb
2
2
  ### forked from the [Annotate aka AnnotateModels gem](https://github.com/ctran/annotate_models)
3
3
 
4
+ A Ruby Gem that adds annotations to your Rails models and route files.
5
+
4
6
  ----------
5
7
  [![CI](https://github.com/drwl/annotaterb/actions/workflows/ci.yml/badge.svg)](https://github.com/drwl/annotaterb/actions/workflows/ci.yml)
8
+ [![Gem Version](https://badge.fury.io/rb/annotaterb.svg)](https://badge.fury.io/rb/annotaterb)
6
9
 
7
10
  Adds comments summarizing the model schema or routes in your:
8
11
 
@@ -57,8 +60,10 @@ $ bin/rails g annotate_rb:install
57
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.
58
61
 
59
62
  ## Migrating from the annotate gem
63
+ The old [annotate gem](https://github.com/ctran/annotate_models) relied on environment variables and hardcoded values in rake files for configuration. AnnotateRb is different. It reads from an optional configuration yml file and options from the CLI to function.
60
64
 
61
- Add steps for migrating from annotate gem.
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.
62
67
 
63
68
  ## Usage
64
69
 
@@ -67,10 +72,95 @@ AnnotateRb has a CLI that you can use to add or remove annotations.
67
72
  ```sh
68
73
  # To show the CLI options
69
74
  $ bundle exec annotaterb
75
+
76
+ Usage: annotaterb [command] [options]
77
+
78
+ Commands:
79
+ models [options]
80
+ routes [options]
81
+ help
82
+ version
83
+
84
+ Options:
85
+ -v, --version Display the version..
86
+ -h, --help You're looking at it.
87
+
88
+ Annotate model options:
89
+ Usage: annotaterb models [options]
90
+
91
+ -a, --active-admin Annotate active_admin models
92
+ --show-migration Include the migration version number in the annotation
93
+ -k, --show-foreign-keys List the table's foreign key constraints in the annotation
94
+ --ck, --complete-foreign-keys
95
+ Complete foreign key names in the annotation
96
+ -i, --show-indexes List the table's database indexes in the annotation
97
+ -s, --simple-indexes Concat the column's related indexes in the annotation
98
+ --hide-limit-column-types VALUES
99
+ don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)
100
+ --hide-default-column-types VALUES
101
+ don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)
102
+ --ignore-unknown-models don't display warnings for bad model files
103
+ -I, --ignore-columns REGEX don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`
104
+ --with-comment include database comments in model annotations
105
+
106
+ Annotate routes options:
107
+ Usage: annotaterb routes [options]
108
+
109
+ --ignore-routes REGEX don't annotate routes that match a given REGEX (i.e., `annotate -I '(mobile|resque|pghero)'`
110
+ --timestamp Include timestamp in (routes) annotation
111
+ --w, --wrapper STR Wrap annotation with the text passed as parameter.
112
+ If --w option is used, the same text will be used as opening and closing
113
+ --wo, --wrapper-open STR Annotation wrapper opening.
114
+ --wc, --wrapper-close STR Annotation wrapper closing
115
+
116
+ Command options:
117
+ Additional options that work for annotating models and routes
118
+
119
+ --additional-file-patterns path1,path2,path3
120
+ Additional file paths or globs to annotate, separated by commas (e.g. `/foo/bar/%model_name%/*.rb,/baz/%model_name%.rb`)
121
+ -d, --delete Remove annotations from all model files or the routes.rb file
122
+ --model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with commas
123
+ --root-dir dir Annotate files stored within root dir projects, separate multiple dirs with commas
124
+ --ignore-model-subdirects Ignore subdirectories of the models directory
125
+ --sort Sort columns alphabetically, rather than in creation order
126
+ --classified-sort Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns
127
+ -R, --require path Additional file to require before loading models, may be used multiple times
128
+ -e [tests,fixtures,factories,serializers],
129
+ --exclude Do not annotate fixtures, test files, factories, and/or serializers
130
+ -f [bare|rdoc|yard|markdown], Render Schema Information as plain/RDoc/Yard/Markdown
131
+ --format
132
+ -p [before|top|after|bottom], Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)
133
+ --position
134
+ --pc, --position-in-class [before|top|after|bottom]
135
+ Place the annotations at the top (before) or the bottom (after) of the model file
136
+ --pf, --position-in-factory [before|top|after|bottom]
137
+ Place the annotations at the top (before) or the bottom (after) of any factory files
138
+ --px, --position-in-fixture [before|top|after|bottom]
139
+ Place the annotations at the top (before) or the bottom (after) of any fixture files
140
+ --pt, --position-in-test [before|top|after|bottom]
141
+ Place the annotations at the top (before) or the bottom (after) of any test files
142
+ --pr, --position-in-routes [before|top|after|bottom]
143
+ Place the annotations at the top (before) or the bottom (after) of the routes.rb file
144
+ --ps, --position-in-serializer [before|top|after|bottom]
145
+ Place the annotations at the top (before) or the bottom (after) of the serializer files
146
+ --force Force new annotations even if there are no changes.
147
+ --debug Prints the options and outputs messages to make it easier to debug.
148
+ --frozen Do not allow to change annotations. Exits non-zero if there are going to be changes to files.
149
+ --trace If unable to annotate a file, print the full stack trace, not just the exception message.
70
150
  ```
71
151
 
72
152
  ## Configuration
73
153
 
154
+ ### Storing default options
155
+ 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.
156
+
157
+ ```yml
158
+ # .annotaterb.yml
159
+
160
+ position: after
161
+ ```
162
+
163
+ Annotaterb reads first from the configuration file, if it exists, then merges it with any options passed into the CLI.
74
164
 
75
165
  ### How to skip annotating a particular model
76
166
  If you want to always skip annotations on a particular model, add this string
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.0.0.beta.1
1
+ 4.1.0
@@ -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,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
- # require 'bigdecimal'
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
- model_files_to_annotate.each do |path, filename|
28
- ModelFileAnnotator.call(annotated, File.join(path, filename), header, options)
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
- deannotated_klass = false
41
- ModelFilesGetter.call(options).each do |file|
42
- file = File.join(file)
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
- patterns = PatternGetter.call(options)
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
- patterns
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
- deannotated << klass if deannotated_klass
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