Commit 0ef095dc by Andrew W. Lee

Merge branch 'release/2.7.5'

parents 89bce57f 6a43890e
Describe your problem here.
## Commands
```
$ show your commands here.
```
## Version
- annotate version
- rails version
- ruby version
......@@ -4,6 +4,7 @@ rvm:
- 2.2.7
- 2.3.4
- 2.4.1
- 2.6.0
- ruby-head
matrix:
allow_failures:
......
== 2.7.5
See https://github.com/ctran/annotate_models/releases/tag/v2.7.5
== 2.7.3
See https://github.com/ctran/annotate_models/releases/tag/v2.7.3
......
......@@ -19,7 +19,7 @@ group :development, :test do
gem 'guard-rspec', require: false
gem 'rspec', require: false
gem 'rubocop', '~> 0.46.0', require: false unless RUBY_VERSION =~ /^1.8/
gem 'rubocop', '~> 0.67.2', require: false unless RUBY_VERSION =~ /^1.8/
gem 'simplecov', require: false
gem 'terminal-notifier-guard', require: false
......
......@@ -203,6 +203,7 @@ you can do so with a simple environment variable, instead of editing the
-f [bare|rdoc|markdown], Render Schema Infomation as plain/RDoc/Markdown
--format
--force Force new annotations even if there are no changes.
--frozen Do not allow to change annotations. Exits non-zero if there are going to be changes to files.
--timestamp Include timestamp in (routes) annotation
--trace If unable to annotate a file, print the full stack trace, not just the exception message.
-I, --ignore-columns REGEX don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`
......
......@@ -44,5 +44,5 @@ Gem::Specification.new do |s|
s.specification_version = 4 if s.respond_to? :specification_version
s.add_runtime_dependency(%q<rake>, ['>= 10.4', '< 13.0'])
s.add_runtime_dependency(%q<activerecord>, ['>= 3.2', '< 6.0'])
s.add_runtime_dependency(%q<activerecord>, ['>= 3.2', '< 7.0'])
end
......@@ -170,6 +170,10 @@ OptionParser.new do |opts|
ENV['force'] = 'yes'
end
opts.on('--frozen', 'Do not allow to change annotations. Exits non-zero if there are going to be changes to files.') do
ENV['frozen'] = 'yes'
end
opts.on('--timestamp', 'Include timestamp in (routes) annotation') do
ENV['timestamp'] = 'true'
end
......
......@@ -29,8 +29,8 @@ module Annotate
FLAG_OPTIONS = [
:show_indexes, :simple_indexes, :include_version, :exclude_tests,
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
:timestamp, :exclude_serializers, :classified_sort,
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :frozen,
:trace, :timestamp, :exclude_serializers, :classified_sort,
:show_foreign_keys, :show_complete_foreign_keys,
:exclude_scaffolds, :exclude_controllers, :exclude_helpers,
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment
......
......@@ -62,7 +62,7 @@ module AnnotateModels
# Don't show limit (#) on these column types
# Example: show "integer" instead of "integer(4)"
NO_LIMIT_COL_TYPES = %w(integer boolean).freeze
NO_LIMIT_COL_TYPES = %w(integer bigint boolean).freeze
# Don't show default value for these column types
NO_DEFAULT_COL_TYPES = %w(json jsonb hstore).freeze
......@@ -301,7 +301,7 @@ module AnnotateModels
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"
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 << format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
end
end
......@@ -362,7 +362,7 @@ module AnnotateModels
end
def get_col_type(col)
if col.respond_to?(:bigint?) && col.bigint?
if (col.respond_to?(:bigint?) && col.bigint?) || /\Abigint\b/ =~ col.sql_type
'bigint'
else
(col.type || col.sql_type).to_s
......@@ -498,7 +498,7 @@ module AnnotateModels
# :before, :top, :after or :bottom. Default is :before.
#
def annotate_one_file(file_name, info_block, position, options = {})
if File.exist?(file_name)
return false unless File.exist?(file_name)
old_content = File.read(file_name)
return false if old_content =~ /#{SKIP_ANNOTATION_PREFIX}.*\n/
......@@ -511,40 +511,43 @@ module AnnotateModels
old_columns = old_header && old_header.scan(column_pattern).sort
new_columns = new_header && new_header.scan(column_pattern).sort
magic_comments_block = magic_comments_as_string(old_content)
return false if old_columns == new_columns && !options[:force]
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(annotate_pattern(options), info_block + "\n")
if new_content.end_with?(info_block + "\n")
new_content = old_content.sub(annotate_pattern(options), "\n" + info_block)
end
abort "annotate error. #{file_name} needs to be updated, but annotate was run with `--frozen`." if options[:frozen]
# Replace inline the old schema info with the new schema info
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_annotation = old_content.match(annotate_pattern(options)).to_s
# if there *was* no old schema info or :force was passed, we simply
# need to insert it in correct position
if old_annotation.empty? || options[:force]
magic_comments_block = magic_comments_as_string(old_content)
old_content.gsub!(magic_comment_matcher, '')
old_content.sub!(annotate_pattern(options), '')
new_content = if %w(after bottom).include?(options[position].to_s)
magic_comments_block + (old_content.rstrip + "\n\n" + wrapped_info_block)
else
elsif magic_comments_block.empty?
magic_comments_block + wrapped_info_block + "\n" + old_content
else
magic_comments_block + "\n" + wrapped_info_block + "\n" + old_content
end
else
# replace the old annotation with the new one
# keep the surrounding whitespace the same
space_match = old_annotation.match(/\A(?<start>\s*).*?\n(?<end>\s*)\z/m)
new_annotation = space_match[:start] + wrapped_info_block + space_match[:end]
new_content = old_content.sub(annotate_pattern(options), new_annotation)
end
File.open(file_name, 'wb') { |f| f.puts new_content }
return true
end
else
false
end
true
end
def magic_comment_matcher
......@@ -555,7 +558,7 @@ module AnnotateModels
magic_comments = content.scan(magic_comment_matcher).flatten.compact
if magic_comments.any?
magic_comments.join + "\n"
magic_comments.join
else
''
end
......@@ -885,7 +888,7 @@ module AnnotateModels
def max_schema_info_width(klass, options)
if with_comments?(klass, options)
max_size = klass.columns.map do |column|
column.name.size + (column.comment ? column.comment.size : 0)
column.name.size + (column.comment ? width(column.comment) : 0)
end.max || 0
max_size += 2
else
......@@ -895,6 +898,20 @@ module AnnotateModels
max_size
end
def format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
sprintf("# %s:%s %s", mb_chars_ljust(col_name, max_size), mb_chars_ljust(col_type, bare_type_allowance), attrs.join(", ")).rstrip + "\n"
end
def width(string)
string.chars.inject(0) { |acc, elem| acc + (elem.bytesize == 1 ? 1 : 2) }
end
def mb_chars_ljust(string, length)
string = string.to_s
padding = length - width(string)
string + (' ' * padding)
end
end
class BadModelFileError < LoadError
......
module Annotate
def self.version
'2.7.3'
'2.7.5'
end
end
......@@ -42,11 +42,12 @@ if Rails.env.development?
'format_markdown' => 'false',
'sort' => 'false',
'force' => 'false',
'frozen' => 'false',
'classified_sort' => 'true',
'trace' => 'false',
'wrapper_open' => nil,
'wrapper_close' => nil,
'with_comment' => true
'with_comment' => 'true'
)
end
......
......@@ -39,6 +39,7 @@ task annotate_models: :environment do
options[:format_markdown] = Annotate.true?(ENV['format_markdown'])
options[:sort] = Annotate.true?(ENV['sort'])
options[:force] = Annotate.true?(ENV['force'])
options[:frozen] = Annotate.true?(ENV['frozen'])
options[:classified_sort] = Annotate.true?(ENV['classified_sort'])
options[:trace] = Annotate.true?(ENV['trace'])
options[:wrapper_open] = Annotate.fallback(ENV['wrapper_open'], ENV['wrapper'])
......@@ -47,7 +48,8 @@ task annotate_models: :environment do
options[:ignore_routes] = ENV.fetch('ignore_routes', nil)
options[:hide_limit_column_types] = Annotate.fallback(ENV['hide_limit_column_types'], '')
options[:hide_default_column_types] = Annotate.fallback(ENV['hide_default_column_types'], '')
options[:with_comment] = Annotate.fallback(ENV['with_comment'], '')
options[:with_comment] = Annotate.true?(ENV['with_comment'])
options[:ignore_unknown_models] = Annotate.true?(ENV.fetch('ignore_unknown_models', 'false'))
AnnotateModels.do_annotations(options)
end
......
......@@ -4,21 +4,17 @@
# Append annotations to Rake tasks for ActiveRecord, so annotate automatically gets
# run after doing db:migrate.
namespace :db do
[:migrate, :rollback].each do |cmd|
task cmd do
Rake::Task['set_annotation_options'].invoke
%w(db:migrate db:migrate:up db:migrate:down db:migrate:reset db:migrate:redo db:rollback).each do |task|
Rake::Task[task].enhance do
Rake::Task[Rake.application.top_level_tasks.last].enhance do
annotation_options_task = if Rake::Task.task_defined?('app:set_annotation_options')
'app:set_annotation_options'
else
'set_annotation_options'
end
Rake::Task[annotation_options_task].invoke
Annotate::Migration.update_annotations
end
namespace cmd do
[:change, :up, :down, :reset, :redo].each do |t|
task t do
Rake::Task['set_annotation_options'].invoke
Annotate::Migration.update_annotations
end
end
end
end
end
......@@ -46,6 +42,8 @@ module Annotate
def self.update_routes
if Rake::Task.task_defined?("annotate_routes")
Rake::Task["annotate_routes"].invoke
elsif Rake::Task.task_defined?("app:annotate_routes")
Rake::Task["app:annotate_routes"].invoke
end
end
end
......
......@@ -5,7 +5,7 @@ require 'annotate/active_record_patch'
require 'active_support/core_ext/string'
require 'files'
describe AnnotateModels do
describe AnnotateModels do # rubocop:disable Metrics/BlockLength
def mock_index(name, params = {})
double('IndexKeyDefinition',
name: name,
......@@ -52,7 +52,8 @@ describe AnnotateModels do
default_options = {
limit: nil,
null: false,
default: nil
default: nil,
sql_type: type
}
stubs = default_options.dup
......@@ -184,6 +185,7 @@ EOS
mock_column(:id, :integer),
mock_column(:integer, :integer, unsigned?: true),
mock_column(:bigint, :integer, unsigned?: true, bigint?: true),
mock_column(:bigint, :bigint, unsigned?: true),
mock_column(:float, :float, unsigned?: true),
mock_column(:decimal, :decimal, unsigned?: true, precision: 10, scale: 2),
])
......@@ -196,6 +198,7 @@ EOS
# id :integer not null
# integer :integer unsigned, not null
# bigint :bigint unsigned, not null
# bigint :bigint unsigned, not null
# float :float unsigned, not null
# decimal :decimal(10, 2) unsigned, not null
#
......@@ -935,6 +938,29 @@ EOS
#
EOS
mocked_columns_with_multibyte_comment = [
[:id, :integer, { limit: 8, comment: 'ID' }],
[:active, :boolean, { limit: 1, comment: 'ACTIVE' }],
[:name, :string, { limit: 50, comment: 'NAME' }],
[:notes, :text, { limit: 55, comment: 'NOTES' }],
[:no_comment, :text, { limit: 20, comment: nil }]
]
when_called_with with_comment: 'yes',
with_columns: mocked_columns_with_multibyte_comment, returns:
<<-EOS.strip_heredoc
# Schema Info
#
# Table name: users
#
# id(ID) :integer not null, primary key
# active(ACTIVE) :boolean not null
# name(NAME) :string(50) not null
# notes(NOTES) :text(55) not null
# no_comment :text(20) not null
#
EOS
it 'should get schema info as RDoc' do
klass = mock_class(:users,
:id,
......@@ -1671,7 +1697,7 @@ end
end
end
it 'adds an empty line between magic comments and model file content (position :after)' do
it 'does not change whitespace between magic comments and model file content (position :after)' do
content = "class User < ActiveRecord::Base\nend\n"
magic_comments_list_each do |magic_comment|
model_file_name, = write_model 'user.rb', "#{magic_comment}\n#{content}"
......@@ -1679,7 +1705,7 @@ end
annotate_one_file position: :after
schema_info = AnnotateModels.get_schema_info(@klass, '== Schema Info')
expect(File.read(model_file_name)).to eq("#{magic_comment}\n\n#{content}\n#{schema_info}")
expect(File.read(model_file_name)).to eq("#{magic_comment}\n#{content}\n#{schema_info}")
end
end
......@@ -1742,6 +1768,25 @@ end
expect(error_output).to include("/user.rb:2:in `<class:User>'")
end
end
describe 'frozen option' do
it "should abort without existing annotation when frozen: true " do
expect { annotate_one_file frozen: true }.to raise_error SystemExit, /user.rb needs to be updated, but annotate was run with `--frozen`./
end
it "should abort with different annotation when frozen: true " do
annotate_one_file
another_schema_info = AnnotateModels.get_schema_info(mock_class(:users, :id, [mock_column(:id, :integer)]), '== Schema Info')
@schema_info = another_schema_info
expect { annotate_one_file frozen: true }.to raise_error SystemExit, /user.rb needs to be updated, but annotate was run with `--frozen`./
end
it "should NOT abort with same annotation when frozen: true " do
annotate_one_file
expect { annotate_one_file frozen: true }.not_to raise_error
end
end
end
describe '.annotate_model_file' do
......
......@@ -61,8 +61,8 @@ module Rails
else
gem 'rails'
end
rescue Gem::LoadError => load_error
if load_error.message =~ /Could not find RubyGem rails/
rescue Gem::LoadError => e
if e.message =~ /Could not find RubyGem rails/
$stderr.puts "Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed."
exit 1
else
......
require_relative '../spec_helper'
describe 'ActiveRecord migration rake task hooks' do
before do
Rake.application = Rake::Application.new
# Stub migration tasks
%w(db:migrate db:migrate:up db:migrate:down db:migrate:reset db:rollback).each do |task|
Rake::Task.define_task(task)
end
Rake::Task.define_task('db:migrate:redo') do
Rake::Task['db:rollback'].invoke
Rake::Task['db:migrate'].invoke
end
Rake::Task.define_task('set_annotation_options')
Rake.load_rakefile('tasks/annotate_models_migrate.rake')
Rake.application.instance_variable_set(:@top_level_tasks, [subject])
end
describe 'db:migrate' do
it 'should update annotations' do
expect(Annotate::Migration).to receive(:update_annotations)
Rake.application.top_level
end
end
describe 'db:migrate:up' do
it 'should update annotations' do
expect(Annotate::Migration).to receive(:update_annotations)
Rake.application.top_level
end
end
describe 'db:migrate:down' do
it 'should update annotations' do
expect(Annotate::Migration).to receive(:update_annotations)
Rake.application.top_level
end
end
describe 'db:migrate:reset' do
it 'should update annotations' do
expect(Annotate::Migration).to receive(:update_annotations)
Rake.application.top_level
end
end
describe 'db:rollback' do
it 'should update annotations' do
expect(Annotate::Migration).to receive(:update_annotations)
Rake.application.top_level
end
end
describe 'db:migrate:redo' do
it 'should update annotations after all migration tasks' do
allow(Annotate::Migration).to receive(:update_annotations)
# Confirm that update_annotations isn't called when the original redo task finishes
Rake::Task[subject].enhance do
expect(Annotate::Migration).not_to have_received(:update_annotations)
end
Rake.application.top_level
# Hooked 3 times by db:rollback, db:migrate, and db:migrate:redo tasks
expect(Annotate::Migration).to have_received(:update_annotations).exactly(3).times
end
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