lib/annotate/annotate_models.rb in annotate-2.7.0 vs lib/annotate/annotate_models.rb in annotate-2.7.1
- old
+ new
@@ -1,15 +1,16 @@
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"
- PATTERN = /^\r?\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\r?\n(#.*\r?\n)*(\r?\n)*/
MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper)
# File.join for windows reverse bar compat?
# I dont use windows, can`t test
@@ -47,98 +48,126 @@
SERIALIZERS_SPEC_DIR = File.join("spec", "serializers")
# Controller files
CONTROLLER_DIR = File.join("app", "controllers")
+ # Active admin registry files
+ ACTIVEADMIN_DIR = File.join("app", "admin")
+
# Helper files
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 = ["integer", "boolean"]
+ NO_LIMIT_COL_TYPES = %w(integer boolean)
+ # Don't show default value for these column types
+ NO_DEFAULT_COL_TYPES = %w(json jsonb)
+
class << self
- def model_dir
- @model_dir.is_a?(Array) ? @model_dir : [@model_dir || "app/models"]
+ 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*/
+ end
+ /^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*/
end
- def model_dir=(dir)
- @model_dir = dir
+ def model_dir
+ @model_dir.is_a?(Array) ? @model_dir : [@model_dir || 'app/models']
end
+ attr_writer :model_dir
+
def root_dir
- @root_dir.is_a?(Array) ? @root_dir : [@root_dir || ""]
+ @root_dir.is_a?(Array) ? @root_dir : [@root_dir || '']
end
- def root_dir=(dir)
- @root_dir = dir
+ 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")
+ ]
end
- def get_patterns(pattern_types=MATCHED_TYPES)
+ 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")
+ ]
+ 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")
+ ]
+ 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")
+ ]
+ 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")
+ ]
+ 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
+ []
+ end
+ end
+
+ def get_patterns(pattern_types=[])
current_patterns = []
root_dir.each do |root_directory|
Array(pattern_types).each do |pattern_type|
- current_patterns += case pattern_type
- when 'test'
- [
- 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"),
- ]
- when 'fixture'
- [
- 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"),
- ]
- when 'scaffold'
- [
- 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"),
- ]
- when 'factory'
- [
- 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"),
- ]
- when 'serializer'
- [
- 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")
- ]
- when 'controller'
- [
- File.join(root_directory, CONTROLLER_DIR, "%PLURALIZED_MODEL_NAME%_controller.rb")
- ]
- when 'helper'
- [
- File.join(root_directory, HELPER_DIR, "%PLURALIZED_MODEL_NAME%_helper.rb")
- ]
- end
+ current_patterns += files_by_pattern(root_directory, pattern_type)
end
end
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 NilClass then 'NULL'
+ when TrueClass then 'TRUE'
+ when FalseClass then 'FALSE'
when Float, Fixnum, Bignum 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)}
else
@@ -155,26 +184,26 @@
# 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])
+ 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"
- max_size = klass.column_names.map{|name| name.size}.max || 0
+ max_size = klass.column_names.map(&:size).max || 0
max_size += options[:format_rdoc] ? 5 : 1
md_names_overhead = 6
md_type_allowance = 18
bare_type_allowance = 16
- if(options[:format_markdown])
+ 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"
end
cols = if ignore_columns = options[:ignore_columns]
@@ -183,53 +212,51 @@
end
else
klass.columns
end
- cols = cols.sort_by(&:name) if(options[:sort])
- cols = classified_sort(cols) if(options[:classified_sort])
+ 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? || col_type == "jsonb"
- attrs << "not null" unless col.null
- attrs << "primary key" if klass.primary_key && (klass.primary_key.is_a?(Array) ? klass.primary_key.collect{|c|c.to_sym}.include?(col.name.to_sym) : col.name.to_sym == klass.primary_key.to_sym)
+ attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || NO_DEFAULT_COL_TYPES.include?(col_type)
+ 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"
+ if col_type == 'decimal'
col_type << "(#{col.precision}, #{col.scale})"
- elsif col_type != "spatial"
- if (col.limit)
+ elsif col_type != 'spatial'
+ if col.limit
if col.limit.is_a? Array
attrs << "(#{col.limit.join(', ')})"
else
col_type << "(#{col.limit})" unless hide_limit?(col_type, options)
end
end
end
# Check out if we got an array column
- if col.respond_to?(:array) && col.array
- attrs << "is an Array"
- end
+ attrs << 'is an Array' if col.respond_to?(:array) && col.array
# Check out if we got a geometric column
# and print the type and SRID
if col.respond_to?(:geometry_type)
attrs << "#{col.geometry_type}, #{col.srid}"
- elsif col.respond_to?(:geometric_type) and col.geometric_type.present?
+ elsif col.respond_to?(:geometric_type) && col.geometric_type.present?
attrs << "#{col.geometric_type.to_s.downcase}, #{col.srid}"
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)
if indices = indices.select { |ind| ind.columns.include? col.name }
- indices.sort_by{|ind| ind.name}.each do |ind|
+ indices.sort_by(&:name).each do |ind|
ind = ind.columns.reject! { |i| i == col.name }
- attrs << (ind.length == 0 ? "indexed" : "indexed => [#{ind.join(", ")}]")
+ attrs << (ind.empty? ? "indexed" : "indexed => [#{ind.join(", ")}]")
end
end
end
if options[:format_rdoc]
@@ -259,80 +286,89 @@
info << "#\n"
end
end
def get_index_info(klass, options={})
- if(options[:format_markdown])
+ if options[:format_markdown]
index_info = "#\n# ### Indexes\n#\n"
else
index_info = "#\n# Indexes\n#\n"
end
indexes = klass.connection.indexes(klass.table_name)
- return "" if indexes.empty?
+ return '' if indexes.empty?
max_size = indexes.collect{|index| index.name.size}.max + 1
- indexes.sort_by{|index| index.name}.each do |index|
- if(options[:format_markdown])
+ 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
end
- return index_info
+
+ index_info
end
def hide_limit?(col_type, options)
- excludes =
+ excludes =
if options[:hide_limit_column_types].blank?
NO_LIMIT_COL_TYPES
else
options[:hide_limit_column_types].split(',')
end
excludes.include?(col_type)
end
def get_foreign_key_info(klass, options={})
- if(options[:format_markdown])
+ if options[:format_markdown]
fk_info = "#\n# ### Foreign Keys\n#\n"
else
fk_info = "#\n# Foreign Keys\n#\n"
end
- return "" unless klass.connection.supports_foreign_keys? && klass.connection.respond_to?(:foreign_keys)
+ 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?
+ return '' if foreign_keys.empty?
max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1
- foreign_keys.sort_by{|fk| fk.name}.each do |fk|
+ foreign_keys.sort_by(&:name).each do |fk|
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
- if(options[:format_markdown])
- fk_info << sprintf("# * `%s`:\n# * **`%s`**\n", fk.name, ref_info)
+ 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", fk.name, "(#{ref_info})").rstrip + "\n"
+ fk_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", fk.name, "(#{ref_info})", constraints_info).rstrip + "\n"
end
end
- return fk_info
+
+ 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.
- # Returns true or false depending on whether the file was modified.
#
+ # == 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={})
if File.exist?(file_name)
old_content = File.read(file_name)
- return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/)
+ return false if old_content =~ /# -\*- SkipSchemaAnnotations.*\n/
# Ignore the Schema version line because it changes with each migration
header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/
old_header = old_content.match(header_pattern).to_s
new_header = info_block.match(header_pattern).to_s
@@ -346,58 +382,65 @@
if old_columns == new_columns && !options[:force]
return false
else
# Replace inline the old schema info with the new schema info
- new_content = old_content.sub(PATTERN, info_block + "\n")
+ new_content = old_content.sub(annotate_pattern(options), info_block + "\n")
if new_content.end_with?(info_block + "\n")
- new_content = old_content.sub(PATTERN, "\n" + info_block)
+ new_content = old_content.sub(annotate_pattern(options), "\n" + info_block)
end
wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
wrapper_close = options[:wrapper_close] ? "# #{options[:wrapper_close]}\n" : ""
wrapped_info_block = "#{wrapper_open}#{info_block}#{wrapper_close}"
# if there *was* no old schema info (no substitution happened) or :force was passed,
# 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!(PATTERN, '')
+ old_content.sub!(annotate_pattern(options), '')
- new_content = %w(after bottom).include?(options[position].to_s) ?
- (magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block)) :
- (magic_comments.join + wrapped_info_block + "\n" + old_content)
+ 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
end
- File.open(file_name, "wb") { |f| f.puts new_content }
+ File.open(file_name, 'wb') { |f| f.puts new_content }
return true
end
else
- return false
+ false
end
end
- def remove_annotation_of_file(file_name)
+ 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" : ""
+ content.sub!(/(#{wrapper_open})?#{annotate_pattern(options)}/, '')
- content.sub!(PATTERN, '')
+ File.open(file_name, 'wb') { |f| f.puts content }
- File.open(file_name, "wb") { |f| f.puts content }
-
- return true
+ true
else
- return false
+ false
end
end
+ def matched_types(options)
+ types = MATCHED_TYPES
+ types << 'admin' if options[:active_admin] =~ TRUE_RE && !types.include?('admin')
+
+ types
+ end
+
# Given the name of an ActiveRecord class, create a schema
# info block (basically a comment containing information
# on the columns and their types) and put it at the front
# of the model and fixture source files.
- # Returns true or false depending on whether the source
- # files were modified.
#
# === Options (opts)
# :position_in_class<Symbol>:: where to place the annotated section in model file
# :position_in_test<Symbol>:: where to place the annotated section in test/spec file(s)
# :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
@@ -409,53 +452,66 @@
# :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
#
+ # == Returns:
+ # an array of file names that were annotated.
+ #
def annotate(klass, file, header, options={})
begin
+ klass.reset_column_information
info = get_schema_info(klass, header, options)
- did_annotate = false
model_name = klass.name.underscore
table_name = klass.table_name
model_file_name = File.join(file)
+ annotated = []
if annotate_one_file(model_file_name, info, :position_in_class, options_with_position(options, :position_in_class))
- did_annotate = true
+ annotated << model_file_name
end
- MATCHED_TYPES.each do |key|
+ matched_types(options).each do |key|
exclusion_key = "exclude_#{key.pluralize}".to_sym
position_key = "position_in_#{key}".to_sym
+ # Same options for active_admin models
+ if key == 'admin'
+ exclusion_key = 'exclude_class'.to_sym
+ position_key = 'position_in_class'.to_sym
+ end
+
unless options[exclusion_key]
- did_annotate = self.get_patterns(key).
+ self.get_patterns(key).
map { |f| resolve_filename(f, model_name, table_name) }.
- map { |f| annotate_one_file(f, info, position_key, options_with_position(options, position_key)) }.
- detect { |result| result } || did_annotate
+ each { |f|
+ if annotate_one_file(f, info, position_key, options_with_position(options, position_key))
+ annotated << f
+ end
+ }
end
end
-
- return did_annotate
rescue Exception => e
puts "Unable to annotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
end
+
+ annotated
end
# position = :position_in_fixture or :position_in_class
def options_with_position(options, position_in)
- options.merge(:position=>(options[position_in] || options[:position]))
+ options.merge(position: (options[position_in] || options[:position]))
end
# Return a list of the model files to annotate.
# 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])
+ if !options[:is_rake]
models = ARGV.dup.reject{|m| m.match(/^(.*)=/)}
end
if models.empty?
begin
@@ -486,17 +542,17 @@
# in subdirectories without namespacing.
def get_model_class(file)
model_path = file.gsub(/\.rb$/, '')
model_dir.each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
begin
- get_loaded_model(model_path) or raise BadModelFileError.new
+ get_loaded_model(model_path) || raise(BadModelFileError.new)
rescue LoadError
# this is for non-rails projects, which don't get Rails auto-require magic
file_path = File.expand_path(file)
if File.file?(file_path) && silence_warnings { Kernel.require(file_path) }
retry
- elsif model_path.match(/\//)
+ elsif model_path =~ /\//
model_path = model_path.split('/')[1..-1].join('/').to_s
retry
else
raise
end
@@ -509,54 +565,54 @@
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 and # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
- c.ancestors.respond_to?(:include?) and # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
+ 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
end
+ 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={})
- header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
+ parse_options(options)
- if options[:include_version]
- version = ActiveRecord::Migrator.current_version rescue 0
- if version > 0
- header << "\n# Schema version: #{version}"
- end
+ header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
+ version = ActiveRecord::Migrator.current_version rescue 0
+ if options[:include_version] && version > 0
+ header << "\n# Schema version: #{version}"
end
- self.model_dir = options[:model_dir] if options[:model_dir]
- self.root_dir = options[:root_dir] if options[:root_dir]
-
annotated = []
- get_model_files(options).each do |file|
- annotate_model_file(annotated, File.join(file), header, options)
+ get_model_files(options).each do |path, filename|
+ annotate_model_file(annotated, File.join(path, filename), header, options)
end
+
if annotated.empty?
- puts "Model files unchanged."
+ puts 'Model files unchanged.'
else
puts "Annotated (#{annotated.length}): #{annotated.join(', ')}"
end
end
def annotate_model_file(annotated, file, header, options)
begin
- return false if (/# -\*- SkipSchemaAnnotations.*/ =~ (File.exist?(file) ? File.read(file) : '') )
+ 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?
- if annotate(klass, file, header, options)
- annotated << file
- end
+ annotated.concat(annotate(klass, file, header, options))
end
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]
@@ -566,68 +622,68 @@
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
end
end
def remove_annotations(options={})
- self.model_dir = options[:model_dir] if options[:model_dir]
- self.root_dir = options[:root_dir] if options[:root_dir]
+ parse_options(options)
+
deannotated = []
deannotated_klass = false
get_model_files(options).each do |file|
file = File.join(file)
begin
klass = get_model_class(file)
if klass < ActiveRecord::Base && !klass.abstract_class?
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))
+ deannotated_klass = true if remove_annotation_of_file(model_file_name, options)
- get_patterns.
+ 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)
+ remove_annotation_of_file(f, options)
deannotated_klass = true
end
end
end
- deannotated << klass if(deannotated_klass)
+ deannotated << klass if deannotated_klass
rescue Exception => 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)
- return 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 = []
associations = []
id = nil
- cols = cols.each do |c|
- if c.name.eql?("id")
+ cols.each do |c|
+ if c.name.eql?('id')
id = c
- elsif (c.name.eql?("created_at") || c.name.eql?("updated_at"))
+ 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) }
- return ([id] << rest_cols << timestamps << associations).flatten
+ return ([id] << rest_cols << timestamps << associations).flatten.compact
end
# Ignore warnings for the duration of the block ()
def silence_warnings
old_verbose, $VERBOSE = $VERBOSE, nil