Commit 0b054562 by cuong.tran

Merge branch 'release/v2.7.0'

parents d878a5b7 bee8f380
sudo: false
language: ruby language: ruby
rvm: rvm:
- 1.9.3 - 1.9.3
- 2.0 - 2.0
- 2.1 - 2.1
- 2.2 - 2.2
- 2.3.0-preview1
before_install: before_install:
- rvm @global do gem install bundler - rvm @global do gem install bundler
== 2.7.0
See https://github.com/ctran/annotate_models/releases/tag/v2.7.0
== 2.6.9 == 2.6.9
* Support foreigh key (#241) * Support foreigh key (#241)
* Check if model has skip tag in annotate_model_file (#167) * Check if model has skip tag in annotate_model_file (#167)
......
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rake', '>= 0.8.7', :require => false gem 'rake', '>= 10.4.2', :require => false
gem 'activerecord', '>= 2.3.0', :require => false gem 'activerecord', '>= 4.2.5', :require => false
group :development do group :development do
gem 'mg', :require => false gem 'mg', :require => false
...@@ -23,6 +23,6 @@ group :development, :test do ...@@ -23,6 +23,6 @@ group :development, :test do
end end
group :test do group :test do
gem 'wrong', '>=0.6.2', :require => false gem 'wrong', :require => false
gem 'files', '>=0.2.1', :require => false gem 'files', :require => false
end end
...@@ -51,7 +51,7 @@ Also, if you pass the -r option, it'll annotate routes.rb with the output of ...@@ -51,7 +51,7 @@ Also, if you pass the -r option, it'll annotate routes.rb with the output of
Into Gemfile from rubygems.org: Into Gemfile from rubygems.org:
gem 'annotate', '~> 2.6.6' gem 'annotate'
Into Gemfile from Github: Into Gemfile from Github:
...@@ -100,9 +100,7 @@ To remove routes.rb annotations: ...@@ -100,9 +100,7 @@ To remove routes.rb annotations:
annotate --routes --delete annotate --routes --delete
To automatically annotate every time you run +db:migrate+, either run +rails g annotate:install+ or add +Annotate.load_tasks+ to your `Rakefile`. See the [configuration in Rails](#configuration-in-rails) section for more info.
== Configuration
=== Usage Outside of Rails === Usage Outside of Rails
...@@ -112,7 +110,6 @@ or more +--model-dir+ options to inform annotate about the structure of your ...@@ -112,7 +110,6 @@ or more +--model-dir+ options to inform annotate about the structure of your
project and help it bootstrap and load the relevant code. project and help it bootstrap and load the relevant code.
== Configuration == Configuration
If you want to always skip annotations on a particular model, add this string If you want to always skip annotations on a particular model, add this string
...@@ -130,7 +127,13 @@ default options: ...@@ -130,7 +127,13 @@ default options:
Edit this file to control things like output format, where annotations are Edit this file to control things like output format, where annotations are
added (top or bottom of file), and in which artifacts. added (top or bottom of file), and in which artifacts.
== Rails Integration The generated rakefile +lib/tasks/auto_annotate_models.rake+ also contains
`Annotate.load_tasks`. This adds a few rake tasks which duplicate command-line
functionality:
rake annotate_models # Add schema information (as comments) to model and fixture files
rake annotate_routes # Adds the route map to routes.rb
rake remove_annotation # Remove schema information from model and fixture files
By default, once you've generated a configuration file, annotate will be By default, once you've generated a configuration file, annotate will be
executed whenever you run +rake db:migrate+ (but only in development mode). executed whenever you run +rake db:migrate+ (but only in development mode).
......
...@@ -27,13 +27,13 @@ Gem::Specification.new do |s| ...@@ -27,13 +27,13 @@ Gem::Specification.new do |s|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<rake>, ["~> 10.4"]) s.add_runtime_dependency(%q<rake>, ["~> 10.4"])
s.add_runtime_dependency(%q<activerecord>, [">= 3.2", "<= 4.3"]) s.add_runtime_dependency(%q<activerecord>, [">= 3.2", "< 6.0"])
else else
s.add_dependency(%q<rake>, ["~> 10.4"]) s.add_dependency(%q<rake>, ["~> 10.4"])
s.add_dependency(%q<activerecord>, [">= 3.2", "<= 4.3"]) s.add_dependency(%q<activerecord>, [">= 3.2", "< 6.0"])
end end
else else
s.add_dependency(%q<rake>, [">= 0.8.7"]) s.add_dependency(%q<rake>, [">= 0.8.7"])
s.add_dependency(%q<activerecord>, [">= 3.2", "<= 4.3"]) s.add_dependency(%q<activerecord>, [">= 3.2", "< 6.0"])
end end
end end
...@@ -18,18 +18,16 @@ require 'optparse' ...@@ -18,18 +18,16 @@ require 'optparse'
require 'annotate' require 'annotate'
Annotate.bootstrap_rake Annotate.bootstrap_rake
target = {
:klass => AnnotateModels,
:task => :do_annotations,
}
has_set_position = {} has_set_position = {}
target_action = :do_annotations
OptionParser.new do |opts| OptionParser.new do |opts|
opts.banner = "Usage: annotate [options] [model_file]*" opts.banner = "Usage: annotate [options] [model_file]*"
opts.on('-d', '--delete', opts.on('-d', '--delete',
"Remove annotations from all model files or the routes.rb file") do "Remove annotations from all model files or the routes.rb file") do
target[:task] = :remove_annotations target_action = :remove_annotations
end end
opts.on('-p', '--position [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], opts.on('-p', '--position [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'],
...@@ -93,10 +91,7 @@ OptionParser.new do |opts| ...@@ -93,10 +91,7 @@ OptionParser.new do |opts|
opts.on('-r', '--routes', opts.on('-r', '--routes',
"Annotate routes.rb with the output of 'rake routes'") do "Annotate routes.rb with the output of 'rake routes'") do
target = { ENV['routes'] = 'true'
:klass => AnnotateRoutes,
:task => :do_annotations
}
end end
opts.on('-v', '--version', opts.on('-v', '--version',
...@@ -129,6 +124,11 @@ OptionParser.new do |opts| ...@@ -129,6 +124,11 @@ OptionParser.new do |opts|
ENV['model_dir'] = dir ENV['model_dir'] = dir
end end
opts.on('--root-dir dir',
"Annotate files stored within root dir projects, separate multiple dirs with comas") do |dir|
ENV['root_dir'] = dir
end
opts.on('--ignore-model-subdirects', opts.on('--ignore-model-subdirects',
"Ignore subdirectories of the models directory") do |dir| "Ignore subdirectories of the models directory") do |dir|
ENV['ignore_model_sub_dir'] = "yes" ENV['ignore_model_sub_dir'] = "yes"
...@@ -178,8 +178,18 @@ OptionParser.new do |opts| ...@@ -178,8 +178,18 @@ OptionParser.new do |opts|
ENV['ignore_columns'] = regex ENV['ignore_columns'] = regex
end end
opts.on('--hide-limit-column-types VALUES', "don't show limit for given column types, separated by comas (i.e., `integer,boolean,text`)" ) do |values|
ENV['hide_limit_column_types'] = "#{values}"
end
opts.on('--ignore-unknown-models', "don't display warnings for bad model files" ) do |values|
ENV['ignore_unknown_models'] = "true"
end
end.parse! end.parse!
options = Annotate.setup_options({ :is_rake => ENV['is_rake'] && !ENV['is_rake'].empty? }) options = Annotate.setup_options({ :is_rake => ENV['is_rake'] && !ENV['is_rake'].empty? })
Annotate.eager_load(options) Annotate.eager_load(options)
target[:klass].send(target[:task], options)
AnnotateModels.send(target_action, options) if Annotate.include_models?
AnnotateRoutes.send(target_action, options) if Annotate.include_routes?
...@@ -7,7 +7,7 @@ begin ...@@ -7,7 +7,7 @@ begin
# ActiveSupport 3.x... # ActiveSupport 3.x...
require 'active_support/hash_with_indifferent_access' require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/blank'
rescue Exception => e rescue Exception
# ActiveSupport 2.x... # ActiveSupport 2.x...
require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/blank' require 'active_support/core_ext/blank'
...@@ -27,32 +27,36 @@ module Annotate ...@@ -27,32 +27,36 @@ module Annotate
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir, :exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace, :format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
:timestamp, :exclude_serializers, :classified_sort, :show_foreign_keys, :timestamp, :exclude_serializers, :classified_sort, :show_foreign_keys,
:exclude_scaffolds, :exclude_controllers, :exclude_helpers, :ignore_unknown_models,
] ]
OTHER_OPTIONS=[ OTHER_OPTIONS=[
:ignore_columns :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close, :wrapper, :routes,
:hide_limit_column_types,
] ]
PATH_OPTIONS=[ PATH_OPTIONS=[
:require, :model_dir :require, :model_dir, :root_dir
] ]
## ##
# Set default values that can be overridden via environment variables. # Set default values that can be overridden via environment variables.
# #
def self.set_defaults(options = {}) def self.set_defaults(options = {})
return if(@has_set_defaults) return if(@has_set_defaults)
@has_set_defaults = true @has_set_defaults = true
options = HashWithIndifferentAccess.new(options) options = HashWithIndifferentAccess.new(options)
[POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS].flatten.each do |key| [POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS].flatten.each do |key|
if(options.has_key?(key)) if options.has_key?(key)
default_value = if(options[key].is_a?(Array)) default_value = if options[key].is_a?(Array)
options[key].join(",") options[key].join(",")
else else
options[key] options[key]
end end
end end
default_value = ENV[key.to_s] if(!ENV[key.to_s].blank?)
ENV[key.to_s] = default_value.to_s default_value = ENV[key.to_s] if !ENV[key.to_s].blank?
ENV[key.to_s] = default_value.nil? ? nil : default_value.to_s
end end
end end
...@@ -75,13 +79,34 @@ module Annotate ...@@ -75,13 +79,34 @@ module Annotate
options[:model_dir] = ['app/models'] options[:model_dir] = ['app/models']
end end
if(options[:root_dir].empty?)
options[:root_dir] = ['']
end
options[:wrapper_open] ||= options[:wrapper]
options[:wrapper_close] ||= options[:wrapper]
return options return options
end end
def self.reset_options
[POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS].flatten.each do |key|
ENV[key.to_s] = nil
end
end
def self.skip_on_migration? def self.skip_on_migration?
ENV['skip_on_db_migrate'] =~ TRUE_RE ENV['skip_on_db_migrate'] =~ TRUE_RE
end end
def self.include_routes?
ENV['routes'] =~ TRUE_RE
end
def self.include_models?
true
end
def self.loaded_tasks=(val); @loaded_tasks = val; end def self.loaded_tasks=(val); @loaded_tasks = val; end
def self.loaded_tasks; return @loaded_tasks; end def self.loaded_tasks; return @loaded_tasks; end
...@@ -124,7 +149,7 @@ module Annotate ...@@ -124,7 +149,7 @@ module Annotate
def self.bootstrap_rake def self.bootstrap_rake
begin begin
require 'rake/dsl_definition' require 'rake/dsl_definition'
rescue Exception => e rescue Exception
# We might just be on an old version of Rake... # We might just be on an old version of Rake...
end end
require 'rake' require 'rake'
......
...@@ -9,6 +9,8 @@ module AnnotateModels ...@@ -9,6 +9,8 @@ module AnnotateModels
END_MARK = "== Schema Information End" END_MARK = "== Schema Information End"
PATTERN = /^\r?\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\r?\n(#.*\r?\n)*(\r?\n)*/ 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? # File.join for windows reverse bar compat?
# I dont use windows, can`t test # I dont use windows, can`t test
UNIT_TEST_DIR = File.join("test", "unit") UNIT_TEST_DIR = File.join("test", "unit")
...@@ -17,6 +19,12 @@ module AnnotateModels ...@@ -17,6 +19,12 @@ module AnnotateModels
FIXTURE_TEST_DIR = File.join("test", "fixtures") FIXTURE_TEST_DIR = File.join("test", "fixtures")
FIXTURE_SPEC_DIR = File.join("spec", "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")
# Object Daddy http://github.com/flogic/object_daddy/tree/master # Object Daddy http://github.com/flogic/object_daddy/tree/master
EXEMPLARS_TEST_DIR = File.join("test", "exemplars") EXEMPLARS_TEST_DIR = File.join("test", "exemplars")
EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars") EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars")
...@@ -38,36 +46,11 @@ module AnnotateModels ...@@ -38,36 +46,11 @@ module AnnotateModels
SERIALIZERS_TEST_DIR = File.join("test", "serializers") SERIALIZERS_TEST_DIR = File.join("test", "serializers")
SERIALIZERS_SPEC_DIR = File.join("spec", "serializers") SERIALIZERS_SPEC_DIR = File.join("spec", "serializers")
# Controller files
CONTROLLER_DIR = File.join("app", "controllers")
TEST_PATTERNS = [ # Helper files
File.join(UNIT_TEST_DIR, "%MODEL_NAME%_test.rb"), HELPER_DIR = File.join("app", "helpers")
File.join(MODEL_TEST_DIR, "%MODEL_NAME%_test.rb"),
File.join(SPEC_MODEL_DIR, "%MODEL_NAME%_spec.rb"),
]
FIXTURE_PATTERNS = [
File.join(FIXTURE_TEST_DIR, "%TABLE_NAME%.yml"),
File.join(FIXTURE_SPEC_DIR, "%TABLE_NAME%.yml"),
]
FACTORY_PATTERNS = [
File.join(EXEMPLARS_TEST_DIR, "%MODEL_NAME%_exemplar.rb"),
File.join(EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
File.join(BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
File.join(BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
File.join(FACTORY_GIRL_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
File.join(FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
File.join(FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
File.join(FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
File.join(FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
File.join(FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb"),
]
SERIALIZER_PATTERNS = [
File.join(SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
File.join(SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_spec.rb"),
File.join(SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
]
# Don't show limit (#) on these column types # Don't show limit (#) on these column types
# Example: show "integer" instead of "integer(4)" # Example: show "integer" instead of "integer(4)"
...@@ -82,6 +65,72 @@ module AnnotateModels ...@@ -82,6 +65,72 @@ module AnnotateModels
@model_dir = dir @model_dir = dir
end end
def root_dir
@root_dir.is_a?(Array) ? @root_dir : [@root_dir || ""]
end
def root_dir=(dir)
@root_dir = dir
end
def get_patterns(pattern_types=MATCHED_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
end
end
current_patterns.map{ |p| p.sub(/^[\/]*/, '') }
end
# Simple quoting for the default column value # Simple quoting for the default column value
def quote(value) def quote(value)
case value case value
...@@ -91,6 +140,7 @@ module AnnotateModels ...@@ -91,6 +140,7 @@ module AnnotateModels
when Float, Fixnum, Bignum then value.to_s when Float, Fixnum, Bignum then value.to_s
# BigDecimals need to be output in a non-normalized form and quoted. # BigDecimals need to be output in a non-normalized form and quoted.
when BigDecimal then value.to_s('F') when BigDecimal then value.to_s('F')
when Array then value.map {|v| quote(v)}
else else
value.inspect value.inspect
end end
...@@ -127,20 +177,24 @@ module AnnotateModels ...@@ -127,20 +177,24 @@ module AnnotateModels
info<< "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n" info<< "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
end end
cols = klass.columns cols = if ignore_columns = options[:ignore_columns]
if options[:ignore_columns] klass.columns.reject do |col|
cols.reject! { |col| col.name.match(/#{options[:ignore_columns]}/) } col.name.match(/#{ignore_columns}/)
end
else
klass.columns
end end
cols = cols.sort_by(&:name) if(options[:sort]) cols = cols.sort_by(&:name) if(options[:sort])
cols = classified_sort(cols) if(options[:classified_sort]) cols = classified_sort(cols) if(options[:classified_sort])
cols.each do |col| cols.each do |col|
col_type = (col.type || col.sql_type).to_s
attrs = [] attrs = []
attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || col_type == "jsonb"
attrs << "not null" unless col.null 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 << "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)
col_type = (col.type || col.sql_type).to_s
if col_type == "decimal" if col_type == "decimal"
col_type << "(#{col.precision}, #{col.scale})" col_type << "(#{col.precision}, #{col.scale})"
elsif col_type != "spatial" elsif col_type != "spatial"
...@@ -148,7 +202,7 @@ module AnnotateModels ...@@ -148,7 +202,7 @@ module AnnotateModels
if col.limit.is_a? Array if col.limit.is_a? Array
attrs << "(#{col.limit.join(', ')})" attrs << "(#{col.limit.join(', ')})"
else else
col_type << "(#{col.limit})" unless NO_LIMIT_COL_TYPES.include?(col_type) col_type << "(#{col.limit})" unless hide_limit?(col_type, options)
end end
end end
end end
...@@ -227,6 +281,17 @@ module AnnotateModels ...@@ -227,6 +281,17 @@ module AnnotateModels
return index_info return index_info
end end
def hide_limit?(col_type, options)
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={}) def get_foreign_key_info(klass, options={})
if(options[:format_markdown]) if(options[:format_markdown])
fk_info = "#\n# ### Foreign Keys\n#\n" fk_info = "#\n# ### Foreign Keys\n#\n"
...@@ -234,7 +299,9 @@ module AnnotateModels ...@@ -234,7 +299,9 @@ module AnnotateModels
fk_info = "#\n# Foreign Keys\n#\n" fk_info = "#\n# Foreign Keys\n#\n"
end end
foreign_keys = klass.connection.respond_to?(:foreign_keys) ? klass.connection.foreign_keys(klass.table_name) : [] return "" unless 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 max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1
...@@ -274,8 +341,8 @@ module AnnotateModels ...@@ -274,8 +341,8 @@ module AnnotateModels
old_columns = old_header && old_header.scan(column_pattern).sort old_columns = old_header && old_header.scan(column_pattern).sort
new_columns = new_header && new_header.scan(column_pattern).sort new_columns = new_header && new_header.scan(column_pattern).sort
encoding = Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)|(^# -\*- encoding\s?:.*\n)/) 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)/)
encoding_header = old_content.match(encoding).to_s magic_comments= old_content.scan(magic_comment_matcher).flatten.compact
if old_columns == new_columns && !options[:force] if old_columns == new_columns && !options[:force]
return false return false
...@@ -288,17 +355,17 @@ module AnnotateModels ...@@ -288,17 +355,17 @@ module AnnotateModels
end end
wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : "" wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
wrapper_close = options[:wrapper_close] ? "\n# #{options[:wrapper_close]}" : "" wrapper_close = options[:wrapper_close] ? "# #{options[:wrapper_close]}\n" : ""
wrapped_info_block = "#{wrapper_open}#{info_block}#{wrapper_close}" wrapped_info_block = "#{wrapper_open}#{info_block}#{wrapper_close}"
# if there *was* no old schema info (no substitution happened) or :force was passed, # if there *was* no old schema info (no substitution happened) or :force was passed,
# we simply need to insert it in correct position # we simply need to insert it in correct position
if new_content == old_content || options[:force] if new_content == old_content || options[:force]
old_content.sub!(encoding, '') old_content.sub!(magic_comment_matcher, '')
old_content.sub!(PATTERN, '') old_content.sub!(PATTERN, '')
new_content = %w(after bottom).include?(options[position].to_s) ? new_content = %w(after bottom).include?(options[position].to_s) ?
(encoding_header + (old_content.rstrip + "\n\n" + wrapped_info_block)) : (magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block)) :
(encoding_header + wrapped_info_block + "\n" + old_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 }
...@@ -340,6 +407,9 @@ module AnnotateModels ...@@ -340,6 +407,9 @@ module AnnotateModels
# :exclude_fixtures<Symbol>:: whether to skip modification of fixture files # :exclude_fixtures<Symbol>:: whether to skip modification of fixture files
# :exclude_factories<Symbol>:: whether to skip modification of factory files # :exclude_factories<Symbol>:: whether to skip modification of factory files
# :exclude_serializers<Symbol>:: whether to skip modification of serializer 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
# #
def annotate(klass, file, header, options={}) def annotate(klass, file, header, options={})
begin begin
...@@ -353,15 +423,14 @@ module AnnotateModels ...@@ -353,15 +423,14 @@ module AnnotateModels
did_annotate = true did_annotate = true
end end
%w(test fixture factory serializer).each do |key| MATCHED_TYPES.each do |key|
exclusion_key = "exclude_#{key.pluralize}".to_sym exclusion_key = "exclude_#{key.pluralize}".to_sym
patterns_constant = "#{key.upcase}_PATTERNS".to_sym
position_key = "position_in_#{key}".to_sym position_key = "position_in_#{key}".to_sym
unless options[exclusion_key] unless options[exclusion_key]
did_annotate = self.const_get(patterns_constant). did_annotate = self.get_patterns(key).
map { |file| resolve_filename(file, model_name, table_name) }. map { |f| resolve_filename(f, model_name, table_name) }.
map { |file| annotate_one_file(file, info, position_key, options_with_position(options, position_key)) }. map { |f| annotate_one_file(f, info, position_key, options_with_position(options, position_key)) }.
detect { |result| result } || did_annotate detect { |result| result } || did_annotate
end end
end end
...@@ -419,11 +488,11 @@ module AnnotateModels ...@@ -419,11 +488,11 @@ module AnnotateModels
model_path = file.gsub(/\.rb$/, '') model_path = file.gsub(/\.rb$/, '')
model_dir.each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') } model_dir.each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
begin begin
get_loaded_model(model_path) or raise LoadError.new("cannot load a model from #{file}") get_loaded_model(model_path) or raise BadModelFileError.new
rescue LoadError rescue LoadError
# this is for non-rails projects, which don't get Rails auto-require magic # this is for non-rails projects, which don't get Rails auto-require magic
file_path = File.expand_path(file) file_path = File.expand_path(file)
if File.file?(file_path) && Kernel.require(file_path) if File.file?(file_path) && silence_warnings { Kernel.require(file_path) }
retry retry
elsif model_path.match(/\//) elsif model_path.match(/\//)
model_path = model_path.split('/')[1..-1].join('/').to_s model_path = model_path.split('/')[1..-1].join('/').to_s
...@@ -465,13 +534,14 @@ module AnnotateModels ...@@ -465,13 +534,14 @@ module AnnotateModels
end end
self.model_dir = options[:model_dir] if options[:model_dir] self.model_dir = options[:model_dir] if options[:model_dir]
self.root_dir = options[:root_dir] if options[:root_dir]
annotated = [] annotated = []
get_model_files(options).each do |file| get_model_files(options).each do |file|
annotate_model_file(annotated, File.join(file), header, options) annotate_model_file(annotated, File.join(file), header, options)
end end
if annotated.empty? if annotated.empty?
puts "Nothing to annotate." puts "Model files unchanged."
else else
puts "Annotated (#{annotated.length}): #{annotated.join(', ')}" puts "Annotated (#{annotated.length}): #{annotated.join(', ')}"
end end
...@@ -483,9 +553,14 @@ module AnnotateModels ...@@ -483,9 +553,14 @@ module AnnotateModels
klass = get_model_class(file) klass = get_model_class(file)
if klass && klass < ActiveRecord::Base && !klass.abstract_class? && klass.table_exists? if klass && klass < ActiveRecord::Base && !klass.abstract_class? && klass.table_exists?
if annotate(klass, file, header, options) if annotate(klass, file, header, options)
annotated << klass annotated << file
end end
end 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]
end
rescue Exception => e rescue Exception => e
puts "Unable to annotate #{file}: #{e.message}" puts "Unable to annotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace] puts "\t" + e.backtrace.join("\n\t") if options[:trace]
...@@ -494,6 +569,7 @@ module AnnotateModels ...@@ -494,6 +569,7 @@ module AnnotateModels
def remove_annotations(options={}) def remove_annotations(options={})
self.model_dir = options[:model_dir] if options[:model_dir] self.model_dir = options[:model_dir] if options[:model_dir]
self.root_dir = options[:root_dir] if options[:root_dir]
deannotated = [] deannotated = []
deannotated_klass = false deannotated_klass = false
get_model_files(options).each do |file| get_model_files(options).each do |file|
...@@ -506,11 +582,11 @@ module AnnotateModels ...@@ -506,11 +582,11 @@ module AnnotateModels
model_file_name = file 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))
(TEST_PATTERNS + FIXTURE_PATTERNS + FACTORY_PATTERNS + SERIALIZER_PATTERNS). get_patterns.
map { |file| resolve_filename(file, model_name, table_name) }. map { |f| resolve_filename(f, model_name, table_name) }.
each do |file| each do |f|
if File.exist?(file) if File.exist?(f)
remove_annotation_of_file(file) remove_annotation_of_file(f)
deannotated_klass = true deannotated_klass = true
end end
end end
...@@ -527,6 +603,7 @@ module AnnotateModels ...@@ -527,6 +603,7 @@ module AnnotateModels
def resolve_filename(filename_template, model_name, table_name) def resolve_filename(filename_template, model_name, table_name)
return filename_template. return filename_template.
gsub('%MODEL_NAME%', model_name). gsub('%MODEL_NAME%', model_name).
gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize).
gsub('%TABLE_NAME%', table_name || model_name.pluralize) gsub('%TABLE_NAME%', table_name || model_name.pluralize)
end end
...@@ -551,5 +628,19 @@ module AnnotateModels ...@@ -551,5 +628,19 @@ module AnnotateModels
return ([id] << rest_cols << timestamps << associations).flatten return ([id] << rest_cols << timestamps << associations).flatten
end end
# Ignore warnings for the duration of the block ()
def silence_warnings
old_verbose, $VERBOSE = $VERBOSE, nil
yield
ensure
$VERBOSE = old_verbose
end
end
class BadModelFileError < LoadError
def to_s
"file doesn't contain a valid model class"
end
end end
end end
...@@ -37,7 +37,8 @@ module AnnotateRoutes ...@@ -37,7 +37,8 @@ module AnnotateRoutes
"#" "#"
] + routes_map.map { |line| "# #{line}".rstrip } ] + routes_map.map { |line| "# #{line}".rstrip }
(content, where_header_found) = strip_annotations(File.read(routes_file)) existing_text = File.read(routes_file)
(content, where_header_found) = strip_annotations(existing_text)
changed = where_header_found != 0 # This will either be :before, :after, or changed = where_header_found != 0 # This will either be :before, :after, or
# a number. If the number is > 0, the # a number. If the number is > 0, the
# annotation was found somewhere in the # annotation was found somewhere in the
...@@ -60,21 +61,25 @@ module AnnotateRoutes ...@@ -60,21 +61,25 @@ module AnnotateRoutes
content = position_after ? (content + header) : header + content content = position_after ? (content + header) : header + content
write_contents(content) if write_contents(existing_text, content)
puts "#{routes_file} annotated."
puts "Route file annotated." else
puts "#{routes_file} unchanged."
end
end end
def self.remove_annotations(options={}) def self.remove_annotations(options={})
return unless(routes_exists?) return unless(routes_exists?)
existing_text = File.read(routes_file)
(content, where_header_found) = strip_annotations(File.read(routes_file)) (content, where_header_found) = strip_annotations(existing_text)
content = strip_on_removal(content, where_header_found) content = strip_on_removal(content, where_header_found)
write_contents(content) if write_contents(existing_text, content)
puts "Removed annotations from #{routes_file}."
puts "Removed annotations from routes file." else
puts "#{routes_file} unchanged."
end
end end
protected protected
...@@ -89,11 +94,15 @@ protected ...@@ -89,11 +94,15 @@ protected
return routes_exists return routes_exists
end end
def self.write_contents(content) def self.write_contents(existing_text, new_content)
content << '' unless(content.last == '') # Make sure we end on a trailing # Make sure we end on a trailing newline.
# newline. new_content << '' unless(new_content.last == '')
new_text = new_content.join("\n")
return false if existing_text == new_text
File.open(routes_file, "wb") { |f| f.puts(content.join("\n")) } File.open(routes_file, "wb") { |f| f.puts(new_text) }
return true
end end
def self.strip_annotations(content) def self.strip_annotations(content)
......
module Annotate module Annotate
def self.version def self.version
'2.6.10' '2.7.0'
end end
end end
...@@ -5,32 +5,42 @@ if Rails.env.development? ...@@ -5,32 +5,42 @@ if Rails.env.development?
task :set_annotation_options do task :set_annotation_options do
# You can override any of these by setting an environment variable of the # You can override any of these by setting an environment variable of the
# same name. # same name.
Annotate.set_defaults({ Annotate.set_defaults(
'position_in_routes' => "before", 'routes' => 'false',
'position_in_class' => "before", 'position_in_routes' => 'before',
'position_in_test' => "before", 'position_in_class' => 'before',
'position_in_fixture' => "before", 'position_in_test' => 'before',
'position_in_factory' => "before", 'position_in_fixture' => 'before',
'position_in_serializer' => "before", 'position_in_factory' => 'before',
'show_foreign_keys' => "true", 'position_in_serializer' => 'before',
'show_indexes' => "true", 'show_foreign_keys' => 'true',
'simple_indexes' => "false", 'show_indexes' => 'true',
'model_dir' => "app/models", 'simple_indexes' => 'false',
'include_version' => "false", 'model_dir' => 'app/models',
'require' => "", 'root_dir' => '',
'exclude_tests' => "false", 'include_version' => 'false',
'exclude_fixtures' => "false", 'require' => '',
'exclude_factories' => "false", 'exclude_tests' => 'false',
'exclude_serializers' => "false", 'exclude_fixtures' => 'false',
'ignore_model_sub_dir' => "false", 'exclude_factories' => 'false',
'skip_on_db_migrate' => "false", 'exclude_serializers' => 'false',
'format_bare' => "true", 'exclude_scaffolds' => 'false',
'format_rdoc' => "false", 'exclude_controllers' => 'false',
'format_markdown' => "false", 'exclude_helpers' => 'false',
'sort' => "false", 'ignore_model_sub_dir' => 'false',
'force' => "false", 'ignore_columns' => nil,
'trace' => "false", 'ignore_unknown_models' => 'false',
}) 'hide_limit_column_types' => '<%= AnnotateModels::NO_LIMIT_COL_TYPES.join(',') %>',
'skip_on_db_migrate' => 'false',
'format_bare' => 'true',
'format_rdoc' => 'false',
'format_markdown' => 'false',
'sort' => 'false',
'force' => 'false',
'trace' => 'false',
'wrapper_open' => nil,
'wrapper_close' => nil,
)
end end
Annotate.load_tasks Annotate.load_tasks
......
annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__)))
if(!ENV['is_cli']) if !ENV['is_cli']
task :set_annotation_options task :set_annotation_options
task :annotate_models => :set_annotation_options task :annotate_models => :set_annotation_options
end end
...@@ -17,15 +17,20 @@ task :annotate_models => :environment do ...@@ -17,15 +17,20 @@ task :annotate_models => :environment do
options[:position_in_factory] = Annotate.fallback(ENV['position_in_factory'], ENV['position']) options[:position_in_factory] = Annotate.fallback(ENV['position_in_factory'], ENV['position'])
options[:position_in_test] = Annotate.fallback(ENV['position_in_test'], ENV['position']) options[:position_in_test] = Annotate.fallback(ENV['position_in_test'], ENV['position'])
options[:position_in_serializer] = Annotate.fallback(ENV['position_in_serializer'], ENV['position']) options[:position_in_serializer] = Annotate.fallback(ENV['position_in_serializer'], ENV['position'])
options[:show_foreign_keys] = Annotate.true?(ENV['show_foreign_keys'])
options[:show_indexes] = Annotate.true?(ENV['show_indexes']) options[:show_indexes] = Annotate.true?(ENV['show_indexes'])
options[:simple_indexes] = Annotate.true?(ENV['simple_indexes']) options[:simple_indexes] = Annotate.true?(ENV['simple_indexes'])
options[:model_dir] = ENV['model_dir'] ? ENV['model_dir'].split(',') : [] options[:model_dir] = ENV['model_dir'] ? ENV['model_dir'].split(',') : ['app/models']
options[:root_dir] = ENV['root_dir'] ? ENV['root_dir'].split(',') : ['']
options[:include_version] = Annotate.true?(ENV['include_version']) options[:include_version] = Annotate.true?(ENV['include_version'])
options[:require] = ENV['require'] ? ENV['require'].split(',') : [] options[:require] = ENV['require'] ? ENV['require'].split(',') : []
options[:exclude_tests] = Annotate.true?(ENV['exclude_tests']) options[:exclude_tests] = Annotate.true?(ENV['exclude_tests'])
options[:exclude_factories] = Annotate.true?(ENV['exclude_factories']) options[:exclude_factories] = Annotate.true?(ENV['exclude_factories'])
options[:exclude_fixtures] = Annotate.true?(ENV['exclude_fixtures']) options[:exclude_fixtures] = Annotate.true?(ENV['exclude_fixtures'])
options[:exclude_serializers] = Annotate.true?(ENV['exclude_serializers']) options[:exclude_serializers] = Annotate.true?(ENV['exclude_serializers'])
options[:exclude_scaffolds] = Annotate.true?(ENV['exclude_scaffolds'])
options[:exclude_controllers] = Annotate.true?(ENV['exclude_controllers'])
options[:exclude_helpers] = Annotate.true?(ENV['exclude_helpers'])
options[:ignore_model_sub_dir] = Annotate.true?(ENV['ignore_model_sub_dir']) options[:ignore_model_sub_dir] = Annotate.true?(ENV['ignore_model_sub_dir'])
options[:format_bare] = Annotate.true?(ENV['format_bare']) options[:format_bare] = Annotate.true?(ENV['format_bare'])
options[:format_rdoc] = Annotate.true?(ENV['format_rdoc']) options[:format_rdoc] = Annotate.true?(ENV['format_rdoc'])
...@@ -36,6 +41,8 @@ task :annotate_models => :environment do ...@@ -36,6 +41,8 @@ task :annotate_models => :environment do
options[:trace] = Annotate.true?(ENV['trace']) options[:trace] = Annotate.true?(ENV['trace'])
options[:wrapper_open] = Annotate.fallback(ENV['wrapper_open'], ENV['wrapper']) options[:wrapper_open] = Annotate.fallback(ENV['wrapper_open'], ENV['wrapper'])
options[:wrapper_close] = Annotate.fallback(ENV['wrapper_close'], ENV['wrapper']) options[:wrapper_close] = Annotate.fallback(ENV['wrapper_close'], ENV['wrapper'])
options[:ignore_columns] = ENV.fetch('ignore_columns', nil)
AnnotateModels.do_annotations(options) AnnotateModels.do_annotations(options)
end end
...@@ -46,6 +53,7 @@ task :remove_annotation => :environment do ...@@ -46,6 +53,7 @@ task :remove_annotation => :environment do
options={ :is_rake => true } options={ :is_rake => true }
options[:model_dir] = ENV['model_dir'] options[:model_dir] = ENV['model_dir']
options[:root_dir] = ENV['root_dir']
options[:require] = ENV['require'] ? ENV['require'].split(',') : [] options[:require] = ENV['require'] ? ENV['require'].split(',') : []
options[:trace] = Annotate.true?(ENV['trace']) options[:trace] = Annotate.true?(ENV['trace'])
AnnotateModels.remove_annotations(options) AnnotateModels.remove_annotations(options)
......
...@@ -3,20 +3,23 @@ ...@@ -3,20 +3,23 @@
# Append annotations to Rake tasks for ActiveRecord, so annotate automatically gets # Append annotations to Rake tasks for ActiveRecord, so annotate automatically gets
# run after doing db:migrate. # run after doing db:migrate.
# Unfortunately it relies on ENV for options; it'd be nice to be able to set options
# in a per-project config file so this task can read them.
namespace :db do namespace :db do
task :migrate do [:migrate, :rollback].each do |cmd|
task cmd do
Rake::Task['set_annotation_options'].invoke
Annotate::Migration.update_annotations Annotate::Migration.update_annotations
end end
namespace :migrate do namespace cmd do
[:change, :up, :down, :reset, :redo].each do |t| [:change, :up, :down, :reset, :redo].each do |t|
task t do task t do
Rake::Task['set_annotation_options'].invoke
Annotate::Migration.update_annotations Annotate::Migration.update_annotations
end end
end end
end end
end
end end
module Annotate module Annotate
...@@ -24,9 +27,25 @@ module Annotate ...@@ -24,9 +27,25 @@ module Annotate
@@working = false @@working = false
def self.update_annotations def self.update_annotations
unless @@working || (ENV['skip_on_db_migrate'] =~ /(true|t|yes|y|1)$/i) unless @@working || Annotate.skip_on_migration?
@@working = true @@working = true
Rake::Task['annotate_models'].invoke
self.update_models if Annotate.include_models?
self.update_routes if Annotate.include_routes?
end
end
def self.update_models
if Rake::Task.task_defined?("annotate_models")
Rake::Task["annotate_models"].invoke
elsif Rake::Task.task_defined?("app:annotate_models")
Rake::Task["app:annotate_models"].invoke
end
end
def self.update_routes
if Rake::Task.task_defined?("annotate_routes")
Rake::Task["annotate_routes"].invoke
end end
end end
end end
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
require File.dirname(__FILE__) + '/../spec_helper.rb' require File.dirname(__FILE__) + '/../spec_helper.rb'
require 'annotate/annotate_models' require 'annotate/annotate_models'
require 'annotate/active_record_patch' require 'annotate/active_record_patch'
require 'active_support/core_ext/string'
describe AnnotateModels do describe AnnotateModels do
def mock_foreign_key(name, from_column, to_table, to_column = 'id') def mock_foreign_key(name, from_column, to_table, to_column = 'id')
...@@ -17,6 +18,7 @@ describe AnnotateModels do ...@@ -17,6 +18,7 @@ describe AnnotateModels do
double("Conn", double("Conn",
:indexes => indexes, :indexes => indexes,
:foreign_keys => foreign_keys, :foreign_keys => foreign_keys,
:supports_foreign_keys? => true,
) )
end end
...@@ -56,11 +58,14 @@ describe AnnotateModels do ...@@ -56,11 +58,14 @@ describe AnnotateModels do
it { expect(AnnotateModels.quote(25)).to eql("25") } it { expect(AnnotateModels.quote(25)).to eql("25") }
it { expect(AnnotateModels.quote(25.6)).to eql("25.6") } it { expect(AnnotateModels.quote(25.6)).to eql("25.6") }
it { expect(AnnotateModels.quote(1e-20)).to eql("1.0e-20") } it { expect(AnnotateModels.quote(1e-20)).to eql("1.0e-20") }
it { expect(AnnotateModels.quote(BigDecimal.new("1.2"))).to eql("1.2") }
it { expect(AnnotateModels.quote([BigDecimal.new("1.2")])).to eql(["1.2"]) }
it "should get schema info" do it "should get schema info with default options" do
klass = mock_class(:users, :id, [ klass = mock_class(:users, :id, [
mock_column(:id, :integer), mock_column(:id, :integer, :limit => 8),
mock_column(:name, :string, :limit => 50) mock_column(:name, :string, :limit => 50),
mock_column(:notes, :text, :limit => 55),
]) ])
expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS) expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS)
...@@ -70,6 +75,7 @@ describe AnnotateModels do ...@@ -70,6 +75,7 @@ describe AnnotateModels do
# #
# id :integer not null, primary key # id :integer not null, primary key
# name :string(50) not null # name :string(50) not null
# notes :text(55) not null
# #
EOS EOS
end end
...@@ -189,6 +195,61 @@ EOS ...@@ -189,6 +195,61 @@ EOS
EOS EOS
end end
describe "#get_schema_info with custom options" do
def self.when_called_with(options = {})
expected = options.delete(:returns)
it "should work with options = #{options}" do
klass = mock_class(:users, :id, [
mock_column(:id, :integer, :limit => 8),
mock_column(:active, :boolean, :limit => 1),
mock_column(:name, :string, :limit => 50),
mock_column(:notes, :text, :limit => 55),
])
schema_info = AnnotateModels.get_schema_info(klass, "Schema Info", options)
expect(schema_info).to eql(expected)
end
end
when_called_with hide_limit_column_types: '', returns: <<-EOS.strip_heredoc
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key
# active :boolean not null
# name :string(50) not null
# notes :text(55) not null
#
EOS
when_called_with hide_limit_column_types: 'integer,boolean', returns:
<<-EOS.strip_heredoc
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key
# active :boolean not null
# name :string(50) not null
# notes :text(55) not null
#
EOS
when_called_with hide_limit_column_types: 'integer,boolean,string,text', returns:
<<-EOS.strip_heredoc
# Schema Info
#
# Table name: users
#
# id :integer not null, primary key
# active :boolean not null
# name :string not null
# notes :text not null
#
EOS
end
describe "#get_model_class" do describe "#get_model_class" do
require "tmpdir" require "tmpdir"
...@@ -435,6 +496,35 @@ end ...@@ -435,6 +496,35 @@ end
end end
end end
describe '#resolve_filename' do
it 'should return the test path for a model' do
filename_template = 'test/unit/%MODEL_NAME%_test.rb'
model_name = 'example_model'
table_name = 'example_models'
filename = AnnotateModels.resolve_filename(filename_template, model_name, table_name)
expect(filename). to eq 'test/unit/example_model_test.rb'
end
it 'should return the fixture path for a model' do
filename_template = 'test/fixtures/%TABLE_NAME%.yml'
model_name = 'example_model'
table_name = 'example_models'
filename = AnnotateModels.resolve_filename(filename_template, model_name, table_name)
expect(filename). to eq 'test/fixtures/example_models.yml'
end
it 'should return the fixture path for a nested model' do
filename_template = 'test/fixtures/%PLURALIZED_MODEL_NAME%.yml'
model_name = 'parent/child'
table_name = 'parent_children'
filename = AnnotateModels.resolve_filename(filename_template, model_name, table_name)
expect(filename). to eq 'test/fixtures/parent/children.yml'
end
end
describe "annotating a file" do describe "annotating a file" do
before do before do
@model_dir = Dir.mktmpdir('annotate_models') @model_dir = Dir.mktmpdir('annotate_models')
...@@ -448,6 +538,7 @@ end ...@@ -448,6 +538,7 @@ end
mock_column(:name, :string, :limit => 50) mock_column(:name, :string, :limit => 50)
]) ])
@schema_info = AnnotateModels.get_schema_info(@klass, "== Schema Info") @schema_info = AnnotateModels.get_schema_info(@klass, "== Schema Info")
Annotate.reset_options
end end
def write_model file_name, file_content def write_model file_name, file_content
...@@ -469,14 +560,20 @@ end ...@@ -469,14 +560,20 @@ end
Annotate::PATH_OPTIONS.each { |key| ENV[key.to_s] = '' } Annotate::PATH_OPTIONS.each { |key| ENV[key.to_s] = '' }
end end
def encoding_comments_list_each def magic_comments_list_each
[ [
'# encoding: UTF-8', '# encoding: UTF-8',
'# coding: UTF-8', '# coding: UTF-8',
'# -*- coding: UTF-8 -*-', '# -*- coding: UTF-8 -*-',
'#encoding: utf-8', '#encoding: utf-8',
'# -*- encoding : utf-8 -*-' '# encoding: utf-8',
].each{|encoding_comment| yield encoding_comment } '# -*- encoding : utf-8 -*-',
"# encoding: utf-8\n# frozen_string_literal: true",
"# frozen_string_literal: true\n# encoding: utf-8",
'# frozen_string_literal: true',
'#frozen_string_literal: false',
'# -*- frozen_string_literal : true -*-',
].each{|magic_comment| yield magic_comment }
end end
it "should put annotation before class if :position == 'before'" do it "should put annotation before class if :position == 'before'" do
...@@ -521,7 +618,7 @@ end ...@@ -521,7 +618,7 @@ end
it 'should wrap annotation if wrapper is specified' do it 'should wrap annotation if wrapper is specified' do
annotate_one_file :wrapper_open => 'START', :wrapper_close => 'END' annotate_one_file :wrapper_open => 'START', :wrapper_close => 'END'
expect(File.read(@model_file_name)).to eq("# START\n#{@schema_info}\n# END\n#{@file_content}") expect(File.read(@model_file_name)).to eq("# START\n#{@schema_info}# END\n\n#{@file_content}")
end end
describe "with existing annotation => :before" do describe "with existing annotation => :before" do
...@@ -593,17 +690,22 @@ end ...@@ -593,17 +690,22 @@ end
expect(File.read(model_file_name)).to eq("#{schema_info}\n#{file_content}") expect(File.read(model_file_name)).to eq("#{schema_info}\n#{file_content}")
end end
it "should not touch encoding comments" do it "should not touch magic comments" do
encoding_comments_list_each do |encoding_comment| magic_comments_list_each do |magic_comment|
write_model "user.rb", <<-EOS write_model "user.rb", <<-EOS
#{encoding_comment} #{magic_comment}
class User < ActiveRecord::Base class User < ActiveRecord::Base
end end
EOS EOS
annotate_one_file :position => :before annotate_one_file :position => :before
expect(File.open(@model_file_name, &:readline)).to eq("#{encoding_comment}\n") lines= magic_comment.split("\n")
File.open @model_file_name do |file|
lines.count.times do |index|
expect(file.readline).to eq "#{lines[index]}\n"
end
end
end end
end end
......
...@@ -2,85 +2,114 @@ require File.dirname(__FILE__) + '/../spec_helper.rb' ...@@ -2,85 +2,114 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
require 'annotate/annotate_routes' require 'annotate/annotate_routes'
describe AnnotateRoutes do describe AnnotateRoutes do
ROUTE_FILE = "config/routes.rb"
ANNOTATION_ADDED = "#{ROUTE_FILE} annotated."
ANNOTATION_REMOVED = "Removed annotations from #{ROUTE_FILE}."
FILE_UNCHANGED = "#{ROUTE_FILE} unchanged."
def mock_file(stubs={}) def mock_file(stubs={})
@mock_file ||= double(File, stubs) @mock_file ||= double(File, stubs)
end end
it "should check if routes.rb exists" do it "should check if routes.rb exists" do
expect(File).to receive(:exists?).with("config/routes.rb").and_return(false) expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(false)
expect(AnnotateRoutes).to receive(:puts).with("Can`t find routes.rb") expect(AnnotateRoutes).to receive(:puts).with("Can`t find routes.rb")
AnnotateRoutes.do_annotations AnnotateRoutes.do_annotations
end end
describe "When Annotating, with older Rake Versions" do describe "When adding" do
before(:each) do
expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(true)
expect(AnnotateRoutes).to receive(:`).with("rake routes").and_return("")
end
it "should insert annotations if file does not contain annotations" do
expect(File).to receive(:read).with(ROUTE_FILE).and_return("")
expect(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file)
expect(@mock_file).to receive(:puts).with("\n# == Route Map\n#\n")
expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED)
AnnotateRoutes.do_annotations
end
it "should skip annotations if file does already contain annotation" do
expect(File).to receive(:read).with(ROUTE_FILE).and_return("\n# == Route Map\n#\n")
expect(AnnotateRoutes).to receive(:puts).with(FILE_UNCHANGED)
AnnotateRoutes.do_annotations
end
end
describe "When adding with older Rake versions" do
before(:each) do before(:each) do
expect(File).to receive(:exists?).with("config/routes.rb").and_return(true) expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(true)
expect(AnnotateRoutes).to receive(:`).with("rake routes").and_return("(in /bad/line)\ngood line") expect(AnnotateRoutes).to receive(:`).with("rake routes").and_return("(in /bad/line)\ngood line")
expect(File).to receive(:open).with("config/routes.rb", "wb").and_yield(mock_file) expect(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file)
expect(AnnotateRoutes).to receive(:puts).with("Route file annotated.") expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED)
end end
it "should annotate and add a newline!" do it "should annotate and add a newline!" do
expect(File).to receive(:read).with("config/routes.rb").and_return("ActionController::Routing...\nfoo") expect(File).to receive(:read).with(ROUTE_FILE).and_return("ActionController::Routing...\nfoo")
expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# good line\n/) expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# good line\n/)
AnnotateRoutes.do_annotations AnnotateRoutes.do_annotations
end end
it "should not add a newline if there are empty lines" do it "should not add a newline if there are empty lines" do
expect(File).to receive(:read).with("config/routes.rb").and_return("ActionController::Routing...\nfoo\n") expect(File).to receive(:read).with(ROUTE_FILE).and_return("ActionController::Routing...\nfoo\n")
expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# good line\n/) expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# good line\n/)
AnnotateRoutes.do_annotations AnnotateRoutes.do_annotations
end end
end end
describe "When Annotating, with newer Rake Versions" do describe "When adding with newer Rake versions" do
before(:each) do before(:each) do
expect(File).to receive(:exists?).with("config/routes.rb").and_return(true) expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(true)
expect(AnnotateRoutes).to receive(:`).with("rake routes").and_return("another good line\ngood line") expect(AnnotateRoutes).to receive(:`).with("rake routes").and_return("another good line\ngood line")
expect(File).to receive(:open).with("config/routes.rb", "wb").and_yield(mock_file) expect(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file)
expect(AnnotateRoutes).to receive(:puts).with("Route file annotated.") expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED)
end end
it "should annotate and add a newline!" do it "should annotate and add a newline!" do
expect(File).to receive(:read).with("config/routes.rb").and_return("ActionController::Routing...\nfoo") expect(File).to receive(:read).with(ROUTE_FILE).and_return("ActionController::Routing...\nfoo")
expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# another good line\n# good line\n/) expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# another good line\n# good line\n/)
AnnotateRoutes.do_annotations AnnotateRoutes.do_annotations
end end
it "should not add a newline if there are empty lines" do it "should not add a newline if there are empty lines" do
expect(File).to receive(:read).with("config/routes.rb").and_return("ActionController::Routing...\nfoo\n") expect(File).to receive(:read).with(ROUTE_FILE).and_return("ActionController::Routing...\nfoo\n")
expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# another good line\n# good line\n/) expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map\n#\n# another good line\n# good line\n/)
AnnotateRoutes.do_annotations AnnotateRoutes.do_annotations
end end
it "should add a timestamp when :timestamp is passed" do it "should add a timestamp when :timestamp is passed" do
expect(File).to receive(:read).with("config/routes.rb").and_return("ActionController::Routing...\nfoo") expect(File).to receive(:read).with(ROUTE_FILE).and_return("ActionController::Routing...\nfoo")
expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map \(Updated \d{4}-\d{2}-\d{2} \d{2}:\d{2}\)\n#\n# another good line\n# good line\n/) expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n# == Route Map \(Updated \d{4}-\d{2}-\d{2} \d{2}:\d{2}\)\n#\n# another good line\n# good line\n/)
AnnotateRoutes.do_annotations :timestamp => true AnnotateRoutes.do_annotations :timestamp => true
end end
end end
describe "When Removing Annotation" do describe "When removing" do
before(:each) do before(:each) do
expect(File).to receive(:exists?).with("config/routes.rb").and_return(true) expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(true)
expect(File).to receive(:open).with("config/routes.rb", "wb").and_yield(mock_file) expect(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file)
expect(AnnotateRoutes).to receive(:puts).with("Removed annotations from routes file.") expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_REMOVED)
end end
it "should remove trailing annotation and trim trailing newlines, but leave leading newlines alone" do it "should remove trailing annotation and trim trailing newlines, but leave leading newlines alone" do
expect(File).to receive(:read).with("config/routes.rb").and_return("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nActionController::Routing...\nfoo\n\n\n\n\n\n\n\n\n\n\n# == Route Map\n#\n# another good line\n# good line\n") expect(File).to receive(:read).with(ROUTE_FILE).and_return("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nActionController::Routing...\nfoo\n\n\n\n\n\n\n\n\n\n\n# == Route Map\n#\n# another good line\n# good line\n")
expect(@mock_file).to receive(:puts).with(/\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nActionController::Routing...\nfoo\n/) expect(@mock_file).to receive(:puts).with(/\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nActionController::Routing...\nfoo\n/)
AnnotateRoutes.remove_annotations AnnotateRoutes.remove_annotations
end end
it "should remove prepended annotation and trim leading newlines, but leave trailing newlines alone" do it "should remove prepended annotation and trim leading newlines, but leave trailing newlines alone" do
expect(File).to receive(:read).with("config/routes.rb").and_return("# == Route Map\n#\n# another good line\n# good line\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nActionController::Routing...\nfoo\n\n\n\n\n\n\n\n\n\n\n") expect(File).to receive(:read).with(ROUTE_FILE).and_return("# == Route Map\n#\n# another good line\n# good line\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nActionController::Routing...\nfoo\n\n\n\n\n\n\n\n\n\n\n")
expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n\n\n\n\n\n\n\n\n\n/) expect(@mock_file).to receive(:puts).with(/ActionController::Routing...\nfoo\n\n\n\n\n\n\n\n\n\n\n/)
AnnotateRoutes.remove_annotations AnnotateRoutes.remove_annotations
end end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment