lib/annotate/annotate_models.rb in annotate-2.7.1 vs lib/annotate/annotate_models.rb in annotate-2.7.2
- old
+ new
@@ -1,69 +1,71 @@
+# rubocop:disable Metrics/ModuleLength
+
require 'bigdecimal'
module AnnotateModels
TRUE_RE = /^(true|t|yes|y|1)$/i
# Annotate Models plugin use this header
- COMPAT_PREFIX = "== Schema Info"
- COMPAT_PREFIX_MD = "## Schema Info"
- PREFIX = "== Schema Information"
- PREFIX_MD = "## Schema Information"
- END_MARK = "== Schema Information End"
+ COMPAT_PREFIX = '== Schema Info'.freeze
+ COMPAT_PREFIX_MD = '## Schema Info'.freeze
+ PREFIX = '== Schema Information'.freeze
+ PREFIX_MD = '## Schema Information'.freeze
+ END_MARK = '== Schema Information End'.freeze
- MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper)
+ MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper).freeze
# File.join for windows reverse bar compat?
# I dont use windows, can`t test
- UNIT_TEST_DIR = File.join("test", "unit")
- MODEL_TEST_DIR = File.join("test", "models") # since rails 4.0
- SPEC_MODEL_DIR = File.join("spec", "models")
- FIXTURE_TEST_DIR = File.join("test", "fixtures")
- FIXTURE_SPEC_DIR = File.join("spec", "fixtures")
+ UNIT_TEST_DIR = File.join('test', "unit")
+ MODEL_TEST_DIR = File.join('test', "models") # since rails 4.0
+ SPEC_MODEL_DIR = File.join('spec', "models")
+ FIXTURE_TEST_DIR = File.join('test', "fixtures")
+ FIXTURE_SPEC_DIR = File.join('spec', "fixtures")
# Other test files
- CONTROLLER_TEST_DIR = File.join("test", "controllers")
- CONTROLLER_SPEC_DIR = File.join("spec", "controllers")
- REQUEST_SPEC_DIR = File.join("spec", "requests")
- ROUTING_SPEC_DIR = File.join("spec", "routing")
+ CONTROLLER_TEST_DIR = File.join('test', "controllers")
+ CONTROLLER_SPEC_DIR = File.join('spec', "controllers")
+ REQUEST_SPEC_DIR = File.join('spec', "requests")
+ ROUTING_SPEC_DIR = File.join('spec', "routing")
# Object Daddy http://github.com/flogic/object_daddy/tree/master
- EXEMPLARS_TEST_DIR = File.join("test", "exemplars")
- EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars")
+ EXEMPLARS_TEST_DIR = File.join('test', "exemplars")
+ EXEMPLARS_SPEC_DIR = File.join('spec', "exemplars")
# Machinist http://github.com/notahat/machinist
- BLUEPRINTS_TEST_DIR = File.join("test", "blueprints")
- BLUEPRINTS_SPEC_DIR = File.join("spec", "blueprints")
+ BLUEPRINTS_TEST_DIR = File.join('test', "blueprints")
+ BLUEPRINTS_SPEC_DIR = File.join('spec', "blueprints")
# Factory Girl http://github.com/thoughtbot/factory_girl
- FACTORY_GIRL_TEST_DIR = File.join("test", "factories")
- FACTORY_GIRL_SPEC_DIR = File.join("spec", "factories")
+ FACTORY_GIRL_TEST_DIR = File.join('test', "factories")
+ FACTORY_GIRL_SPEC_DIR = File.join('spec', "factories")
# Fabrication https://github.com/paulelliott/fabrication.git
- FABRICATORS_TEST_DIR = File.join("test", "fabricators")
- FABRICATORS_SPEC_DIR = File.join("spec", "fabricators")
+ FABRICATORS_TEST_DIR = File.join('test', "fabricators")
+ FABRICATORS_SPEC_DIR = File.join('spec', "fabricators")
# Serializers https://github.com/rails-api/active_model_serializers
- SERIALIZERS_DIR = File.join("app", "serializers")
- SERIALIZERS_TEST_DIR = File.join("test", "serializers")
- SERIALIZERS_SPEC_DIR = File.join("spec", "serializers")
+ SERIALIZERS_DIR = File.join('app', "serializers")
+ SERIALIZERS_TEST_DIR = File.join('test', "serializers")
+ SERIALIZERS_SPEC_DIR = File.join('spec', "serializers")
# Controller files
- CONTROLLER_DIR = File.join("app", "controllers")
+ CONTROLLER_DIR = File.join('app', "controllers")
# Active admin registry files
- ACTIVEADMIN_DIR = File.join("app", "admin")
+ ACTIVEADMIN_DIR = File.join('app', "admin")
# Helper files
- HELPER_DIR = File.join("app", "helpers")
+ HELPER_DIR = File.join('app', "helpers")
# Don't show limit (#) on these column types
# Example: show "integer" instead of "integer(4)"
- NO_LIMIT_COL_TYPES = %w(integer boolean)
+ NO_LIMIT_COL_TYPES = %w(integer boolean).freeze
# Don't show default value for these column types
- NO_DEFAULT_COL_TYPES = %w(json jsonb)
+ NO_DEFAULT_COL_TYPES = %w(json jsonb hstore).freeze
class << self
def annotate_pattern(options = {})
if options[:wrapper_open]
return /(?:^\n?# (?:#{options[:wrapper_open]}).*\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*)|^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*/
@@ -76,136 +78,149 @@
end
attr_writer :model_dir
def root_dir
- @root_dir.is_a?(Array) ? @root_dir : [@root_dir || '']
+ if @root_dir.blank?
+ ['']
+ elsif @root_dir.is_a?(String)
+ @root_dir.split(',')
+ else
+ @root_dir
+ end
end
attr_writer :root_dir
def test_files(root_directory)
[
- File.join(root_directory, UNIT_TEST_DIR, "%MODEL_NAME%_test.rb"),
- File.join(root_directory, MODEL_TEST_DIR, "%MODEL_NAME%_test.rb"),
- File.join(root_directory, SPEC_MODEL_DIR, "%MODEL_NAME%_spec.rb")
+ File.join(root_directory, UNIT_TEST_DIR, "%MODEL_NAME%_test.rb"),
+ File.join(root_directory, MODEL_TEST_DIR, "%MODEL_NAME%_test.rb"),
+ File.join(root_directory, SPEC_MODEL_DIR, "%MODEL_NAME%_spec.rb")
]
end
def fixture_files(root_directory)
[
- File.join(root_directory, FIXTURE_TEST_DIR, "%TABLE_NAME%.yml"),
- File.join(root_directory, FIXTURE_SPEC_DIR, "%TABLE_NAME%.yml"),
- File.join(root_directory, FIXTURE_TEST_DIR, "%PLURALIZED_MODEL_NAME%.yml"),
- File.join(root_directory, FIXTURE_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.yml")
+ File.join(root_directory, FIXTURE_TEST_DIR, "%TABLE_NAME%.yml"),
+ File.join(root_directory, FIXTURE_SPEC_DIR, "%TABLE_NAME%.yml"),
+ File.join(root_directory, FIXTURE_TEST_DIR, "%PLURALIZED_MODEL_NAME%.yml"),
+ File.join(root_directory, FIXTURE_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.yml")
]
end
def scaffold_files(root_directory)
[
- File.join(root_directory, CONTROLLER_TEST_DIR, "%PLURALIZED_MODEL_NAME%_controller_test.rb"),
- File.join(root_directory, CONTROLLER_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_controller_spec.rb"),
- File.join(root_directory, REQUEST_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_spec.rb"),
- File.join(root_directory, ROUTING_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_routing_spec.rb")
+ File.join(root_directory, CONTROLLER_TEST_DIR, "%PLURALIZED_MODEL_NAME%_controller_test.rb"),
+ File.join(root_directory, CONTROLLER_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_controller_spec.rb"),
+ File.join(root_directory, REQUEST_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_spec.rb"),
+ File.join(root_directory, ROUTING_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_routing_spec.rb")
]
end
def factory_files(root_directory)
[
- File.join(root_directory, EXEMPLARS_TEST_DIR, "%MODEL_NAME%_exemplar.rb"),
- File.join(root_directory, EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
- File.join(root_directory, BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
- File.join(root_directory, BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
- File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
- File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
- File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
- File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
- File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
- File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb")
+ File.join(root_directory, EXEMPLARS_TEST_DIR, "%MODEL_NAME%_exemplar.rb"),
+ File.join(root_directory, EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
+ File.join(root_directory, BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
+ File.join(root_directory, BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
+ File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
+ File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
+ File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
+ File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
+ File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
+ File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb")
]
end
def serialize_files(root_directory)
[
- File.join(root_directory, SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
- File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_spec.rb"),
- File.join(root_directory, SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
+ File.join(root_directory, SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
+ File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_spec.rb"),
+ File.join(root_directory, SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
]
end
def files_by_pattern(root_directory, pattern_type)
case pattern_type
- when 'test' then test_files(root_directory)
- when 'fixture' then fixture_files(root_directory)
- when 'scaffold' then scaffold_files(root_directory)
- when 'factory' then factory_files(root_directory)
- when 'serializer' then serialize_files(root_directory)
- when 'controller'
- [File.join(root_directory, CONTROLLER_DIR, "%PLURALIZED_MODEL_NAME%_controller.rb")]
- when 'admin'
- [File.join(root_directory, ACTIVEADMIN_DIR, "%MODEL_NAME%.rb")]
- when 'helper'
- [File.join(root_directory, HELPER_DIR, "%PLURALIZED_MODEL_NAME%_helper.rb")]
- else
- []
+ when 'test' then test_files(root_directory)
+ when 'fixture' then fixture_files(root_directory)
+ when 'scaffold' then scaffold_files(root_directory)
+ when 'factory' then factory_files(root_directory)
+ when 'serializer' then serialize_files(root_directory)
+ when 'controller'
+ [File.join(root_directory, CONTROLLER_DIR, "%PLURALIZED_MODEL_NAME%_controller.rb")]
+ when 'admin'
+ [File.join(root_directory, ACTIVEADMIN_DIR, "%MODEL_NAME%.rb")]
+ when 'helper'
+ [File.join(root_directory, HELPER_DIR, "%PLURALIZED_MODEL_NAME%_helper.rb")]
+ else
+ []
end
end
- def get_patterns(pattern_types=[])
+ def get_patterns(pattern_types = [])
current_patterns = []
root_dir.each do |root_directory|
Array(pattern_types).each do |pattern_type|
current_patterns += files_by_pattern(root_directory, pattern_type)
end
end
- current_patterns.map{ |p| p.sub(/^[\/]*/, '') }
+ current_patterns.map { |p| p.sub(/^[\/]*/, '') }
end
# Simple quoting for the default column value
def quote(value)
case value
when NilClass then 'NULL'
when TrueClass then 'TRUE'
when FalseClass then 'FALSE'
- when Float, Fixnum, Bignum then value.to_s
+ when Float, Integer then value.to_s
# BigDecimals need to be output in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
- when Array then value.map {|v| quote(v)}
+ when Array then value.map { |v| quote(v) }
else
value.inspect
end
end
def schema_default(klass, column)
quote(klass.column_defaults[column.name])
end
+ def retrieve_indexes_from_table(klass)
+ table_name = klass.table_name
+ return [] unless table_name
+
+ indexes = klass.connection.indexes(table_name)
+ return indexes if indexes.any? || !klass.table_name_prefix
+
+ # Try to search the table without prefix
+ table_name.to_s.slice!(klass.table_name_prefix)
+ klass.connection.indexes(table_name)
+ end
+
# Use the column information in an ActiveRecord class
# to create a comment block containing a line for
# each column. The line contains the column name,
# the type (and length), and any optional attributes
def get_schema_info(klass, header, options = {})
info = "# #{header}\n"
- info<< "#\n"
- if options[:format_markdown]
- info<< "# Table name: `#{klass.table_name}`\n"
- info<< "#\n"
- info<< "# ### Columns\n"
- else
- info<< "# Table name: #{klass.table_name}\n"
- end
- info<< "#\n"
+ info << get_schema_header_text(klass, options)
max_size = klass.column_names.map(&:size).max || 0
+ with_comment = options[:with_comment] && klass.columns.first.respond_to?(:comment)
+ max_size = klass.columns.map{|col| col.name.size + col.comment.size }.max || 0 if with_comment
+ max_size += 2 if with_comment
max_size += options[:format_rdoc] ? 5 : 1
md_names_overhead = 6
md_type_allowance = 18
bare_type_allowance = 16
if options[:format_markdown]
- info<< sprintf( "# %-#{max_size + md_names_overhead}.#{max_size + md_names_overhead}s | %-#{md_type_allowance}.#{md_type_allowance}s | %s\n", 'Name', 'Type', 'Attributes' )
- info<< "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
+ info << sprintf( "# %-#{max_size + md_names_overhead}.#{max_size + md_names_overhead}s | %-#{md_type_allowance}.#{md_type_allowance}s | %s\n", 'Name', 'Type', 'Attributes' )
+ info << "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
end
cols = if ignore_columns = options[:ignore_columns]
klass.columns.reject do |col|
col.name.match(/#{ignore_columns}/)
@@ -216,13 +231,13 @@
cols = cols.sort_by(&:name) if options[:sort]
cols = classified_sort(cols) if options[:classified_sort]
cols.each do |col|
col_type = (col.type || col.sql_type).to_s
-
attrs = []
- attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || NO_DEFAULT_COL_TYPES.include?(col_type)
+ attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || hide_default?(col_type, options)
+ attrs << 'unsigned' if col.respond_to?(:unsigned?) && col.unsigned?
attrs << 'not null' unless col.null
attrs << 'primary key' if klass.primary_key && (klass.primary_key.is_a?(Array) ? klass.primary_key.collect(&:to_sym).include?(col.name.to_sym) : col.name.to_sym == klass.primary_key.to_sym)
if col_type == 'decimal'
col_type << "(#{col.precision}, #{col.scale})"
@@ -248,27 +263,32 @@
end
# Check if the column has indices and print "indexed" if true
# If the index includes another column, print it too.
if options[:simple_indexes] && klass.table_exists?# Check out if this column is indexed
- indices = klass.connection.indexes(klass.table_name)
+ indices = retrieve_indexes_from_table(klass)
if indices = indices.select { |ind| ind.columns.include? col.name }
indices.sort_by(&:name).each do |ind|
+ next if ind.columns.is_a?(String)
ind = ind.columns.reject! { |i| i == col.name }
attrs << (ind.empty? ? "indexed" : "indexed => [#{ind.join(", ")}]")
end
end
end
-
+ col_name = if with_comment
+ "#{col.name}(#{col.comment})"
+ else
+ col.name
+ end
if options[:format_rdoc]
- info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col.name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
+ info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col_name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
elsif options[:format_markdown]
- name_remainder = max_size - col.name.length
+ name_remainder = max_size - col_name.length
type_remainder = (md_type_allowance - 2) - col_type.length
- info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col.name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n"
+ info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col_name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n"
else
- info << sprintf("# %-#{max_size}.#{max_size}s:%-#{bare_type_allowance}.#{bare_type_allowance}s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n"
+ info << sprintf("# %-#{max_size}.#{max_size}s:%-#{bare_type_allowance}.#{bare_type_allowance}s %s", col_name, col_type, attrs.join(", ")).rstrip + "\n"
end
end
if options[:show_indexes] && klass.table_exists?
info << get_index_info(klass, options)
@@ -276,36 +296,53 @@
if options[:show_foreign_keys] && klass.table_exists?
info << get_foreign_key_info(klass, options)
end
+ info << get_schema_footer_text(klass, options)
+ end
+
+ def get_schema_header_text(klass, options = {})
+ info = "#\n"
+ if options[:format_markdown]
+ info << "# Table name: `#{klass.table_name}`\n"
+ info << "#\n"
+ info << "# ### Columns\n"
+ else
+ info << "# Table name: #{klass.table_name}\n"
+ end
+ info << "#\n"
+ end
+
+ def get_schema_footer_text(_klass, options = {})
+ info = ''
if options[:format_rdoc]
info << "#--\n"
info << "# #{END_MARK}\n"
info << "#++\n"
else
info << "#\n"
end
end
- def get_index_info(klass, options={})
- if options[:format_markdown]
- index_info = "#\n# ### Indexes\n#\n"
- else
- index_info = "#\n# Indexes\n#\n"
- end
+ def get_index_info(klass, options = {})
+ index_info = if options[:format_markdown]
+ "#\n# ### Indexes\n#\n"
+ else
+ "#\n# Indexes\n#\n"
+ end
- indexes = klass.connection.indexes(klass.table_name)
+ indexes = retrieve_indexes_from_table(klass)
return '' if indexes.empty?
max_size = indexes.collect{|index| index.name.size}.max + 1
indexes.sort_by(&:name).each do |index|
- if options[:format_markdown]
- index_info << sprintf("# * `%s`%s:\n# * **`%s`**\n", index.name, index.unique ? " (_unique_)" : "", index.columns.join("`**\n# * **`"))
- else
- index_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{index.columns.join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n"
- end
+ index_info << if options[:format_markdown]
+ sprintf("# * `%s`%s:\n# * **`%s`**\n", index.name, index.unique ? " (_unique_)" : "", Array(index.columns).join("`**\n# * **`"))
+ else
+ sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{Array(index.columns).join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n"
+ end
end
index_info
end
@@ -318,54 +355,68 @@
end
excludes.include?(col_type)
end
- def get_foreign_key_info(klass, options={})
- if options[:format_markdown]
- fk_info = "#\n# ### Foreign Keys\n#\n"
- else
- fk_info = "#\n# Foreign Keys\n#\n"
- end
+ def hide_default?(col_type, options)
+ excludes =
+ if options[:hide_default_column_types].blank?
+ NO_DEFAULT_COL_TYPES
+ else
+ options[:hide_default_column_types].split(',')
+ end
+ excludes.include?(col_type)
+ end
+
+ def get_foreign_key_info(klass, options = {})
+ fk_info = if options[:format_markdown]
+ "#\n# ### Foreign Keys\n#\n"
+ else
+ "#\n# Foreign Keys\n#\n"
+ end
+
return '' unless klass.connection.respond_to?(:supports_foreign_keys?) &&
klass.connection.supports_foreign_keys? && klass.connection.respond_to?(:foreign_keys)
foreign_keys = klass.connection.foreign_keys(klass.table_name)
return '' if foreign_keys.empty?
- max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1
- foreign_keys.sort_by(&:name).each do |fk|
+ format_name = ->(fk) { options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, '...') }
+
+ max_size = foreign_keys.map(&format_name).map(&:size).max + 1
+ foreign_keys.sort_by {|fk| [format_name.call(fk), fk.column]}.each do |fk|
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
constraints_info = ''
constraints_info += "ON DELETE => #{fk.on_delete} " if fk.on_delete
constraints_info += "ON UPDATE => #{fk.on_update} " if fk.on_update
constraints_info.strip!
- if options[:format_markdown]
- fk_info << sprintf("# * `%s`%s:\n# * **`%s`**\n", fk.name, constraints_info.blank? ? '' : " (_#{constraints_info}_)", ref_info)
- else
- fk_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", fk.name, "(#{ref_info})", constraints_info).rstrip + "\n"
- end
+
+ fk_info << if options[:format_markdown]
+ sprintf("# * `%s`%s:\n# * **`%s`**\n", format_name.call(fk), constraints_info.blank? ? '' : " (_#{constraints_info}_)", ref_info)
+ else
+ sprintf("# %-#{max_size}.#{max_size}s %s %s", format_name.call(fk), "(#{ref_info})", constraints_info).rstrip + "\n"
+ end
end
fk_info
end
# Add a schema block to a file. If the file already contains
- # a schema info block (a comment starting with "== Schema Information"), check if it
- # matches the block that is already there. If so, leave it be. If not, remove the old
- # info block and write a new one.
+ # a schema info block (a comment starting with "== Schema Information"),
+ # check if it matches the block that is already there. If so, leave it be.
+ # If not, remove the old info block and write a new one.
#
# == Returns:
# true or false depending on whether the file was modified.
#
# === Options (opts)
# :force<Symbol>:: whether to update the file even if it doesn't seem to need it.
# :position_in_*<Symbol>:: where to place the annotated section in fixture or model file,
# :before, :top, :after or :bottom. Default is :before.
#
- def annotate_one_file(file_name, info_block, position, options={})
+ def annotate_one_file(file_name, info_block, position, options = {})
if File.exist?(file_name)
old_content = File.read(file_name)
return false if old_content =~ /# -\*- SkipSchemaAnnotations.*\n/
# Ignore the Schema version line because it changes with each migration
@@ -375,12 +426,12 @@
column_pattern = /^#[\t ]+[\w\*`]+[\t ]+.+$/
old_columns = old_header && old_header.scan(column_pattern).sort
new_columns = new_header && new_header.scan(column_pattern).sort
- magic_comment_matcher= Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)|(^# -\*- encoding\s?:.*\n)|(^#\s*frozen_string_literal:.+\n)|(^# -\*- frozen_string_literal\s*:.+-\*-\n)/)
- magic_comments= old_content.scan(magic_comment_matcher).flatten.compact
+ magic_comment_matcher = Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)|(^# -\*- encoding\s?:.*\n)|(^#\s*frozen_string_literal:.+\n)|(^# -\*- frozen_string_literal\s*:.+-\*-\n)/)
+ magic_comments = old_content.scan(magic_comment_matcher).flatten.compact
if old_columns == new_columns && !options[:force]
return false
else
# Replace inline the old schema info with the new schema info
@@ -397,29 +448,29 @@
# we simply need to insert it in correct position
if new_content == old_content || options[:force]
old_content.sub!(magic_comment_matcher, '')
old_content.sub!(annotate_pattern(options), '')
- if %w(after bottom).include?(options[position].to_s)
- new_content = magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block)
- else
- new_content = magic_comments.join + wrapped_info_block + "\n" + old_content
- end
+ new_content = if %w(after bottom).include?(options[position].to_s)
+ magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block)
+ else
+ magic_comments.join + wrapped_info_block + "\n" + old_content
+ end
end
File.open(file_name, 'wb') { |f| f.puts new_content }
return true
end
else
false
end
end
- def remove_annotation_of_file(file_name, options={})
+ def remove_annotation_of_file(file_name, options = {})
if File.exist?(file_name)
content = File.read(file_name)
- wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
+ wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ''
content.sub!(/(#{wrapper_open})?#{annotate_pattern(options)}/, '')
File.open(file_name, 'wb') { |f| f.puts content }
true
@@ -451,15 +502,16 @@
# :exclude_factories<Symbol>:: whether to skip modification of factory files
# :exclude_serializers<Symbol>:: whether to skip modification of serializer files
# :exclude_scaffolds<Symbol>:: whether to skip modification of scaffold files
# :exclude_controllers<Symbol>:: whether to skip modification of controller files
# :exclude_helpers<Symbol>:: whether to skip modification of helper files
+ # :exclude_sti_subclasses<Symbol>:: whether to skip modification of files for STI subclasses
#
# == Returns:
# an array of file names that were annotated.
#
- def annotate(klass, file, header, options={})
+ def annotate(klass, file, header, options = {})
begin
klass.reset_column_information
info = get_schema_info(klass, header, options)
model_name = klass.name.underscore
table_name = klass.table_name
@@ -478,21 +530,20 @@
if key == 'admin'
exclusion_key = 'exclude_class'.to_sym
position_key = 'position_in_class'.to_sym
end
- unless options[exclusion_key]
- self.get_patterns(key).
- map { |f| resolve_filename(f, model_name, table_name) }.
- each { |f|
- if annotate_one_file(f, info, position_key, options_with_position(options, position_key))
- annotated << f
- end
- }
- end
+ next if options[exclusion_key]
+ get_patterns(key)
+ .map { |f| resolve_filename(f, model_name, table_name) }
+ .each do |f|
+ if annotate_one_file(f, info, position_key, options_with_position(options, position_key))
+ annotated << f
+ end
+ end
end
- rescue Exception => e
+ rescue StandardError => e
puts "Unable to annotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
end
annotated
@@ -507,12 +558,12 @@
# If we have command line arguments, they're assumed to the path
# of model files from root dir. Otherwise we take all the model files
# in the model_dir directory.
def get_model_files(options)
models = []
- if !options[:is_rake]
- models = ARGV.dup.reject{|m| m.match(/^(.*)=/)}
+ unless options[:is_rake]
+ models = ARGV.dup.reject { |m| m.match(/^(.*)=/) }
end
if models.empty?
begin
model_dir.each do |dir|
@@ -559,34 +610,31 @@
end
end
# Retrieve loaded model class by path to the file where it's supposed to be defined.
def get_loaded_model(model_path)
- begin
- ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.camelize(model_path))
- rescue
- # Revert to the old way but it is not really robust
- ObjectSpace.each_object(::Class).
- select do |c|
- Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
- c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
- c.ancestors.include?(ActiveRecord::Base)
- end.
- detect { |c| ActiveSupport::Inflector.underscore(c.to_s) == model_path }
- end
+ ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.camelize(model_path))
+ rescue
+ # Revert to the old way but it is not really robust
+ ObjectSpace.each_object(::Class)
+ .select do |c|
+ Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
+ c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
+ c.ancestors.include?(ActiveRecord::Base)
+ end.detect { |c| ActiveSupport::Inflector.underscore(c.to_s) == model_path }
end
- def parse_options(options={})
+ def parse_options(options = {})
self.model_dir = options[:model_dir] if options[:model_dir]
self.root_dir = options[:root_dir] if options[:root_dir]
end
# We're passed a name of things that might be
# ActiveRecord models. If we can find the class, and
# if its a subclass of ActiveRecord::Base,
# then pass it to the associated block
- def do_annotations(options={})
+ def do_annotations(options = {})
parse_options(options)
header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
version = ActiveRecord::Migrator.current_version rescue 0
if options[:include_version] && version > 0
@@ -607,25 +655,29 @@
def annotate_model_file(annotated, file, header, options)
begin
return false if /# -\*- SkipSchemaAnnotations.*/ =~ (File.exist?(file) ? File.read(file) : '')
klass = get_model_class(file)
- if klass && klass < ActiveRecord::Base && !klass.abstract_class? && klass.table_exists?
- annotated.concat(annotate(klass, file, header, options))
- end
+ do_annotate = klass &&
+ klass < ActiveRecord::Base &&
+ (!options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name)) &&
+ !klass.abstract_class? &&
+ klass.table_exists?
+
+ annotated.concat(annotate(klass, file, header, options)) if do_annotate
rescue BadModelFileError => e
unless options[:ignore_unknown_models]
puts "Unable to annotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
end
- rescue Exception => e
+ rescue StandardError => e
puts "Unable to annotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
end
end
- def remove_annotations(options={})
+ def remove_annotations(options = {})
parse_options(options)
deannotated = []
deannotated_klass = false
get_model_files(options).each do |file|
@@ -636,33 +688,33 @@
model_name = klass.name.underscore
table_name = klass.table_name
model_file_name = file
deannotated_klass = true if remove_annotation_of_file(model_file_name, options)
- get_patterns(matched_types(options)).
- map { |f| resolve_filename(f, model_name, table_name) }.
- each do |f|
+ get_patterns(matched_types(options))
+ .map { |f| resolve_filename(f, model_name, table_name) }
+ .each do |f|
if File.exist?(f)
remove_annotation_of_file(f, options)
deannotated_klass = true
end
end
end
deannotated << klass if deannotated_klass
- rescue Exception => e
+ rescue StandardError => e
puts "Unable to deannotate #{File.join(file)}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
end
end
puts "Removed annotations from: #{deannotated.join(', ')}"
end
def resolve_filename(filename_template, model_name, table_name)
- filename_template.
- gsub('%MODEL_NAME%', model_name).
- gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize).
- gsub('%TABLE_NAME%', table_name || model_name.pluralize)
+ filename_template
+ .gsub('%MODEL_NAME%', model_name)
+ .gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize)
+ .gsub('%TABLE_NAME%', table_name || model_name.pluralize)
end
def classified_sort(cols)
rest_cols = []
timestamps = []
@@ -672,23 +724,24 @@
cols.each do |c|
if c.name.eql?('id')
id = c
elsif c.name.eql?('created_at') || c.name.eql?('updated_at')
timestamps << c
- elsif c.name[-3,3].eql?('_id')
+ elsif c.name[-3, 3].eql?('_id')
associations << c
else
rest_cols << c
end
end
- [rest_cols, timestamps, associations].each {|a| a.sort_by!(&:name) }
+ [rest_cols, timestamps, associations].each { |a| a.sort_by!(&:name) }
- return ([id] << rest_cols << timestamps << associations).flatten.compact
+ ([id] << rest_cols << timestamps << associations).flatten.compact
end
# Ignore warnings for the duration of the block ()
def silence_warnings
- old_verbose, $VERBOSE = $VERBOSE, nil
+ old_verbose = $VERBOSE
+ $VERBOSE = nil
yield
ensure
$VERBOSE = old_verbose
end
end