annotate 3.0.3 → 3.1.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.
@@ -0,0 +1,19 @@
1
+ ## Prerequisite
2
+
3
+ - Install "git-flow" (`brew install git-flow`)
4
+ - Install "bump" gem (`gem install bump`)
5
+
6
+
7
+ ## Perform a release
8
+
9
+ - `git flow release start <release>`
10
+ - Update the `CHANGELOG.md` file
11
+ - `bump current`
12
+ - `bump patch`
13
+ - `rm -rf dist`
14
+ - `rake spec`
15
+ - `rake gem`
16
+ - `git flow release finish <release>`
17
+
18
+ - `rake gem:publish`
19
+
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  s.description = 'Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.'
14
14
  s.email = ['alex@stinky.com', 'cuong.tran@gmail.com', 'x@nofxx.com', 'turadg@aleahmad.net', 'jon@cloudability.com']
15
15
  s.executables = ['annotate']
16
- s.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.rdoc', 'TODO.rdoc']
17
- s.files = `git ls-files -z LICENSE.txt *.rdoc *.gemspec bin lib`.split("\x0")
16
+ s.extra_rdoc_files = ['README.md', 'CHANGELOG.md']
17
+ s.files = `git ls-files -z LICENSE.txt *.md *.gemspec bin lib`.split("\x0")
18
18
  s.homepage = 'http://github.com/ctran/annotate_models'
19
19
  s.licenses = ['Ruby']
20
20
  s.require_paths = ['lib']
@@ -26,7 +26,7 @@ exit if options_result[:exit]
26
26
  options = Annotate.setup_options(
27
27
  is_rake: ENV['is_rake'] && !ENV['is_rake'].empty?
28
28
  )
29
- Annotate.eager_load(options) if Annotate.include_models?
29
+ Annotate.eager_load(options) if Annotate::Helpers.include_models?
30
30
 
31
- AnnotateModels.send(options_result[:target_action], options) if Annotate.include_models?
32
- AnnotateRoutes.send(options_result[:target_action], options) if Annotate.include_routes?
31
+ AnnotateModels.send(options_result[:target_action], options) if Annotate::Helpers.include_models?
32
+ AnnotateRoutes.send(options_result[:target_action], options) if Annotate::Helpers.include_routes?
@@ -1,10 +1,9 @@
1
- # rubocop:disable Metrics/ModuleLength
2
-
3
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
4
2
  require 'annotate/version'
5
3
  require 'annotate/annotate_models'
6
4
  require 'annotate/annotate_routes'
7
5
  require 'annotate/constants'
6
+ require 'annotate/helpers'
8
7
 
9
8
  begin
10
9
  # ActiveSupport 3.x...
@@ -17,36 +16,6 @@ rescue StandardError
17
16
  end
18
17
 
19
18
  module Annotate
20
- ##
21
- # The set of available options to customize the behavior of Annotate.
22
- #
23
- POSITION_OPTIONS = [
24
- :position_in_routes, :position_in_class, :position_in_test,
25
- :position_in_fixture, :position_in_factory, :position,
26
- :position_in_serializer
27
- ].freeze
28
- FLAG_OPTIONS = [
29
- :show_indexes, :simple_indexes, :include_version, :exclude_tests,
30
- :exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
31
- :format_bare, :format_rdoc, :format_markdown, :sort, :force, :frozen,
32
- :trace, :timestamp, :exclude_serializers, :classified_sort,
33
- :show_foreign_keys, :show_complete_foreign_keys,
34
- :exclude_scaffolds, :exclude_controllers, :exclude_helpers,
35
- :exclude_sti_subclasses, :ignore_unknown_models, :with_comment
36
- ].freeze
37
- OTHER_OPTIONS = [
38
- :additional_file_patterns, :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close,
39
- :wrapper, :routes, :models, :hide_limit_column_types, :hide_default_column_types,
40
- :ignore_routes, :active_admin
41
- ].freeze
42
- PATH_OPTIONS = [
43
- :require, :model_dir, :root_dir
44
- ].freeze
45
-
46
- def self.all_options
47
- [POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS]
48
- end
49
-
50
19
  ##
51
20
  # Set default values that can be overridden via environment variables.
52
21
  #
@@ -54,9 +23,9 @@ module Annotate
54
23
  return if @has_set_defaults
55
24
  @has_set_defaults = true
56
25
 
57
- options = HashWithIndifferentAccess.new(options)
26
+ options = ActiveSupport::HashWithIndifferentAccess.new(options)
58
27
 
59
- all_options.flatten.each do |key|
28
+ Constants::ALL_ANNOTATE_OPTIONS.flatten.each do |key|
60
29
  if options.key?(key)
61
30
  default_value = if options[key].is_a?(Array)
62
31
  options[key].join(',')
@@ -74,69 +43,42 @@ module Annotate
74
43
  # TODO: what is the difference between this and set_defaults?
75
44
  #
76
45
  def self.setup_options(options = {})
77
- POSITION_OPTIONS.each do |key|
78
- options[key] = fallback(ENV[key.to_s], ENV['position'], 'before')
46
+ Constants::POSITION_OPTIONS.each do |key|
47
+ options[key] = Annotate::Helpers.fallback(ENV[key.to_s], ENV['position'], 'before')
79
48
  end
80
- FLAG_OPTIONS.each do |key|
81
- options[key] = true?(ENV[key.to_s])
49
+ Constants::FLAG_OPTIONS.each do |key|
50
+ options[key] = Annotate::Helpers.true?(ENV[key.to_s])
82
51
  end
83
- OTHER_OPTIONS.each do |key|
52
+ Constants::OTHER_OPTIONS.each do |key|
84
53
  options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s] : nil
85
54
  end
86
- PATH_OPTIONS.each do |key|
55
+ Constants::PATH_OPTIONS.each do |key|
87
56
  options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s].split(',') : []
88
57
  end
89
58
 
90
59
  options[:additional_file_patterns] ||= []
60
+ options[:additional_file_patterns] = options[:additional_file_patterns].split(',') if options[:additional_file_patterns].is_a?(String)
91
61
  options[:model_dir] = ['app/models'] if options[:model_dir].empty?
92
62
 
93
63
  options[:wrapper_open] ||= options[:wrapper]
94
64
  options[:wrapper_close] ||= options[:wrapper]
95
65
 
96
66
  # These were added in 2.7.0 but so this is to revert to old behavior by default
97
- options[:exclude_scaffolds] = Annotate.true?(ENV.fetch('exclude_scaffolds', 'true'))
98
- options[:exclude_controllers] = Annotate.true?(ENV.fetch('exclude_controllers', 'true'))
99
- options[:exclude_helpers] = Annotate.true?(ENV.fetch('exclude_helpers', 'true'))
67
+ options[:exclude_scaffolds] = Annotate::Helpers.true?(ENV.fetch('exclude_scaffolds', 'true'))
68
+ options[:exclude_controllers] = Annotate::Helpers.true?(ENV.fetch('exclude_controllers', 'true'))
69
+ options[:exclude_helpers] = Annotate::Helpers.true?(ENV.fetch('exclude_helpers', 'true'))
100
70
 
101
71
  options
102
72
  end
103
73
 
104
- def self.reset_options
105
- all_options.flatten.each { |key| ENV[key.to_s] = nil }
106
- end
107
-
108
- def self.skip_on_migration?
109
- ENV['ANNOTATE_SKIP_ON_DB_MIGRATE'] =~ Constants::TRUE_RE || ENV['skip_on_db_migrate'] =~ Constants::TRUE_RE
110
- end
111
-
112
- def self.include_routes?
113
- ENV['routes'] =~ Constants::TRUE_RE
114
- end
115
-
116
- def self.include_models?
117
- ENV['models'] =~ Constants::TRUE_RE
118
- end
119
-
120
- def self.loaded_tasks=(val)
121
- @loaded_tasks = val
122
- end
123
-
124
- def self.loaded_tasks
125
- @loaded_tasks
126
- end
127
-
128
74
  def self.load_tasks
129
- return if loaded_tasks
130
- self.loaded_tasks = true
75
+ return if @tasks_loaded
131
76
 
132
77
  Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each do |rake|
133
78
  load rake
134
79
  end
135
- end
136
80
 
137
- def self.load_requires(options)
138
- options[:require].count > 0 &&
139
- options[:require].each { |path| require path }
81
+ @tasks_loaded = true
140
82
  end
141
83
 
142
84
  def self.eager_load(options)
@@ -192,13 +134,12 @@ module Annotate
192
134
  Rake::Task[:set_annotation_options].invoke
193
135
  end
194
136
 
195
- def self.fallback(*args)
196
- args.detect { |arg| !arg.blank? }
197
- end
137
+ class << self
138
+ private
198
139
 
199
- def self.true?(val)
200
- return false if val.blank?
201
- return false unless val =~ Constants::TRUE_RE
202
- true
140
+ def load_requires(options)
141
+ options[:require].count > 0 &&
142
+ options[:require].each { |path| require path }
143
+ end
203
144
  end
204
145
  end
@@ -38,9 +38,9 @@ module AnnotateModels
38
38
  BLUEPRINTS_TEST_DIR = File.join('test', "blueprints")
39
39
  BLUEPRINTS_SPEC_DIR = File.join('spec', "blueprints")
40
40
 
41
- # Factory Girl http://github.com/thoughtbot/factory_girl
42
- FACTORY_GIRL_TEST_DIR = File.join('test', "factories")
43
- FACTORY_GIRL_SPEC_DIR = File.join('spec', "factories")
41
+ # Factory Girl https://github.com/thoughtbot/factory_bot
42
+ FACTORY_BOT_TEST_DIR = File.join('test', "factories")
43
+ FACTORY_BOT_SPEC_DIR = File.join('spec', "factories")
44
44
 
45
45
  # Fabrication https://github.com/paulelliott/fabrication.git
46
46
  FABRICATORS_TEST_DIR = File.join('test', "fabricators")
@@ -82,6 +82,8 @@ module AnnotateModels
82
82
  }
83
83
  }.freeze
84
84
 
85
+ 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
86
+
85
87
  class << self
86
88
  def annotate_pattern(options = {})
87
89
  if options[:wrapper_open]
@@ -140,12 +142,12 @@ module AnnotateModels
140
142
  File.join(root_directory, EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
141
143
  File.join(root_directory, BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
142
144
  File.join(root_directory, BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
143
- File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
144
- File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
145
- File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
146
- File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
147
- File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
148
- File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
145
+ File.join(root_directory, FACTORY_BOT_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
146
+ File.join(root_directory, FACTORY_BOT_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
147
+ File.join(root_directory, FACTORY_BOT_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
148
+ File.join(root_directory, FACTORY_BOT_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
149
+ File.join(root_directory, FACTORY_BOT_TEST_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
150
+ File.join(root_directory, FACTORY_BOT_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
149
151
  File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
150
152
  File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb")
151
153
  ]
@@ -154,7 +156,7 @@ module AnnotateModels
154
156
  def serialize_files(root_directory)
155
157
  [
156
158
  File.join(root_directory, SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
157
- File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_spec.rb"),
159
+ File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_test.rb"),
158
160
  File.join(root_directory, SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
159
161
  ]
160
162
  end
@@ -211,7 +213,7 @@ module AnnotateModels
211
213
  end
212
214
 
213
215
  def schema_default(klass, column)
214
- quote(klass.column_defaults[column.name])
216
+ quote(klass.columns.find { |x| x.name.to_s == column.name.to_s }.try(:default))
215
217
  end
216
218
 
217
219
  def retrieve_indexes_from_table(klass)
@@ -244,16 +246,7 @@ module AnnotateModels
244
246
  info << "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
245
247
  end
246
248
 
247
- cols = if ignore_columns = options[:ignore_columns]
248
- klass.columns.reject do |col|
249
- col.name.match(/#{ignore_columns}/)
250
- end
251
- else
252
- klass.columns
253
- end
254
-
255
- cols = cols.sort_by(&:name) if options[:sort]
256
- cols = classified_sort(cols) if options[:classified_sort]
249
+ cols = columns(klass, options)
257
250
  cols.each do |col|
258
251
  col_type = get_col_type(col)
259
252
  attrs = []
@@ -264,8 +257,8 @@ module AnnotateModels
264
257
 
265
258
  if col_type == 'decimal'
266
259
  col_type << "(#{col.precision}, #{col.scale})"
267
- elsif col_type != 'spatial'
268
- if col.limit
260
+ elsif !%w[spatial geometry geography].include?(col_type)
261
+ if col.limit && !options[:format_yard]
269
262
  if col.limit.is_a? Array
270
263
  attrs << "(#{col.limit.join(', ')})"
271
264
  else
@@ -304,8 +297,12 @@ module AnnotateModels
304
297
  end
305
298
  if options[:format_rdoc]
306
299
  info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col_name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
300
+ elsif options[:format_yard]
301
+ info << sprintf("# @!attribute #{col_name}") + "\n"
302
+ ruby_class = col.respond_to?(:array) && col.array ? "Array<#{map_col_type_to_ruby_classes(col_type)}>": map_col_type_to_ruby_classes(col_type)
303
+ info << sprintf("# @return [#{ruby_class}]") + "\n"
307
304
  elsif options[:format_markdown]
308
- name_remainder = max_size - col_name.length
305
+ name_remainder = max_size - col_name.length - non_ascii_length(col_name)
309
306
  type_remainder = (md_type_allowance - 2) - col_type.length
310
307
  info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col_name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n"
311
308
  else
@@ -472,7 +469,10 @@ module AnnotateModels
472
469
  foreign_keys = klass.connection.foreign_keys(klass.table_name)
473
470
  return '' if foreign_keys.empty?
474
471
 
475
- format_name = ->(fk) { options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, '...') }
472
+ format_name = lambda do |fk|
473
+ return fk.options[:column] if fk.name.blank?
474
+ options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, '...')
475
+ end
476
476
 
477
477
  max_size = foreign_keys.map(&format_name).map(&:size).max + 1
478
478
  foreign_keys.sort_by {|fk| [format_name.call(fk), fk.column]}.each do |fk|
@@ -515,7 +515,7 @@ module AnnotateModels
515
515
  old_header = old_content.match(header_pattern).to_s
516
516
  new_header = info_block.match(header_pattern).to_s
517
517
 
518
- column_pattern = /^#[\t ]+[\w\*`]+[\t ]+.+$/
518
+ column_pattern = /^#[\t ]+[\w\*\.`]+[\t ]+.+$/
519
519
  old_columns = old_header && old_header.scan(column_pattern).sort
520
520
  new_columns = new_header && new_header.scan(column_pattern).sort
521
521
 
@@ -534,15 +534,15 @@ module AnnotateModels
534
534
  # need to insert it in correct position
535
535
  if old_annotation.empty? || options[:force]
536
536
  magic_comments_block = magic_comments_as_string(old_content)
537
- old_content.gsub!(magic_comment_matcher, '')
537
+ old_content.gsub!(MAGIC_COMMENT_MATCHER, '')
538
538
  old_content.sub!(annotate_pattern(options), '')
539
539
 
540
540
  new_content = if %w(after bottom).include?(options[position].to_s)
541
541
  magic_comments_block + (old_content.rstrip + "\n\n" + wrapped_info_block)
542
542
  elsif magic_comments_block.empty?
543
- magic_comments_block + wrapped_info_block + "\n" + old_content
543
+ magic_comments_block + wrapped_info_block + old_content.lstrip
544
544
  else
545
- magic_comments_block + "\n" + wrapped_info_block + "\n" + old_content
545
+ magic_comments_block + "\n" + wrapped_info_block + old_content.lstrip
546
546
  end
547
547
  else
548
548
  # replace the old annotation with the new one
@@ -558,12 +558,8 @@ module AnnotateModels
558
558
  true
559
559
  end
560
560
 
561
- def magic_comment_matcher
562
- 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))/)
563
- end
564
-
565
561
  def magic_comments_as_string(content)
566
- magic_comments = content.scan(magic_comment_matcher).flatten.compact
562
+ magic_comments = content.scan(MAGIC_COMMENT_MATCHER).flatten.compact
567
563
 
568
564
  if magic_comments.any?
569
565
  magic_comments.join
@@ -902,13 +898,15 @@ module AnnotateModels
902
898
  end
903
899
 
904
900
  def max_schema_info_width(klass, options)
901
+ cols = columns(klass, options)
902
+
905
903
  if with_comments?(klass, options)
906
- max_size = klass.columns.map do |column|
904
+ max_size = cols.map do |column|
907
905
  column.name.size + (column.comment ? width(column.comment) : 0)
908
906
  end.max || 0
909
907
  max_size += 2
910
908
  else
911
- max_size = klass.column_names.map(&:size).max
909
+ max_size = cols.map(&:name).map(&:size).max
912
910
  end
913
911
  max_size += options[:format_rdoc] ? 5 : 1
914
912
 
@@ -920,7 +918,7 @@ module AnnotateModels
920
918
  end
921
919
 
922
920
  def width(string)
923
- string.chars.inject(0) { |acc, elem| acc + (elem.bytesize == 1 ? 1 : 2) }
921
+ string.chars.inject(0) { |acc, elem| acc + (elem.bytesize == 3 ? 2 : 1) }
924
922
  end
925
923
 
926
924
  def mb_chars_ljust(string, length)
@@ -932,6 +930,72 @@ module AnnotateModels
932
930
  string[0..length-1]
933
931
  end
934
932
  end
933
+
934
+ def non_ascii_length(string)
935
+ string.to_s.chars.reject(&:ascii_only?).length
936
+ end
937
+
938
+ def map_col_type_to_ruby_classes(col_type)
939
+ case col_type
940
+ when 'integer' then Integer.to_s
941
+ when 'float' then Float.to_s
942
+ when 'decimal' then BigDecimal.to_s
943
+ when 'datetime', 'timestamp', 'time' then Time.to_s
944
+ when 'date' then Date.to_s
945
+ when 'text', 'string', 'binary', 'inet', 'uuid' then String.to_s
946
+ when 'json', 'jsonb' then Hash.to_s
947
+ when 'boolean' then 'Boolean'
948
+ end
949
+ end
950
+
951
+ def columns(klass, options)
952
+ cols = klass.columns
953
+ cols += translated_columns(klass)
954
+
955
+ if ignore_columns = options[:ignore_columns]
956
+ cols = cols.reject do |col|
957
+ col.name.match(/#{ignore_columns}/)
958
+ end
959
+ end
960
+
961
+ cols = cols.sort_by(&:name) if options[:sort]
962
+ cols = classified_sort(cols) if options[:classified_sort]
963
+
964
+ cols
965
+ end
966
+
967
+ ##
968
+ # Add columns managed by the globalize gem if this gem is being used.
969
+ def translated_columns(klass)
970
+ return [] unless klass.respond_to? :translation_class
971
+
972
+ ignored_cols = ignored_translation_table_colums(klass)
973
+ klass.translation_class.columns.reject do |col|
974
+ ignored_cols.include? col.name.to_sym
975
+ end
976
+ end
977
+
978
+ ##
979
+ # These are the columns that the globalize gem needs to work but
980
+ # are not necessary for the models to be displayed as annotations.
981
+ def ignored_translation_table_colums(klass)
982
+ # Construct the foreign column name in the translations table
983
+ # eg. Model: Car, foreign column name: car_id
984
+ foreign_column_name = [
985
+ klass.translation_class.to_s
986
+ .gsub('::Translation', '').gsub('::', '_')
987
+ .downcase,
988
+ '_id'
989
+ ].join.to_sym
990
+
991
+ [
992
+ :id,
993
+ :created_at,
994
+ :updated_at,
995
+ :locale,
996
+ foreign_column_name
997
+ ]
998
+ end
935
999
  end
936
1000
 
937
1001
  class BadModelFileError < LoadError