annotaterb 4.0.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -5
  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_diff.rb +19 -0
  6. data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +44 -0
  7. data/lib/annotate_rb/model_annotator/annotation_generator.rb +94 -0
  8. data/lib/annotate_rb/model_annotator/annotator.rb +27 -31
  9. data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +92 -0
  10. data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +102 -0
  11. data/lib/annotate_rb/model_annotator/column_type_builder.rb +51 -0
  12. data/lib/annotate_rb/model_annotator/column_wrapper.rb +84 -0
  13. data/lib/annotate_rb/model_annotator/constants.rb +2 -2
  14. data/lib/annotate_rb/model_annotator/file_annotator.rb +9 -11
  15. data/lib/annotate_rb/model_annotator/file_annotator_instruction.rb +17 -0
  16. data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +55 -0
  17. data/lib/annotate_rb/model_annotator/helper.rb +75 -22
  18. data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +74 -0
  19. data/lib/annotate_rb/model_annotator/model_file_annotator.rb +21 -84
  20. data/lib/annotate_rb/model_annotator/model_files_getter.rb +4 -2
  21. data/lib/annotate_rb/model_annotator/model_wrapper.rb +155 -0
  22. data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +137 -0
  23. data/lib/annotate_rb/model_annotator.rb +13 -1
  24. data/lib/annotate_rb/options.rb +16 -9
  25. data/lib/annotate_rb/parser.rb +0 -14
  26. data/lib/annotate_rb.rb +0 -1
  27. metadata +15 -4
  28. data/lib/annotate_rb/env.rb +0 -30
  29. data/lib/annotate_rb/model_annotator/schema_info.rb +0 -480
@@ -1,8 +1,6 @@
1
1
  module AnnotateRb
2
2
  module ModelAnnotator
3
3
  module Constants
4
- TRUE_RE = /^(true|t|yes|y|1)$/i.freeze
5
-
6
4
  ##
7
5
  # The set of available options to customize the behavior of Annotate.
8
6
  #
@@ -17,6 +15,8 @@ module AnnotateRb
17
15
  ALL_ANNOTATE_OPTIONS = ::AnnotateRb::Options::ALL_OPTION_KEYS
18
16
 
19
17
  SKIP_ANNOTATION_PREFIX = '# -\*- SkipSchemaAnnotations'.freeze
18
+
19
+ 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
20
20
  end
21
21
  end
22
22
  end
@@ -4,6 +4,10 @@ module AnnotateRb
4
4
  module ModelAnnotator
5
5
  class FileAnnotator
6
6
  class << self
7
+ def call_with_instructions(instruction)
8
+ call(instruction.file, instruction.annotation, instruction.position, instruction.options)
9
+ end
10
+
7
11
  # Add a schema block to a file. If the file already contains
8
12
  # a schema info block (a comment starting with "== Schema Information"),
9
13
  # check if it matches the block that is already there. If so, leave it be.
@@ -20,20 +24,14 @@ module AnnotateRb
20
24
  def call(file_name, info_block, position, options = {})
21
25
  return false unless File.exist?(file_name)
22
26
  old_content = File.read(file_name)
23
- return false if old_content =~ /#{Constants::SKIP_ANNOTATION_PREFIX}.*\n/
24
27
 
25
- # Ignore the Schema version line because it changes with each migration
26
- header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/
27
- old_header = old_content.match(header_pattern).to_s
28
- new_header = info_block.match(header_pattern).to_s
28
+ return false if old_content =~ /#{Constants::SKIP_ANNOTATION_PREFIX}.*\n/
29
29
 
30
- column_pattern = /^#[\t ]+[\w\*\.`]+[\t ]+.+$/
31
- old_columns = old_header && old_header.scan(column_pattern).sort
32
- new_columns = new_header && new_header.scan(column_pattern).sort
30
+ diff = AnnotationDiffGenerator.new(old_content, info_block).generate
33
31
 
34
- return false if old_columns == new_columns && !options[:force]
32
+ return false if !diff.changed? && !options[:force]
35
33
 
36
- abort "annotate error. #{file_name} needs to be updated, but annotate was run with `--frozen`." if options[:frozen]
34
+ abort "AnnotateRb error. #{file_name} needs to be updated, but annotaterb was run with `--frozen`." if options[:frozen]
37
35
 
38
36
  # Replace inline the old schema info with the new schema info
39
37
  wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
@@ -47,7 +45,7 @@ module AnnotateRb
47
45
  # need to insert it in correct position
48
46
  if old_annotation.empty? || options[:force]
49
47
  magic_comments_block = Helper.magic_comments_as_string(old_content)
50
- old_content.gsub!(Annotator::MAGIC_COMMENT_MATCHER, '')
48
+ old_content.gsub!(Constants::MAGIC_COMMENT_MATCHER, '')
51
49
 
52
50
  annotation_pattern = AnnotationPatternGenerator.call(options)
53
51
  old_content.sub!(annotation_pattern, '')
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ # A plain old Ruby object (PORO) that contains all necessary information for FileAnnotator
6
+ class FileAnnotatorInstruction
7
+ def initialize(file, annotation, position, options = {})
8
+ @file = file # Path to file
9
+ @annotation = annotation # Annotation string
10
+ @position = position # Position in the file where to write the annotation to
11
+ @options = options
12
+ end
13
+
14
+ attr_reader :file, :annotation, :position, :options
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class ForeignKeyAnnotationBuilder
6
+ def initialize(model, options)
7
+ @model = model
8
+ @options = options
9
+ end
10
+
11
+ def build
12
+ fk_info = if @options[:format_markdown]
13
+ "#\n# ### Foreign Keys\n#\n"
14
+ else
15
+ "#\n# Foreign Keys\n#\n"
16
+ end
17
+
18
+ return '' unless @model.connection.respond_to?(:supports_foreign_keys?) &&
19
+ @model.connection.supports_foreign_keys? && @model.connection.respond_to?(:foreign_keys)
20
+
21
+ foreign_keys = @model.connection.foreign_keys(@model.table_name)
22
+ return '' if foreign_keys.empty?
23
+
24
+ format_name = lambda do |fk|
25
+ return fk.options[:column] if fk.name.blank?
26
+
27
+ @options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, '...')
28
+ end
29
+
30
+ max_size = foreign_keys.map(&format_name).map(&:size).max + 1
31
+ foreign_keys.sort_by { |fk| [format_name.call(fk), fk.column] }.each do |fk|
32
+ ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
33
+ constraints_info = ''
34
+ constraints_info += "ON DELETE => #{fk.on_delete} " if fk.on_delete
35
+ constraints_info += "ON UPDATE => #{fk.on_update} " if fk.on_update
36
+ constraints_info = constraints_info.strip
37
+
38
+ fk_info += if @options[:format_markdown]
39
+ format("# * `%s`%s:\n# * **`%s`**\n",
40
+ format_name.call(fk),
41
+ constraints_info.blank? ? '' : " (_#{constraints_info}_)",
42
+ ref_info)
43
+ else
44
+ format("# %-#{max_size}.#{max_size}s %s %s",
45
+ format_name.call(fk),
46
+ "(#{ref_info})",
47
+ constraints_info).rstrip + "\n"
48
+ end
49
+ end
50
+
51
+ fk_info
52
+ end
53
+ end
54
+ end
55
+ end
@@ -3,51 +3,104 @@
3
3
  module AnnotateRb
4
4
  module ModelAnnotator
5
5
  module Helper
6
- MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper).freeze
6
+ INDEX_CLAUSES = {
7
+ unique: {
8
+ default: 'UNIQUE',
9
+ markdown: '_unique_'
10
+ },
11
+ where: {
12
+ default: 'WHERE',
13
+ markdown: '_where_'
14
+ },
15
+ using: {
16
+ default: 'USING',
17
+ markdown: '_using_'
18
+ }
19
+ }.freeze
7
20
 
8
21
  class << self
9
- def matched_types(options)
10
- types = MATCHED_TYPES.dup
11
- types << 'admin' if options[:active_admin] =~ Constants::TRUE_RE && !types.include?('admin')
12
- types << 'additional_file_patterns' if options[:additional_file_patterns].present?
22
+ def mb_chars_ljust(string, length)
23
+ string = string.to_s
24
+ padding = length - Helper.width(string)
25
+ if padding.positive?
26
+ string + (' ' * padding)
27
+ else
28
+ string[0..(length - 1)]
29
+ end
30
+ end
13
31
 
14
- types
32
+ def index_unique_info(index, format = :default)
33
+ index.unique ? " #{INDEX_CLAUSES[:unique][format]}" : ''
15
34
  end
16
35
 
17
- def magic_comments_as_string(content)
18
- magic_comments = content.scan(Annotator::MAGIC_COMMENT_MATCHER).flatten.compact
36
+ def index_where_info(index, format = :default)
37
+ value = index.try(:where).try(:to_s)
38
+ if value.blank?
39
+ ''
40
+ else
41
+ " #{INDEX_CLAUSES[:where][format]} #{value}"
42
+ end
43
+ end
19
44
 
20
- if magic_comments.any?
21
- magic_comments.join
45
+ def index_using_info(index, format = :default)
46
+ value = index.try(:using) && index.using.try(:to_sym)
47
+ if !value.blank? && value != :btree
48
+ " #{INDEX_CLAUSES[:using][format]} #{value}"
22
49
  else
23
50
  ''
24
51
  end
25
52
  end
26
53
 
27
- def skip_on_migration?
28
- Env.read('ANNOTATE_SKIP_ON_DB_MIGRATE') =~ Constants::TRUE_RE || Env.read('skip_on_db_migrate') =~ Constants::TRUE_RE
54
+ def map_col_type_to_ruby_classes(col_type)
55
+ case col_type
56
+ when 'integer' then Integer.to_s
57
+ when 'float' then Float.to_s
58
+ when 'decimal' then BigDecimal.to_s
59
+ when 'datetime', 'timestamp', 'time' then Time.to_s
60
+ when 'date' then Date.to_s
61
+ when 'text', 'string', 'binary', 'inet', 'uuid' then String.to_s
62
+ when 'json', 'jsonb' then Hash.to_s
63
+ when 'boolean' then 'Boolean'
64
+ end
65
+ end
66
+
67
+ def non_ascii_length(string)
68
+ string.to_s.chars.reject(&:ascii_only?).length
29
69
  end
30
70
 
31
- def include_routes?
32
- Env.read('routes') =~ Constants::TRUE_RE
71
+ # Simple quoting for the default column value
72
+ def quote(value)
73
+ case value
74
+ when NilClass then 'NULL'
75
+ when TrueClass then 'TRUE'
76
+ when FalseClass then 'FALSE'
77
+ when Float, Integer then value.to_s
78
+ # BigDecimals need to be output in a non-normalized form and quoted.
79
+ when BigDecimal then value.to_s('F')
80
+ when Array then value.map { |v| quote(v) }
81
+ else
82
+ value.inspect
83
+ end
33
84
  end
34
85
 
35
- def include_models?
36
- Env.read('models') =~ Constants::TRUE_RE
86
+ def width(string)
87
+ string.chars.inject(0) { |acc, elem| acc + (elem.bytesize == 3 ? 2 : 1) }
37
88
  end
38
89
 
39
- def true?(val)
40
- val.present? && Constants::TRUE_RE.match?(val)
90
+ def magic_comments_as_string(content)
91
+ magic_comments = content.scan(Constants::MAGIC_COMMENT_MATCHER).flatten.compact
92
+
93
+ if magic_comments.any?
94
+ magic_comments.join
95
+ else
96
+ ''
97
+ end
41
98
  end
42
99
 
43
100
  # TODO: Find another implementation that doesn't depend on ActiveSupport
44
101
  def fallback(*args)
45
102
  args.compact.detect(&:present?)
46
103
  end
47
-
48
- def reset_options(options)
49
- options.flatten.each { |key| Env.write(key, nil) }
50
- end
51
104
  end
52
105
  end
53
106
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class IndexAnnotationBuilder
6
+ def initialize(model, options)
7
+ @model = model
8
+ @options = options
9
+ end
10
+
11
+ def build
12
+ index_info = if @options[:format_markdown]
13
+ "#\n# ### Indexes\n#\n"
14
+ else
15
+ "#\n# Indexes\n#\n"
16
+ end
17
+
18
+ indexes = @model.retrieve_indexes_from_table
19
+ return '' if indexes.empty?
20
+
21
+ max_size = indexes.collect { |index| index.name.size }.max + 1
22
+ indexes.sort_by(&:name).each do |index|
23
+ index_info += if @options[:format_markdown]
24
+ final_index_string_in_markdown(index)
25
+ else
26
+ final_index_string(index, max_size)
27
+ end
28
+ end
29
+
30
+ index_info
31
+ end
32
+
33
+ private
34
+
35
+ def final_index_string_in_markdown(index)
36
+ details = format(
37
+ '%s%s%s',
38
+ Helper.index_unique_info(index, :markdown),
39
+ Helper.index_where_info(index, :markdown),
40
+ Helper.index_using_info(index, :markdown)
41
+ ).strip
42
+ details = " (#{details})" unless details.blank?
43
+
44
+ format(
45
+ "# * `%s`%s:\n# * **`%s`**\n",
46
+ index.name,
47
+ details,
48
+ index_columns_info(index).join("`**\n# * **`")
49
+ )
50
+ end
51
+
52
+ def final_index_string(index, max_size)
53
+ format(
54
+ "# %-#{max_size}.#{max_size}s %s%s%s%s",
55
+ index.name,
56
+ "(#{index_columns_info(index).join(',')})",
57
+ Helper.index_unique_info(index),
58
+ Helper.index_where_info(index),
59
+ Helper.index_using_info(index)
60
+ ).rstrip + "\n"
61
+ end
62
+
63
+ def index_columns_info(index)
64
+ Array(index.columns).map do |col|
65
+ if index.try(:orders) && index.orders[col.to_s]
66
+ "#{col} #{index.orders[col.to_s].upcase}"
67
+ else
68
+ col.to_s.gsub("\r", '\r').gsub("\n", '\n')
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -2,36 +2,21 @@
2
2
 
3
3
  module AnnotateRb
4
4
  module ModelAnnotator
5
- # Not sure yet what the difference is between this and FileAnnotator
5
+ # Annotates a model file and its related files (controllers, factories, etc)
6
6
  class ModelFileAnnotator
7
7
  class << self
8
- def call(annotated, file, header, options)
8
+ def call(annotated, file, options)
9
9
  begin
10
- return false if /#{Constants::SKIP_ANNOTATION_PREFIX}.*/ =~ (File.exist?(file) ? File.read(file) : '')
11
10
  klass = ModelClassGetter.call(file, options)
12
11
 
13
- klass_is_a_class = klass.is_a?(Class)
14
- klass_inherits_active_record_base = klass < ActiveRecord::Base
15
- klass_is_not_abstract = !klass.abstract_class?
16
- klass_table_exists = klass.table_exists?
17
-
18
- not_sure_this_conditional = (!options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name))
19
-
20
- annotate_conditions = [
21
- klass_is_a_class,
22
- klass_inherits_active_record_base,
23
- not_sure_this_conditional,
24
- klass_is_not_abstract,
25
- klass_table_exists
26
- ]
27
-
28
- do_annotate = annotate_conditions.all?
29
-
30
- if do_annotate
31
- files_annotated = annotate(klass, file, header, options)
32
- annotated.concat(files_annotated)
12
+ instructions = build_instructions(klass, file, options)
13
+ instructions.each do |instruction|
14
+ if FileAnnotator.call_with_instructions(instruction)
15
+ annotated << instruction.file
16
+ end
33
17
  end
34
18
 
19
+ annotated
35
20
  rescue BadModelFileError => e
36
21
  unless options[:ignore_unknown_models]
37
22
  $stderr.puts "Unable to annotate #{file}: #{e.message}"
@@ -45,72 +30,24 @@ module AnnotateRb
45
30
 
46
31
  private
47
32
 
48
- # Given the name of an ActiveRecord class, create a schema
49
- # info block (basically a comment containing information
50
- # on the columns and their types) and put it at the front
51
- # of the model and fixture source files.
52
- #
53
- # === Options (opts)
54
- # :position_in_class<Symbol>:: where to place the annotated section in model file
55
- # :position_in_test<Symbol>:: where to place the annotated section in test/spec file(s)
56
- # :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
57
- # :position_in_factory<Symbol>:: where to place the annotated section in factory file
58
- # :position_in_serializer<Symbol>:: where to place the annotated section in serializer file
59
- # :exclude_tests<Symbol>:: whether to skip modification of test/spec files
60
- # :exclude_fixtures<Symbol>:: whether to skip modification of fixture files
61
- # :exclude_factories<Symbol>:: whether to skip modification of factory files
62
- # :exclude_serializers<Symbol>:: whether to skip modification of serializer files
63
- # :exclude_scaffolds<Symbol>:: whether to skip modification of scaffold files
64
- # :exclude_controllers<Symbol>:: whether to skip modification of controller files
65
- # :exclude_helpers<Symbol>:: whether to skip modification of helper files
66
- # :exclude_sti_subclasses<Symbol>:: whether to skip modification of files for STI subclasses
67
- #
68
- # == Returns:
69
- # an array of file names that were annotated.
70
- #
71
- def annotate(klass, file, header, options = {})
72
- begin
73
- klass.reset_column_information
74
- info = SchemaInfo.generate(klass, header, options)
75
- model_name = klass.name.underscore
76
- table_name = klass.table_name
77
- model_file_name = File.join(file)
78
- annotated = []
79
-
80
- if FileAnnotator.call(model_file_name, info, :position_in_class, options)
81
- annotated << model_file_name
82
- end
83
-
84
- Helper.matched_types(options).each do |key|
85
- exclusion_key = "exclude_#{key.pluralize}".to_sym
86
- position_key = "position_in_#{key}".to_sym
87
-
88
- # Same options for active_admin models
89
- if key == 'admin'
90
- exclusion_key = 'exclude_class'.to_sym
91
- position_key = 'position_in_class'.to_sym
92
- end
33
+ def build_instructions(klass, file, options = {})
34
+ instructions = []
93
35
 
94
- next if options[exclusion_key]
36
+ klass.reset_column_information
37
+ annotation = AnnotationGenerator.new(klass, options).generate
38
+ model_name = klass.name.underscore
39
+ table_name = klass.table_name
95
40
 
96
- patterns = PatternGetter.call(options, key)
41
+ model_instruction = FileAnnotatorInstruction.new(file, annotation, :position_in_class, options)
42
+ instructions << model_instruction
97
43
 
98
- patterns
99
- .map { |f| FileNameResolver.call(f, model_name, table_name) }
100
- .map { |f| Dir.glob(f) }
101
- .flatten
102
- .each do |f|
103
- if FileAnnotator.call(f, info, position_key, options)
104
- annotated << f
105
- end
106
- end
107
- end
108
- rescue StandardError => e
109
- $stderr.puts "Unable to annotate #{file}: #{e.message}"
110
- $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
44
+ related_files = RelatedFilesListBuilder.new(file, model_name, table_name, options).build
45
+ related_file_instructions = related_files.map do |f, position_key|
46
+ _instruction = FileAnnotatorInstruction.new(f, annotation, position_key, options)
111
47
  end
48
+ instructions.concat(related_file_instructions)
112
49
 
113
- annotated
50
+ instructions
114
51
  end
115
52
  end
116
53
  end
@@ -11,6 +11,8 @@ module AnnotateRb
11
11
  def call(options)
12
12
  model_files = []
13
13
 
14
+ # Note: This is currently broken as we don't set `is_rake` anywhere.
15
+ # It's an artifact from the old Annotate gem and how it did control flow.
14
16
  model_files = list_model_files_from_argument(options) if !options[:is_rake]
15
17
 
16
18
  return model_files if !model_files.empty?
@@ -30,7 +32,7 @@ module AnnotateRb
30
32
  rescue SystemCallError
31
33
  $stderr.puts "No models found in directory '#{options[:model_dir].join("', '")}'."
32
34
  $stderr.puts "Either specify models on the command line, or use the --model-dir option."
33
- $stderr.puts "Call 'annotate --help' for more info."
35
+ $stderr.puts "Call 'annotaterb --help' for more info."
34
36
  # exit 1 # TODO: Return exit code back to caller. Right now it messes up RSpec being able to run
35
37
  end
36
38
 
@@ -50,7 +52,7 @@ module AnnotateRb
50
52
 
51
53
  if model_files.size != specified_files.size
52
54
  $stderr.puts "The specified file could not be found in directory '#{options[:model_dir].join("', '")}'."
53
- $stderr.puts "Call 'annotate --help' for more info."
55
+ $stderr.puts "Call 'annotaterb --help' for more info."
54
56
  # exit 1 # TODO: Return exit code back to caller. Right now it messes up RSpec being able to run
55
57
  end
56
58
 
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class ModelWrapper
6
+ # Should be the wrapper for an ActiveRecord model that serves as the source of truth of the model
7
+ # of the model that we're annotating
8
+
9
+ def initialize(klass, options = {})
10
+ @klass = klass
11
+ @options = options
12
+ end
13
+
14
+ # Gets the columns of the ActiveRecord model, processes them, and then returns them.
15
+ def columns
16
+ @columns ||= begin
17
+ cols = raw_columns
18
+ cols += translated_columns
19
+
20
+ ignore_columns = @options[:ignore_columns]
21
+ if ignore_columns
22
+ cols = cols.reject do |col|
23
+ col.name.match(/#{ignore_columns}/)
24
+ end
25
+ end
26
+
27
+ cols = cols.sort_by(&:name) if @options[:sort]
28
+ cols = classified_sort(cols) if @options[:classified_sort]
29
+
30
+ cols
31
+ end
32
+ end
33
+
34
+ def connection
35
+ @klass.connection
36
+ end
37
+
38
+ # Returns the unmodified model columns
39
+ def raw_columns
40
+ @raw_columns ||= @klass.columns
41
+ end
42
+
43
+ def primary_key
44
+ @klass.primary_key
45
+ end
46
+
47
+ def table_exists?
48
+ @klass.table_exists?
49
+ end
50
+
51
+ def column_defaults
52
+ @klass.column_defaults
53
+ end
54
+
55
+ # Add columns managed by the globalize gem if this gem is being used.
56
+ # TODO: Audit if this is still needed, it seems like Globalize gem is no longer maintained
57
+ def translated_columns
58
+ return [] unless @klass.respond_to?(:translation_class)
59
+
60
+ ignored_cols = ignored_translation_table_columns
61
+
62
+ @klass.translation_class.columns.reject do |col|
63
+ ignored_cols.include? col.name.to_sym
64
+ end
65
+ end
66
+
67
+ def table_name
68
+ @klass.table_name
69
+ end
70
+
71
+ def model_name
72
+ @klass.name.underscore
73
+ end
74
+
75
+ # Calculates the max width of the schema for the model by looking at the columns, schema comments, with respect
76
+ # to the options.
77
+ def max_schema_info_width
78
+ cols = columns
79
+
80
+ if with_comments?
81
+ max_size = cols.map do |column|
82
+ column.name.size + (column.comment ? Helper.width(column.comment) : 0)
83
+ end.max || 0
84
+ max_size += 2
85
+ else
86
+ max_size = cols.map(&:name).map(&:size).max
87
+ end
88
+ max_size += @options[:format_rdoc] ? 5 : 1
89
+
90
+ max_size
91
+ end
92
+
93
+ def retrieve_indexes_from_table
94
+ table_name = @klass.table_name
95
+ return [] unless table_name
96
+
97
+ indexes = @klass.connection.indexes(table_name)
98
+ return indexes if indexes.any? || !@klass.table_name_prefix
99
+
100
+ # Try to search the table without prefix
101
+ table_name_without_prefix = table_name.to_s.sub(@klass.table_name_prefix, '')
102
+ @klass.connection.indexes(table_name_without_prefix)
103
+ end
104
+
105
+ def with_comments?
106
+ @options[:with_comment] &&
107
+ raw_columns.first.respond_to?(:comment) &&
108
+ raw_columns.map(&:comment).any? { |comment| !comment.nil? }
109
+ end
110
+
111
+ def classified_sort(cols)
112
+ rest_cols = []
113
+ timestamps = []
114
+ associations = []
115
+ id = nil
116
+
117
+ cols.each do |c|
118
+ if c.name.eql?('id')
119
+ id = c
120
+ elsif c.name.eql?('created_at') || c.name.eql?('updated_at')
121
+ timestamps << c
122
+ elsif c.name[-3, 3].eql?('_id')
123
+ associations << c
124
+ else
125
+ rest_cols << c
126
+ end
127
+ end
128
+ [rest_cols, timestamps, associations].each { |a| a.sort_by!(&:name) }
129
+
130
+ ([id] << rest_cols << timestamps << associations).flatten.compact
131
+ end
132
+
133
+ # These are the columns that the globalize gem needs to work but
134
+ # are not necessary for the models to be displayed as annotations.
135
+ def ignored_translation_table_columns
136
+ # Construct the foreign column name in the translations table
137
+ # eg. Model: Car, foreign column name: car_id
138
+ foreign_column_name = [
139
+ @klass.translation_class.to_s
140
+ .gsub('::Translation', '').gsub('::', '_')
141
+ .downcase,
142
+ '_id'
143
+ ].join.to_sym
144
+
145
+ [
146
+ :id,
147
+ :created_at,
148
+ :updated_at,
149
+ :locale,
150
+ foreign_column_name
151
+ ]
152
+ end
153
+ end
154
+ end
155
+ end