diff --git a/.gitignore b/.gitignore index 5f7d1feca..c2e56f804 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,23 @@ +!/spec/integration/*/log/.gitkeep +!/spec/integration/*/tmp/.gitkeep +*.gem .DS_Store +.bundle +.gems +.rbenv-version +.ruby-* +/.idea/ +/.rbx +/.rvmrc /.yardoc/* -/doc/* +/Gemfile.lock /coverage/* -/spec/debug.log -/pkg/* /dist -/Gemfile.lock -*.gem -/.idea/ -/.rvmrc -.bundle -/.rbx +/doc/* +/pkg/* +/spec/debug.log /spec/integration/*/bin/ +/spec/integration/*/db/test.* /spec/integration/*/log/* -!/spec/integration/*/log/.gitkeep /spec/integration/*/tmp/* -!/spec/integration/*/tmp/.gitkeep -/spec/integration/*/db/test.* -.rbenv-version \ No newline at end of file +.byebug_history diff --git a/.overcommit.yml b/.overcommit.yml new file mode 100644 index 000000000..1f28db927 --- /dev/null +++ b/.overcommit.yml @@ -0,0 +1,38 @@ +# Use this file to configure the Overcommit hooks you wish to use. This will +# extend the default configuration defined in: +# https://github.com/brigade/overcommit/blob/master/config/default.yml +# +# At the topmost level of this YAML file is a key representing type of hook +# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can +# customize each hook, such as whether to only run it on certain files (via +# `include`), whether to only display output if it fails (via `quiet`), etc. +# +# For a complete list of hooks, see: +# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook +# +# For a complete list of options that you can use to customize hooks, see: +# https://github.com/brigade/overcommit#configuration +# +# Uncomment the following lines to make the configuration take effect. + +PreCommit: + RuboCop: + enabled: true + on_warn: fail # Treat all warnings as failures + +PrePush: + RSpec: + enabled: true + on_warn: fail # Treat all warnings as failures +# +# TrailingWhitespace: +# enabled: true +# exclude: +# - '**/db/structure.sql' # Ignore trailing whitespace in generated files +# +#PostCheckout: +# ALL: # Special hook name that customizes all hooks of this type +# quiet: true # Change all post-checkout hooks to only display output on failure +# +# IndexTags: +# enabled: true # Generate a tags file with `ctags` each time HEAD changes diff --git a/.rbenv-gemsets b/.rbenv-gemsets new file mode 100644 index 000000000..a9606b749 --- /dev/null +++ b/.rbenv-gemsets @@ -0,0 +1 @@ +.gems diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..c58fbed4c --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,7 @@ +inherit_from: ./.rubocop_todo.yml + +AllCops: + Exclude: + - 'vendor/**/*' + - 'spec/fixtures/**/*' + - 'tmp/**/*' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..03801c2e1 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,589 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2016-12-17 10:16:05 +0100 using RuboCop version 0.46.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 9 +# Configuration parameters: Include. +# Include: **/Gemfile, **/gems.rb +Bundler/DuplicatedGem: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/Gemfile' + - 'spec/integration/rails_3.2.2/Gemfile' + - 'spec/integration/rails_3.2.8/Gemfile' + - 'spec/integration/rails_3.2_autoloading_factory_girl/Gemfile' + - 'spec/integration/rails_3.2_custom_inflections/Gemfile' + - 'spec/integration/rails_3.2_with_asset_pipeline/Gemfile' + - 'spec/integration/rails_4.1.1/Gemfile' + - 'spec/integration/rails_4.2.0/Gemfile' + - 'spec/integration/standalone/Gemfile' + +# Offense count: 2 +# Configuration parameters: Include. +# Include: **/Gemfile, **/gems.rb +Bundler/OrderedGems: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/Gemfile' + - 'spec/integration/rails_3.2_with_asset_pipeline/Gemfile' + +# Offense count: 3 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Exclude: + - 'lib/annotate/annotate_models.rb' + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AlignWith, SupportedStyles. +# SupportedStyles: either, start_of_block, start_of_line +Lint/BlockAlignment: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Lint/DeprecatedClassMethods: + Exclude: + - 'lib/annotate/annotate_routes.rb' + - 'spec/integration/rails_3.2.2/config/boot.rb' + - 'spec/integration/rails_3.2.8/config/boot.rb' + - 'spec/integration/rails_3.2_autoloading_factory_girl/config/boot.rb' + - 'spec/integration/rails_3.2_custom_inflections/config/boot.rb' + - 'spec/integration/rails_3.2_with_asset_pipeline/config/boot.rb' + +# Offense count: 1 +Lint/DuplicateMethods: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 1 +Lint/HandleExceptions: + Exclude: + - 'bin/annotate' + +# Offense count: 8 +Lint/IneffectiveAccessModifier: + Exclude: + - 'lib/annotate/annotate_routes.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: runtime_error, standard_error +Lint/InheritException: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 2 +Lint/RescueException: + Exclude: + - 'Rakefile' + +# Offense count: 1 +Lint/ShadowingOuterLocalVariable: + Exclude: + - 'Rakefile' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Exclude: + - 'bin/annotate' + +# Offense count: 1 +# Configuration parameters: ContextCreatingMethods. +Lint/UselessAccessModifier: + Exclude: + - 'lib/annotate/annotate_routes.rb' + +# Offense count: 17 +Metrics/AbcSize: + Max: 159 + +# Offense count: 3 +# Configuration parameters: CountComments. +Metrics/BlockLength: + Max: 134 + +# Offense count: 2 +Metrics/BlockNesting: + Max: 4 + +# Offense count: 8 +Metrics/CyclomaticComplexity: + Max: 42 + +# Offense count: 350 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 543 + +# Offense count: 24 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 79 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ModuleLength: + Max: 116 + +# Offense count: 7 +Metrics/PerceivedComplexity: + Max: 48 + +# Offense count: 1 +Style/AccessorMethodName: + Exclude: + - 'lib/annotate.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Style/AlignArray: + Exclude: + - 'spec/annotate/annotate_models_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: percent_q, bare_percent +Style/BarePercentLiterals: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Exclude: + - 'spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake' + - 'spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake' + - 'spec/integration/standalone/config/init.rb' + +# Offense count: 1 +Style/CaseEquality: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 14 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: nested, compact +Style/ClassAndModuleChildren: + Exclude: + - 'lib/annotate/active_record_patch.rb' + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + - 'spec/integration/rails_2.3_with_bundler/test/test_helper.rb' + - 'spec/integration/rails_3.2.2/test/test_helper.rb' + - 'spec/integration/rails_3.2.8/test/test_helper.rb' + - 'spec/integration/rails_3.2_autoloading_factory_girl/test/test_helper.rb' + - 'spec/integration/rails_3.2_custom_inflections/test/test_helper.rb' + - 'spec/integration/rails_3.2_with_asset_pipeline/test/test_helper.rb' + - 'spec/integration/rails_4.1.1/app/models/sub1/sub2/sub3/event.rb' + - 'spec/integration/rails_4.1.1/app/models/sub1/user.rb' + - 'spec/integration/rails_4.1.1/test/test_helper.rb' + - 'spec/integration/rails_4.2.0/app/models/sub1/sub2/sub3/event.rb' + - 'spec/integration/rails_4.2.0/app/models/sub1/user.rb' + - 'spec/integration/rails_4.2.0/test/test_helper.rb' + +# Offense count: 2 +Style/ClassVars: + Exclude: + - 'lib/tasks/annotate_models_migrate.rake' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'bin/annotate' + +# Offense count: 7 +Style/Documentation: + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'lib/annotate.rb' + - 'lib/annotate/active_record_patch.rb' + - 'lib/annotate/annotate_models.rb' + - 'lib/annotate/version.rb' + - 'lib/generators/annotate/install_generator.rb' + - 'lib/tasks/annotate_models_migrate.rake' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/EmptyLines: + Exclude: + - 'spec/integration/rails_4.1.1/Gemfile' + - 'spec/integration/rails_4.2.0/Gemfile' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/EmptyLinesAroundAccessModifier: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: empty_lines, no_empty_lines +Style/EmptyLinesAroundBlockBody: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/db/schema.rb' + - 'spec/integration/rails_4.1.1/db/schema.rb' + - 'spec/integration/rails_4.2.0/db/schema.rb' + +# Offense count: 23 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Style/ExtraSpacing: + Exclude: + - 'Guardfile' + - 'lib/annotate/annotate_models.rb' + - 'lib/tasks/annotate_routes.rake' + - 'spec/integration/rails_2.3_with_bundler/script/console' + - 'spec/integration/rails_3.2.2/script/rails' + - 'spec/integration/rails_3.2.8/script/rails' + - 'spec/integration/rails_3.2_autoloading_factory_girl/script/rails' + - 'spec/integration/rails_3.2_custom_inflections/script/rails' + - 'spec/integration/rails_3.2_with_asset_pipeline/script/rails' + - 'spec/integration/rails_4.1.1/Gemfile' + - 'spec/integration/rails_4.1.1/config.ru' + - 'spec/integration/rails_4.2.0/Gemfile' + - 'spec/integration/rails_4.2.0/config.ru' + +# Offense count: 9 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: format, sprintf, percent +Style/FormatString: + Exclude: + - 'lib/annotate/annotate_models.rb' + - 'lib/annotate/annotate_routes.rb' + +# Offense count: 181 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: when_needed, always +Style/FrozenStringLiteralComment: + Enabled: false + +# Offense count: 7 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'lib/annotate/annotate_models.rb' + - 'lib/annotate/annotate_routes.rb' + - 'lib/tasks/annotate_models_migrate.rake' + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 57 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys +Style/HashSyntax: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Style/IndentHash: + EnforcedStyle: consistent + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: normal, rails +Style/IndentationConsistency: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: Width. +Style/IndentationWidth: + Exclude: + - 'spec/integration/rails_4.1.1/app/models/sub1/sub2/sub3/event.rb' + - 'spec/integration/rails_4.1.1/app/models/task.rb' + - 'spec/integration/rails_4.2.0/app/models/sub1/sub2/sub3/event.rb' + - 'spec/integration/rails_4.2.0/app/models/task.rb' + +# Offense count: 1 +Style/MethodMissing: + Exclude: + - 'lib/annotate/active_record_patch.rb' + +# Offense count: 3 +Style/MultilineBlockChain: + Exclude: + - 'Rakefile' + - 'lib/annotate/annotate_models.rb' + - 'spec/spec_helper.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: aligned, indented +Style/MultilineOperationIndentation: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Style/MutableConstant: + Exclude: + - 'lib/annotate/annotate_routes.rb' + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + - 'spec/integration/rails_2.3_with_bundler/config/environment.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/NestedParenthesizedCalls: + Exclude: + - 'bin/annotate' + +# Offense count: 9 +# Cop supports --auto-correct. +Style/NumericLiterals: + MinDigits: 15 + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/annotate.rb' + - 'lib/annotate/annotate_models.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Exclude: + - 'annotate.gemspec' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/PerlBackrefs: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantBegin: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantParentheses: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'lib/annotate/annotate_routes.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/RedundantSelf: + Exclude: + - 'lib/tasks/annotate_models_migrate.rake' + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Exclude: + - 'Rakefile' + - 'lib/annotate/annotate_models.rb' + - 'lib/annotate/annotate_routes.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/RescueModifier: + Exclude: + - 'lib/annotate/annotate_models.rb' + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowAsExpressionSeparator. +Style/Semicolon: + Exclude: + - 'bin/annotate' + - 'spec/integration/rails_2.3_with_bundler/config/initializers/unified_initializer.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceAroundEqualsInParameterDefault: + Exclude: + - 'lib/annotate/annotate_routes.rb' + - 'spec/integration/common_validation.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +Style/SpaceAroundKeyword: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/Gemfile' + - 'spec/integration/rails_3.2.2/Gemfile' + - 'spec/integration/rails_3.2.8/Gemfile' + - 'spec/integration/rails_3.2_autoloading_factory_girl/Gemfile' + - 'spec/integration/rails_3.2_custom_inflections/Gemfile' + - 'spec/integration/rails_3.2_with_asset_pipeline/Gemfile' + - 'spec/integration/rails_4.1.1/Gemfile' + - 'spec/integration/rails_4.2.0/Gemfile' + - 'spec/integration/standalone/Gemfile' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Style/SpaceAroundOperators: + Exclude: + - 'lib/annotate/annotate_models.rb' + - 'lib/tasks/annotate_models.rake' + - 'lib/tasks/annotate_routes.rake' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceBeforeBlockBraces: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/SpaceBeforeComment: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +Style/SpaceInsideBlockBraces: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SpaceInsideBrackets: + Exclude: + - 'spec/integration/rails_2.3_with_bundler/config/environment.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. +# SupportedStyles: space, no_space, compact +Style/SpaceInsideHashLiteralBraces: + Exclude: + - 'lib/tasks/annotate_models.rake' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/SpaceInsideParens: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceInsideStringInterpolation: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 223 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Exclude: + - 'lib/annotate/annotate_models.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/SymbolLiteral: + Exclude: + - 'spec/annotate/annotate_models_spec.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Style/Tab: + Exclude: + - 'spec/integration/rails_4.1.1/app/models/sub1/sub2/sub3/event.rb' + - 'spec/integration/rails_4.1.1/app/models/task.rb' + - 'spec/integration/rails_4.2.0/app/models/sub1/sub2/sub3/event.rb' + - 'spec/integration/rails_4.2.0/app/models/task.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: final_newline, final_blank_line +Style/TrailingBlankLines: + Exclude: + - 'spec/integration/rails_4.1.1/app/models/task_observer.rb' + - 'spec/integration/rails_4.1.1/config/initializers/cookies_serializer.rb' + - 'spec/integration/rails_4.2.0/app/models/task_observer.rb' + - 'spec/integration/rails_4.2.0/config/initializers/cookies_serializer.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. +# SupportedStyles: comma, consistent_comma, no_comma +Style/TrailingCommaInLiteral: + Exclude: + - 'spec/annotate/annotate_models_spec.rb' + - 'spec/integration/rails_4.1.1/lib/tasks/auto_annotate_models.rake' + - 'spec/integration/rails_4.2.0/lib/tasks/auto_annotate_models.rake' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/TrailingWhitespace: + Exclude: + - 'spec/annotate/annotate_routes_spec.rb' + - 'spec/integration/rails_2.3_with_bundler/db/schema.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/UnneededInterpolation: + Exclude: + - 'bin/annotate' + +# Offense count: 8 +# Cop supports --auto-correct. +Style/UnneededPercentQ: + Exclude: + - 'annotate.gemspec' + - 'spec/integration/rails_2.3_with_bundler/config/boot.rb' diff --git a/.travis.yml b/.travis.yml index b6a32cd82..c72f5d690 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,16 @@ rvm: - 1.9.3 - 2.0 - 2.1 - - 2.2 - - 2.3.0-preview1 + - 2.2.5 + - 2.3.0 + - 2.4.0 + - ruby-head +matrix: + allow_failures: + - rvm: ruby-head + - rvm: 1.9.3 before_install: - - rvm @global do gem install bundler + - gem update --system + - gem update bundler +script: + - bundle exec rubocop && bundle exec rspec diff --git a/AUTHORS.rdoc b/AUTHORS.rdoc index 364f29807..5c18e2551 100644 --- a/AUTHORS.rdoc +++ b/AUTHORS.rdoc @@ -29,5 +29,6 @@ With help from: - Paul Alexander - Dmitry Lihachev - qichunren +- Guillermo Guerrero - http://github.com/ryanfox1985 and many others that I may have forgotten to add. diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index 40e5df77d..bd6ce32ff 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,3 +1,9 @@ +== 2.7.2 +See https://github.com/ctran/annotate_models/releases/tag/v2.7.2 + +== 2.7.1 +See https://github.com/ctran/annotate_models/releases/tag/v2.7.1 + == 2.7.0 See https://github.com/ctran/annotate_models/releases/tag/v2.7.0 diff --git a/Gemfile b/Gemfile index 8455ab359..f30abba0d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,28 +1,38 @@ source 'https://rubygems.org' -gem 'rake', '>= 10.4.2', :require => false -gem 'activerecord', '>= 4.2.5', :require => false +gem 'activerecord', '>= 4.2.5', require: false +gem 'rake', require: false group :development do - gem 'mg', :require => false gem 'bump' - platforms :mri do - gem 'yard', :require => false + gem 'mg', require: false + platforms :mri, :mingw do + gem 'yard', require: false end end group :development, :test do - gem 'rspec', :require => false - gem 'guard-rspec', :require => false - gem 'terminal-notifier-guard', :require => false + gem 'byebug' + gem 'guard-rspec', require: false + gem 'rspec', require: false - platforms :mri do - gem 'pry', :require => false - gem 'pry-coolline', :require => false + gem 'rubocop', '~> 0.46.0', require: false unless RUBY_VERSION =~ /^1.8/ + gem 'simplecov', require: false + gem 'terminal-notifier-guard', require: false + + gem 'codeclimate-test-reporter' + gem 'coveralls' + + gem 'overcommit' + gem 'ruby_dep', '1.3.1' + + platforms :mri, :mingw do + gem 'pry', require: false + gem 'pry-coolline', require: false end end group :test do - gem 'wrong', :require => false - gem 'files', :require => false + gem 'files', require: false + gem 'wrong', require: false end diff --git a/Guardfile b/Guardfile index 5120ddd02..602c7b57c 100644 --- a/Guardfile +++ b/Guardfile @@ -1,5 +1,3 @@ - - # Note: The cmd option is now required due to the increasing number of ways # rspec may be run, below are examples of the most common uses. # * bundler: 'bundle exec rspec' @@ -11,16 +9,16 @@ guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { "spec" } + watch('spec/spec_helper.rb') { 'spec' } # Rails example watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } - watch(%r{^spec/support/(.+)\.rb$}) { "spec" } - watch('config/routes.rb') { "spec/routing" } - watch('app/controllers/application_controller.rb') { "spec/controllers" } - watch('spec/rails_helper.rb') { "spec" } + watch(%r{^spec/support/(.+)\.rb$}) { 'spec' } + watch('config/routes.rb') { 'spec/routing' } + watch('app/controllers/application_controller.rb') { 'spec/controllers' } + watch('spec/rails_helper.rb') { 'spec' } # Capybara features specs watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" } @@ -29,4 +27,3 @@ guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec/acceptance/(.+)\.feature$}) watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } end - diff --git a/README.rdoc b/README.rdoc old mode 100755 new mode 100644 index ac85a4b92..efd634586 --- a/README.rdoc +++ b/README.rdoc @@ -1,7 +1,11 @@ == Annotate (aka AnnotateModels) {Gem Version}[http://badge.fury.io/rb/annotate] -{}[https://travis-ci.org/ctran/annotate_models] +{}[https://rubygems.org/gems/annotate] +{}[https://travis-ci.org/ctran/annotate_models] +{}[https://coveralls.io/r/ctran/annotate_models?branch=develop] +{}[https://codeclimate.com/github/ctran/annotate_models] +{Inline docs}[http://inch-ci.org/github/ctran/annotate_models] {}[https://gemnasium.com/ctran/annotate_models] Add a comment summarizing the current schema to the top or bottom of each of @@ -44,7 +48,7 @@ It also annotates geometrical columns, geom type and srid, when using # path :geometry line_string, 4326 Also, if you pass the -r option, it'll annotate routes.rb with the output of -+rake routes+. +rake routes. == Install @@ -55,7 +59,7 @@ Into Gemfile from rubygems.org: Into Gemfile from Github: - gem 'annotate', github: 'ctran/annotate_models' + gem 'annotate', git: 'https://github.com/ctran/annotate_models.git' Into environment gems from rubygems.org: @@ -63,7 +67,7 @@ Into environment gems from rubygems.org: Into environment gems from Github checkout: - git clone git://github.com/ctran/annotate_models.git annotate_models + git clone https://github.com/ctran/annotate_models.git annotate_models cd annotate_models rake build gem install pkg/annotate-*.gem @@ -71,7 +75,7 @@ Into environment gems from Github checkout: == Usage -(If you used the Gemfile install, prefix the below commands with +bundle exec+.) +(If you used the Gemfile install, prefix the below commands with bundle exec.) === Usage in Rails @@ -100,7 +104,7 @@ To remove routes.rb annotations: 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. +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}[link:README.rdoc#configuration-in-rails] section for more info. === Usage Outside of Rails @@ -136,7 +140,7 @@ functionality: rake remove_annotation # Remove schema information from model and fixture files 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). If you want to disable this behavior permanently, edit the +.rake+ file and change: @@ -146,7 +150,7 @@ To: 'skip_on_db_migrate' => 'true', -If you want to run +rake db:migrate+ as a one-off without running annotate, +If you want to run rake db:migrate as a one-off without running annotate, you can do so with a simple environment variable, instead of editing the +.rake+ file: @@ -175,12 +179,13 @@ you can do so with a simple environment variable, instead of editing the --wo, --wrapper-open STR Annotation wrapper opening. --wc, --wrapper-close STR Annotation wrapper closing -r, --routes Annotate routes.rb with the output of 'rake routes' + -aa, --active-admin Annotate all activeadmin models -v, --version Show the current version of this gem -m, --show-migration Include the migration version number in the annotation -i, --show-indexes List the table's database indexes in the annotation -k, --show-foreign-keys List the table's foreign key constraints in the annotation -s, --simple-indexes Concat the column's related indexes in the annotation - --model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with comas + --model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with commas --ignore-model-subdirects Ignore subdirectories of the models directory --sort Sort columns alphabetically, rather than in creation order -R, --require path Additional file to require before loading models, may be used multiple times diff --git a/Rakefile b/Rakefile index 250e7111e..65aae5e59 100644 --- a/Rakefile +++ b/Rakefile @@ -1,11 +1,15 @@ +def exit_exception(e) + $stderr.puts e.message + exit e.status_code +end + # Note : this causes annoying psych warnings under Ruby 1.9.2-p180; to fix, upgrade to 1.9.3 begin require 'bundler' Bundler.setup(:default, :development) rescue Bundler::BundlerError => e - $stderr.puts e.message - $stderr.puts "Run `bundle install` to install missing gems" - exit e.status_code + $stderr.puts 'Run `bundle install` to install missing gems' + exit_exception(e) end using_dsl = false @@ -14,67 +18,65 @@ begin using_dsl = true rescue Exception => e # We might just be on an old version of Rake... + exit_exception(e) end require 'rake' -if(using_dsl) - include Rake::DSL -end - -require "./lib/annotate" +include Rake::DSL if using_dsl -require "mg" +require './lib/annotate' +require 'mg' begin - MG.new("annotate.gemspec") -rescue Exception => e + MG.new('annotate.gemspec') +rescue Exception STDERR.puts("WARNING: Couldn't read gemspec. As such, a number of tasks may be unavailable to you until you run 'rake gem:gemspec' to correct the issue.") # Gemspec is probably in a broken state, so let's give ourselves a chance to # build a new one... end -DEVELOPMENT_GROUPS=[:development, :test] -RUNTIME_GROUPS=Bundler.definition.groups - DEVELOPMENT_GROUPS +DEVELOPMENT_GROUPS = [:development, :test].freeze +RUNTIME_GROUPS = Bundler.definition.groups - DEVELOPMENT_GROUPS namespace :gem do task :gemspec do spec = Gem::Specification.new do |gem| # See http://docs.rubygems.org/read/chapter/20 # for more options. gem.version = Annotate.version - gem.name = "annotate" - gem.homepage = "http://github.com/ctran/annotate_models" - gem.rubyforge_project = "annotate" - gem.license = "Ruby" - gem.summary = %q{Annotates Rails Models, routes, fixtures, and others based on the database schema.} - gem.description = %q{Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.} - gem.email = ["alex@stinky.com", "cuong@gmail.com", "x@nofxx.com", "turadg@aleahmad.net", "jon@cloudability.com"] - gem.authors = ["Alex Chaffee", "Cuong Tran", "Marcos Piccinini", "Turadg Aleahmad", "Jon Frisby"] - gem.require_paths = ["lib"] + gem.name = 'annotate' + gem.homepage = 'http://github.com/ctran/annotate_models' + gem.rubyforge_project = 'annotate' + gem.license = 'Ruby' + gem.summary = 'Annotates Rails Models, routes, fixtures, and others based on the database schema.' + gem.description = 'Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.' + gem.email = ['alex@stinky.com', 'cuong@gmail.com', 'x@nofxx.com', 'turadg@aleahmad.net', 'jon@cloudability.com'] + gem.authors = ['Alex Chaffee', 'Cuong Tran', 'Marcos Piccinini', 'Turadg Aleahmad', 'Jon Frisby'] + gem.require_paths = ['lib'] # gem.rdoc_options = ["--charset=UTF-8"] # gem.required_ruby_version = "> 1.9.2" Bundler.load.dependencies_for(*RUNTIME_GROUPS).each do |dep| - runtime_resolved = Bundler.definition.specs_for(RUNTIME_GROUPS).select { |spec| spec.name == dep.name }.first - if(!runtime_resolved.nil?) + runtime_resolved = Bundler.definition.specs_for(RUNTIME_GROUPS).find { |spec| spec.name == dep.name } + unless runtime_resolved.nil? gem.add_dependency(dep.name, dep.requirement) end end - gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } gem.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.rdoc', 'TODO.rdoc'] gem.files = `git ls-files -- .`.split("\n").reject do |fn| fn =~ /^Gemfile.*/ || - fn =~ /^Rakefile/ || - fn =~ /^\.rvmrc/ || - fn =~ /^\.gitignore/ || - fn =~ /^\.rspec/ || - fn =~ /^\.document/ || - fn =~ /^\.yardopts/ || - fn =~ /^pkg/ || - fn =~ /^spec/ || - fn =~ /^doc/ || - fn =~ /^vendor\/cache/ + fn =~ /^Rakefile/ || + fn =~ /^\.rvmrc/ || + fn =~ /^\.gitignore/ || + fn =~ /^\.rspec/ || + fn =~ /^\.document/ || + fn =~ /^\.yardopts/ || + fn =~ /^pkg/ || + fn =~ /^spec/ || + fn =~ /^doc/ || + fn =~ /^vendor\/cache/ end.sort end - File.open("annotate.gemspec", "wb") do |fh| + File.open('annotate.gemspec', 'wb') do |fh| fh.write("# This file is auto-generated!\n") fh.write("# DO NOT EDIT THIS FILE DIRECTLY!\n") fh.write("# Instead, edit the Rakefile and run 'rake gems:gemspec'.") @@ -85,12 +87,12 @@ end namespace :jeweler do task :clobber do - FileUtils.rm_f("pkg") + FileUtils.rm_f('pkg') end end -task :clobber => :'jeweler:clobber' +task clobber: :'jeweler:clobber' -require "rspec/core/rake_task" # RSpec 2.0 +require 'rspec/core/rake_task' # RSpec 2.0 RSpec::Core::RakeTask.new(:spec) do |t| t.pattern = ['spec/*_spec.rb', 'spec/**/*_spec.rb'] t.rspec_opts = ['--backtrace', '--format d'] @@ -105,78 +107,76 @@ end namespace :gemsets do desc "Completely empty any gemsets used by scenarios, so they'll be perfectly clean on the next run." - task :empty => [:integration_environment] do - Annotate::Integration::SCENARIOS.each do |test_rig, base_dir, test_name| + task empty: [:integration_environment] do + Annotate::Integration::SCENARIOS.each do |test_rig, _base_dir, _test_name| Annotate::Integration.empty_gemset(test_rig) end end end -task :clobber => :'gemsets:empty' +task clobber: :'gemsets:empty' namespace :integration do desc "Remove any cruft generated by manual debugging runs which is .gitignore'd." - task :clean => :integration_environment do + task clean: :integration_environment do Annotate::Integration.nuke_all_cruft end - desc "Reset any changed files, and remove any untracked files in spec/integration/*/, plus run integration:clean." - task :clobber => [:integration_environment, :'integration:clean'] do + desc 'Reset any changed files, and remove any untracked files in spec/integration/*/, plus run integration:clean.' + task clobber: [:integration_environment, :'integration:clean'] do Annotate::Integration.reset_dirty_files Annotate::Integration.clear_untracked_files end - task :symlink => [:integration_environment] do + task symlink: [:integration_environment] do require 'digest/md5' integration_dir = File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'integration')) - fixture_dir = File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'fixtures')) - target_dir = File.expand_path(ENV['TARGET']) if(ENV['TARGET']) - raise "Must specify TARGET=x, where x is an integration test scenario!" unless(target_dir && Dir.exist?(target_dir)) - raise "TARGET directory must be within spec/integration/!" unless(target_dir.start_with?(integration_dir)) + # fixture_dir = File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'fixtures')) + target_dir = File.expand_path(ENV['TARGET']) if ENV['TARGET'] + raise 'Must specify TARGET=x, where x is an integration test scenario!' unless target_dir && Dir.exist?(target_dir) + raise 'TARGET directory must be within spec/integration/!' unless target_dir.start_with?(integration_dir) candidates = {} FileList[ "#{target_dir}/.rvmrc", "#{target_dir}/**/*" - ].select { |fname| !(File.symlink?(fname) || File.directory?(fname)) }. - map { |fname| fname.sub(integration_dir, '') }. - reject do |fname| + ].select { |fname| !(File.symlink?(fname) || File.directory?(fname)) } + .map { |fname| fname.sub(integration_dir, '') } + .reject do |fname| fname =~ /\/\.gitkeep$/ || - fname =~ /\/app\/models\// || - fname =~ /\/routes\.rb$/ || - fname =~ /\/fixtures\// || - fname =~ /\/factories\// || - fname =~ /\.sqlite3$/ || - (fname =~ /\/test\// && fname !~ /_helper\.rb$/) || - (fname =~ /\/spec\// && fname !~ /_helper\.rb$/) - end. - map { |fname| "#{integration_dir}#{fname}"}. - each do |fname| + fname =~ /\/app\/models\// || + fname =~ /\/routes\.rb$/ || + fname =~ /\/fixtures\// || + fname =~ /\/factories\// || + fname =~ /\.sqlite3$/ || + (fname =~ /\/test\// && fname !~ /_helper\.rb$/) || + (fname =~ /\/spec\// && fname !~ /_helper\.rb$/) + end + .map { |fname| "#{integration_dir}#{fname}" } + .each do |fname| digest = Digest::MD5.hexdigest(File.read(fname)) candidates[digest] ||= [] candidates[digest] << fname end fixtures = {} - FileList["spec/fixtures/**/*"].each do |fname| + FileList['spec/fixtures/**/*'].each do |fname| fixtures[Digest::MD5.hexdigest(File.read(fname))] = File.expand_path(fname) end candidates.keys.each do |digest| - if(fixtures.has_key?(digest)) - candidates[digest].each do |fname| - # Double-check contents in case of hash collision... - if(FileUtils.identical?(fname, fixtures[digest])) - destination_dir = Pathname.new(File.dirname(fname)) - relative_target = Pathname.new(fixtures[digest]).relative_path_from(destination_dir) - Dir.chdir(destination_dir) do - sh("ln", "-sfn", relative_target.to_s, File.basename(fname)) - end - end + next unless fixtures.key?(digest) + candidates[digest].each do |fname| + # Double-check contents in case of hash collision... + next unless FileUtils.identical?(fname, fixtures[digest]) + destination_dir = Pathname.new(File.dirname(fname)) + relative_target = Pathname.new(fixtures[digest]).relative_path_from(destination_dir) + Dir.chdir(destination_dir) do + sh('ln', '-sfn', relative_target.to_s, File.basename(fname)) end end end end end -task :clobber => :'integration:clobber' +task clobber: :'integration:clobber' require 'yard' YARD::Rake::YardocTask.new do |t| @@ -186,19 +186,19 @@ end namespace :yard do task :clobber do - FileUtils.rm_f(".yardoc") - FileUtils.rm_f("doc") + FileUtils.rm_f('.yardoc') + FileUtils.rm_f('doc') end end -task :clobber => :'yard:clobber' +task clobber: :'yard:clobber' namespace :rubinius do task :clobber do - FileList["**/*.rbc"].each { |fname| FileUtils.rm_f(fname) } - FileList[".rbx/**/*"].each { |fname| FileUtils.rm_f(fname) } + FileList['**/*.rbc'].each { |fname| FileUtils.rm_f(fname) } + FileList['.rbx/**/*'].each { |fname| FileUtils.rm_f(fname) } end end -task :clobber => :'rubinius:clobber' +task clobber: :'rubinius:clobber' # want other tests/tasks run by default? Add them to the list -task :default => [:spec] +task default: [:spec] diff --git a/annotate.gemspec b/annotate.gemspec index 56d5972f8..e46a4ff00 100644 --- a/annotate.gemspec +++ b/annotate.gemspec @@ -4,36 +4,45 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'annotate/version' Gem::Specification.new do |s| - s.name = "annotate" + s.name = 'annotate' s.version = Annotate.version s.required_ruby_version = '>= 1.9.3' - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Alex Chaffee", "Cuong Tran", "Marcos Piccinini", "Turadg Aleahmad", "Jon Frisby"] - s.description = "Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema." - s.email = ["alex@stinky.com", "cuong.tran@gmail.com", "x@nofxx.com", "turadg@aleahmad.net", "jon@cloudability.com"] - s.executables = ["annotate"] - s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc", "TODO.rdoc"] - s.files = ["AUTHORS.rdoc", "CHANGELOG.rdoc", "LICENSE.txt", "README.rdoc", "TODO.rdoc", "annotate.gemspec", "bin/annotate", "lib/annotate.rb", "lib/annotate/active_record_patch.rb", "lib/annotate/annotate_models.rb", "lib/annotate/annotate_routes.rb", "lib/annotate/tasks.rb", "lib/annotate/version.rb", "lib/generators/annotate/USAGE", "lib/generators/annotate/install_generator.rb", "lib/generators/annotate/templates/auto_annotate_models.rake", "lib/tasks/annotate_models.rake", "lib/tasks/annotate_routes.rake", "lib/tasks/migrate.rake"] - s.homepage = "http://github.com/ctran/annotate_models" - s.licenses = ["Ruby"] - s.require_paths = ["lib"] - s.rubyforge_project = "annotate" - s.rubygems_version = "2.1.11" - s.summary = "Annotates Rails Models, routes, fixtures, and others based on the database schema." + s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version= + s.authors = ['Alex Chaffee', 'Cuong Tran', 'Marcos Piccinini', 'Turadg Aleahmad', 'Jon Frisby'] + s.description = 'Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.' + s.email = ['alex@stinky.com', 'cuong.tran@gmail.com', 'x@nofxx.com', 'turadg@aleahmad.net', 'jon@cloudability.com'] + s.executables = ['annotate'] + s.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.rdoc', 'TODO.rdoc'] + s.files = [ + 'AUTHORS.rdoc', + 'CHANGELOG.rdoc', + 'LICENSE.txt', + 'README.rdoc', + 'TODO.rdoc', + 'annotate.gemspec', + 'bin/annotate', + 'lib/annotate.rb', + 'lib/annotate/active_record_patch.rb', + 'lib/annotate/annotate_models.rb', + 'lib/annotate/annotate_routes.rb', + 'lib/annotate/tasks.rb', + 'lib/annotate/version.rb', + 'lib/generators/annotate/USAGE', + 'lib/generators/annotate/install_generator.rb', + 'lib/generators/annotate/templates/auto_annotate_models.rake', + 'lib/tasks/annotate_models.rake', + 'lib/tasks/annotate_routes.rake', + 'lib/tasks/annotate_models_migrate.rake' + ] + s.homepage = 'http://github.com/ctran/annotate_models' + s.licenses = ['Ruby'] + s.require_paths = ['lib'] + s.rubyforge_project = 'annotate' + s.rubygems_version = '2.1.11' + s.summary = 'Annotates Rails Models, routes, fixtures, and others based on the database schema.' - if s.respond_to? :specification_version then - s.specification_version = 4 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, ["~> 10.4"]) - s.add_runtime_dependency(%q, [">= 3.2", "< 6.0"]) - else - s.add_dependency(%q, ["~> 10.4"]) - s.add_dependency(%q, [">= 3.2", "< 6.0"]) - end - else - s.add_dependency(%q, [">= 0.8.7"]) - s.add_dependency(%q, [">= 3.2", "< 6.0"]) - end + s.specification_version = 4 if s.respond_to? :specification_version + s.add_runtime_dependency(%q, ['>= 10.4', '< 13.0']) + s.add_runtime_dependency(%q, ['>= 3.2', '< 6.0']) end diff --git a/bin/annotate b/bin/annotate index 24a8f9cca..a5acf0818 100755 --- a/bin/annotate +++ b/bin/annotate @@ -1,18 +1,18 @@ #!/usr/bin/env ruby -unless File.exists?('./Rakefile') || File.exists?('./Gemfile') - abort "Please run annotate from the root of the project." +unless File.exist?('./Rakefile') || File.exist?('./Gemfile') + abort 'Please run annotate from the root of the project.' end require 'rubygems' begin require 'bundler' Bundler.setup -rescue Exception => e +rescue StandardError end here = File.expand_path(File.dirname __FILE__) -$:<< "#{here}/../lib" +$LOAD_PATH << "#{here}/../lib" require 'optparse' require 'annotate' @@ -20,58 +20,55 @@ Annotate.bootstrap_rake has_set_position = {} target_action = :do_annotations +positions = %w(before top after bottom) OptionParser.new do |opts| - opts.banner = "Usage: annotate [options] [model_file]*" - - opts.on('-d', '--delete', - "Remove annotations from all model files or the routes.rb file") do + opts.banner = 'Usage: annotate [options] [model_file]*' + opts.on('-d', '--delete', 'Remove annotations from all model files or the routes.rb file') do target_action = :remove_annotations end - opts.on('-p', '--position [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)") do |p| + opts.on('-p', '--position [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)') do |p| ENV['position'] = p - [ - 'position_in_class','position_in_factory','position_in_fixture','position_in_test', 'position_in_routes', 'position_in_serializer' - ].each do |key| - ENV[key] = p unless(has_set_position[key]) + %w(position_in_class position_in_factory position_in_fixture position_in_test position_in_routes position_in_serializer).each do |key| + ENV[key] = p unless has_set_position[key] end end - opts.on('--pc', '--position-in-class [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of the model file") do |p| + opts.on('--pc', '--position-in-class [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of the model file') do |p| ENV['position_in_class'] = p has_set_position['position_in_class'] = true end - opts.on('--pf', '--position-in-factory [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of any factory files") do |p| + opts.on('--pf', '--position-in-factory [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of any factory files') do |p| ENV['position_in_factory'] = p has_set_position['position_in_factory'] = true end - opts.on('--px', '--position-in-fixture [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of any fixture files") do |p| + opts.on('--px', '--position-in-fixture [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of any fixture files') do |p| ENV['position_in_fixture'] = p has_set_position['position_in_fixture'] = true end - opts.on('--pt', '--position-in-test [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of any test files") do |p| + opts.on('--pt', '--position-in-test [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of any test files') do |p| ENV['position_in_test'] = p has_set_position['position_in_test'] = true end - opts.on('--pr', '--position-in-routes [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of the routes.rb file") do |p| + opts.on('--pr', '--position-in-routes [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of the routes.rb file') do |p| ENV['position_in_routes'] = p has_set_position['position_in_routes'] = true end - opts.on('--ps', '--position-in-serializer [before|top|after|bottom]', ['before', 'top', 'after', 'bottom'], - "Place the annotations at the top (before) or the bottom (after) of the serializer files") do |p| + opts.on('--ps', '--position-in-serializer [before|top|after|bottom]', positions, + 'Place the annotations at the top (before) or the bottom (after) of the serializer files') do |p| ENV['position_in_serializer'] = p has_set_position['position_in_serializer'] = true end @@ -89,59 +86,60 @@ OptionParser.new do |opts| ENV['wrapper_close'] = p end - opts.on('-r', '--routes', - "Annotate routes.rb with the output of 'rake routes'") do + opts.on('-r', '--routes', "Annotate routes.rb with the output of 'rake routes'") do ENV['routes'] = 'true' end - opts.on('-v', '--version', - "Show the current version of this gem") do + opts.on('-aa', '--active-admin', 'Annotate active_admin models') do + ENV['active_admin'] = 'true' + end + + opts.on('-v', '--version', 'Show the current version of this gem') do puts "annotate v#{Annotate.version}"; exit end - opts.on('-m', '--show-migration', - "Include the migration version number in the annotation") do - ENV['include_version'] = "yes" + opts.on('-m', '--show-migration', 'Include the migration version number in the annotation') do + ENV['include_version'] = 'yes' end opts.on('-k', '--show-foreign-keys', "List the table's foreign key constraints in the annotation") do - ENV['show_foreign_keys'] = "yes" + ENV['show_foreign_keys'] = 'yes' end opts.on('-i', '--show-indexes', "List the table's database indexes in the annotation") do - ENV['show_indexes'] = "yes" + ENV['show_indexes'] = 'yes' end opts.on('-s', '--simple-indexes', "Concat the column's related indexes in the annotation") do - ENV['simple_indexes'] = "yes" + ENV['simple_indexes'] = 'yes' end opts.on('--model-dir dir', - "Annotate model files stored in dir rather than app/models, separate multiple dirs with comas") do |dir| + "Annotate model files stored in dir rather than app/models, separate multiple dirs with commas") do |dir| ENV['model_dir'] = dir end opts.on('--root-dir dir', - "Annotate files stored within root dir projects, separate multiple dirs with comas") do |dir| + "Annotate files stored within root dir projects, separate multiple dirs with commas") do |dir| ENV['root_dir'] = dir end opts.on('--ignore-model-subdirects', "Ignore subdirectories of the models directory") do |dir| - ENV['ignore_model_sub_dir'] = "yes" + ENV['ignore_model_sub_dir'] = 'yes' end opts.on('--sort', "Sort columns alphabetically, rather than in creation order") do |dir| - ENV['sort'] = "yes" + ENV['sort'] = 'yes' end opts.on('--classified-sort', "Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns") do |dir| - ENV['classified_sort'] = "yes" + ENV['classified_sort'] = 'yes' end opts.on('-R', '--require path', @@ -155,10 +153,10 @@ OptionParser.new do |opts| opts.on('-e', '--exclude [tests,fixtures,factories,serializers]', Array, "Do not annotate fixtures, test files, factories, and/or serializers") do |exclusions| exclusions ||= %w(tests fixtures factories) - exclusions.each { |exclusion| ENV["exclude_#{exclusion}"] = "yes" } + exclusions.each { |exclusion| ENV["exclude_#{exclusion}"] = 'yes' } end - opts.on('-f', '--format [bare|rdoc|markdown]', ['bare', 'rdoc', 'markdown'], 'Render Schema Infomation as plain/RDoc/Markdown') do |fmt| + opts.on('-f', '--format [bare|rdoc|markdown]', %w(bare rdoc markdown), 'Render Schema Infomation as plain/RDoc/Markdown') do |fmt| ENV["format_#{fmt}"] = 'yes' end @@ -174,21 +172,30 @@ OptionParser.new do |opts| ENV['trace'] = 'yes' end - opts.on('-I', '--ignore-columns REGEX', "don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`" ) do |regex| + opts.on('-I', '--ignore-columns REGEX', "don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`") do |regex| ENV['ignore_columns'] = regex 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| + opts.on('--ignore-routes REGEX', "don't annotate routes that match a given REGEX (i.e., `annotate -I '(mobile|resque|pghero)'`") do |regex| + ENV['ignore_routes'] = regex + end + + opts.on('--hide-limit-column-types VALUES', "don't show limit for given column types, separated by commas (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" + opts.on('--hide-default-column-types VALUES', "don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)") do |values| + ENV['hide_default_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! -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) AnnotateModels.send(target_action, options) if Annotate.include_models? diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..342ed0dec --- /dev/null +++ b/circle.yml @@ -0,0 +1,7 @@ +machine: + ruby: + version: 2.2.6 + +test: + override: + - bundle exec rubocop && bundle exec rspec diff --git a/lib/annotate.rb b/lib/annotate.rb old mode 100755 new mode 100644 index fa9b6607e..a17b14215 --- a/lib/annotate.rb +++ b/lib/annotate.rb @@ -1,4 +1,6 @@ -$:.unshift(File.dirname(__FILE__)) +# rubocop:disable Metrics/ModuleLength + +$LOAD_PATH.unshift(File.dirname(__FILE__)) require 'annotate/version' require 'annotate/annotate_models' require 'annotate/annotate_routes' @@ -7,60 +9,70 @@ # ActiveSupport 3.x... require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/object/blank' -rescue Exception +rescue StandardError # ActiveSupport 2.x... require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/blank' end module Annotate + TRUE_RE = /^(true|t|yes|y|1)$/i + ## # The set of available options to customize the behavior of Annotate. # - POSITION_OPTIONS=[ + POSITION_OPTIONS = [ :position_in_routes, :position_in_class, :position_in_test, :position_in_fixture, :position_in_factory, :position, :position_in_serializer - ] - FLAG_OPTIONS=[ + ].freeze + 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, :show_foreign_keys, - :exclude_scaffolds, :exclude_controllers, :exclude_helpers, :ignore_unknown_models, - ] - OTHER_OPTIONS=[ - :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close, :wrapper, :routes, - :hide_limit_column_types, - ] - PATH_OPTIONS=[ + :exclude_scaffolds, :exclude_controllers, :exclude_helpers, + :exclude_sti_subclasses, :ignore_unknown_models + ].freeze + OTHER_OPTIONS = [ + :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close, + :wrapper, :routes, :hide_limit_column_types, :hide_default_column_types, + :ignore_routes, :active_admin + ].freeze + PATH_OPTIONS = [ :require, :model_dir, :root_dir - ] + ].freeze + + def self.all_options + [POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS] + end ## # Set default values that can be overridden via environment variables. # def self.set_defaults(options = {}) - return if(@has_set_defaults) + return if @has_set_defaults @has_set_defaults = true options = HashWithIndifferentAccess.new(options) - [POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS].flatten.each do |key| - if options.has_key?(key) + all_options.flatten.each do |key| + if options.key?(key) default_value = if options[key].is_a?(Array) - options[key].join(",") - else - options[key] - end + options[key].join(',') + else + options[key] + end end - default_value = ENV[key.to_s] if !ENV[key.to_s].blank? + default_value = ENV[key.to_s] unless ENV[key.to_s].blank? ENV[key.to_s] = default_value.nil? ? nil : default_value.to_s end end - TRUE_RE = /^(true|t|yes|y|1)$/i + ## + # TODO: what is the difference between this and set_defaults? + # def self.setup_options(options = {}) POSITION_OPTIONS.each do |key| options[key] = fallback(ENV[key.to_s], ENV['position'], 'before') @@ -69,30 +81,27 @@ def self.setup_options(options = {}) options[key] = true?(ENV[key.to_s]) end OTHER_OPTIONS.each do |key| - options[key] = (!ENV[key.to_s].blank?) ? ENV[key.to_s] : nil + options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s] : nil end PATH_OPTIONS.each do |key| - options[key] = (!ENV[key.to_s].blank?) ? ENV[key.to_s].split(',') : [] + options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s].split(',') : [] end - if(options[:model_dir].empty?) - options[:model_dir] = ['app/models'] - end - - if(options[:root_dir].empty?) - options[:root_dir] = [''] - end + options[:model_dir] = ['app/models'] if options[:model_dir].empty? options[:wrapper_open] ||= options[:wrapper] options[:wrapper_close] ||= options[:wrapper] - return options + # These were added in 2.7.0 but so this is to revert to old behavior by default + options[:exclude_scaffolds] = Annotate.true?(ENV.fetch('exclude_scaffolds', 'true')) + options[:exclude_controllers] = Annotate.true?(ENV.fetch('exclude_controllers', 'true')) + options[:exclude_helpers] = Annotate.true?(ENV.fetch('exclude_helpers', 'true')) + + options end def self.reset_options - [POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS].flatten.each do |key| - ENV[key.to_s] = nil - end + all_options.flatten.each { |key| ENV[key.to_s] = nil } end def self.skip_on_migration? @@ -107,26 +116,34 @@ def self.include_models? true end - def self.loaded_tasks=(val); @loaded_tasks = val; end - def self.loaded_tasks; return @loaded_tasks; end + def self.loaded_tasks=(val) + @loaded_tasks = val + end + + def self.loaded_tasks + @loaded_tasks + end def self.load_tasks - return if(self.loaded_tasks) + return if loaded_tasks self.loaded_tasks = true - Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake } + Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each do |rake| + load rake + end end def self.load_requires(options) - options[:require].each { |path| require path } if options[:require].count > 0 + options[:require].count > 0 && + options[:require].each { |path| require path } end def self.eager_load(options) - self.load_requires(options) - require "annotate/active_record_patch" + load_requires(options) + require 'annotate/active_record_patch' - if(defined?(Rails)) - if(Rails.version.split('.').first.to_i < 3) + if defined?(Rails::Application) + if Rails.version.split('.').first.to_i < 3 Rails.configuration.eager_load_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| @@ -149,33 +166,38 @@ def self.eager_load(options) def self.bootstrap_rake begin require 'rake/dsl_definition' - rescue Exception + rescue StandardError => e # We might just be on an old version of Rake... + puts e.message + exit e.status_code end require 'rake' - if File.exists?('./Rakefile') - load './Rakefile' + load './Rakefile' if File.exist?('./Rakefile') + begin + Rake::Task[:environment].invoke + rescue + nil end - Rake::Task[:environment].invoke rescue nil - if(!defined?(Rails)) + unless defined?(Rails) # Not in a Rails project, so time to load up the parts of # ActiveSupport we need. require 'active_support' require 'active_support/core_ext/class/subclasses' require 'active_support/core_ext/string/inflections' end - self.load_tasks + + load_tasks Rake::Task[:set_annotation_options].invoke end def self.fallback(*args) - return args.detect { |arg| !arg.blank? } + args.detect { |arg| !arg.blank? } end def self.true?(val) - return false if(val.blank?) - return false unless(val =~ TRUE_RE) - return true + return false if val.blank? + return false unless val =~ TRUE_RE + true end end diff --git a/lib/annotate/active_record_patch.rb b/lib/annotate/active_record_patch.rb index 0b2d9db5c..8983b0465 100644 --- a/lib/annotate/active_record_patch.rb +++ b/lib/annotate/active_record_patch.rb @@ -2,7 +2,7 @@ module ::ActiveRecord class Base - def self.method_missing(name, *args) + def self.method_missing(_name, *_args) # ignore this, so unknown/unloaded macros won't cause parsing to fail end end diff --git a/lib/annotate/annotate_models.rb b/lib/annotate/annotate_models.rb index 72c0a1c5e..1c9d2f950 100644 --- a/lib/annotate/annotate_models.rb +++ b/lib/annotate/annotate_models.rb @@ -1,146 +1,183 @@ +# rubocop:disable Metrics/ModuleLength + require 'bigdecimal' module AnnotateModels + TRUE_RE = /^(true|t|yes|y|1)$/i + # Annotate Models plugin use this header - COMPAT_PREFIX = "== Schema Info" - COMPAT_PREFIX_MD = "## Schema Info" - PREFIX = "== Schema Information" - PREFIX_MD = "## Schema Information" - END_MARK = "== Schema Information End" - PATTERN = /^\r?\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\r?\n(#.*\r?\n)*(\r?\n)*/ + COMPAT_PREFIX = '== Schema Info'.freeze + COMPAT_PREFIX_MD = '## Schema Info'.freeze + PREFIX = '== Schema Information'.freeze + PREFIX_MD = '## Schema Information'.freeze + END_MARK = '== Schema Information End'.freeze - MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper) + MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper).freeze # File.join for windows reverse bar compat? # I dont use windows, can`t test - UNIT_TEST_DIR = File.join("test", "unit") - MODEL_TEST_DIR = File.join("test", "models") # since rails 4.0 - SPEC_MODEL_DIR = File.join("spec", "models") - FIXTURE_TEST_DIR = File.join("test", "fixtures") - FIXTURE_SPEC_DIR = File.join("spec", "fixtures") + UNIT_TEST_DIR = File.join('test', "unit") + MODEL_TEST_DIR = File.join('test', "models") # since rails 4.0 + SPEC_MODEL_DIR = File.join('spec', "models") + FIXTURE_TEST_DIR = File.join('test', "fixtures") + FIXTURE_SPEC_DIR = File.join('spec', "fixtures") # Other test files - CONTROLLER_TEST_DIR = File.join("test", "controllers") - CONTROLLER_SPEC_DIR = File.join("spec", "controllers") - REQUEST_SPEC_DIR = File.join("spec", "requests") - ROUTING_SPEC_DIR = File.join("spec", "routing") + CONTROLLER_TEST_DIR = File.join('test', "controllers") + CONTROLLER_SPEC_DIR = File.join('spec', "controllers") + REQUEST_SPEC_DIR = File.join('spec', "requests") + ROUTING_SPEC_DIR = File.join('spec', "routing") # Object Daddy http://github.com/flogic/object_daddy/tree/master - EXEMPLARS_TEST_DIR = File.join("test", "exemplars") - EXEMPLARS_SPEC_DIR = File.join("spec", "exemplars") + EXEMPLARS_TEST_DIR = File.join('test', "exemplars") + EXEMPLARS_SPEC_DIR = File.join('spec', "exemplars") # Machinist http://github.com/notahat/machinist - BLUEPRINTS_TEST_DIR = File.join("test", "blueprints") - BLUEPRINTS_SPEC_DIR = File.join("spec", "blueprints") + BLUEPRINTS_TEST_DIR = File.join('test', "blueprints") + BLUEPRINTS_SPEC_DIR = File.join('spec', "blueprints") # Factory Girl http://github.com/thoughtbot/factory_girl - FACTORY_GIRL_TEST_DIR = File.join("test", "factories") - FACTORY_GIRL_SPEC_DIR = File.join("spec", "factories") + FACTORY_GIRL_TEST_DIR = File.join('test', "factories") + FACTORY_GIRL_SPEC_DIR = File.join('spec', "factories") # Fabrication https://github.com/paulelliott/fabrication.git - FABRICATORS_TEST_DIR = File.join("test", "fabricators") - FABRICATORS_SPEC_DIR = File.join("spec", "fabricators") + FABRICATORS_TEST_DIR = File.join('test', "fabricators") + FABRICATORS_SPEC_DIR = File.join('spec', "fabricators") # Serializers https://github.com/rails-api/active_model_serializers - SERIALIZERS_DIR = File.join("app", "serializers") - SERIALIZERS_TEST_DIR = File.join("test", "serializers") - SERIALIZERS_SPEC_DIR = File.join("spec", "serializers") + SERIALIZERS_DIR = File.join('app', "serializers") + SERIALIZERS_TEST_DIR = File.join('test', "serializers") + SERIALIZERS_SPEC_DIR = File.join('spec', "serializers") # Controller files - CONTROLLER_DIR = File.join("app", "controllers") + CONTROLLER_DIR = File.join('app', "controllers") + + # Active admin registry files + ACTIVEADMIN_DIR = File.join('app', "admin") # Helper files - HELPER_DIR = File.join("app", "helpers") + HELPER_DIR = File.join('app', "helpers") # Don't show limit (#) on these column types # Example: show "integer" instead of "integer(4)" - NO_LIMIT_COL_TYPES = ["integer", "boolean"] + NO_LIMIT_COL_TYPES = %w(integer boolean).freeze + + # Don't show default value for these column types + NO_DEFAULT_COL_TYPES = %w(json jsonb hstore).freeze 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 || ""] + if @root_dir.blank? + [''] + elsif @root_dir.is_a?(String) + @root_dir.split(',') + else + @root_dir + end 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 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=MATCHED_TYPES) + 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(/^[\/]*/, '') } + current_patterns.map { |p| p.sub(/^[\/]*/, '') } end # Simple quoting for the default column value def quote(value) case value - when NilClass then "NULL" - when TrueClass then "TRUE" - when FalseClass then "FALSE" - when Float, Fixnum, Bignum then value.to_s + when NilClass then 'NULL' + when TrueClass then 'TRUE' + when FalseClass then 'FALSE' + when Float, Integer then value.to_s # BigDecimals need to be output in a non-normalized form and quoted. when BigDecimal then value.to_s('F') - when Array then value.map {|v| quote(v)} + when Array then value.map { |v| quote(v) } else value.inspect end @@ -150,31 +187,38 @@ def schema_default(klass, column) quote(klass.column_defaults[column.name]) end + def retrieve_indexes_from_table(klass) + table_name = klass.table_name + return [] unless table_name + + indexes = klass.connection.indexes(table_name) + return indexes if indexes.any? || !klass.table_name_prefix + + # Try to search the table without prefix + table_name.to_s.slice!(klass.table_name_prefix) + klass.connection.indexes(table_name) + end + # Use the column information in an ActiveRecord class # to create a comment block containing a line for # each column. The line contains the column name, # the type (and length), and any optional attributes def get_schema_info(klass, header, options = {}) info = "# #{header}\n" - info<< "#\n" - if(options[:format_markdown]) - info<< "# Table name: `#{klass.table_name}`\n" - info<< "#\n" - info<< "# ### Columns\n" - else - info<< "# Table name: #{klass.table_name}\n" - end - info<< "#\n" + info << get_schema_header_text(klass, options) - max_size = klass.column_names.map{|name| name.size}.max || 0 + max_size = klass.column_names.map(&:size).max || 0 + with_comment = options[:with_comment] && klass.columns.first.respond_to?(:comment) + max_size = klass.columns.map{|col| col.name.size + col.comment.size }.max || 0 if with_comment + max_size += 2 if with_comment max_size += options[:format_rdoc] ? 5 : 1 md_names_overhead = 6 md_type_allowance = 18 bare_type_allowance = 16 - if(options[:format_markdown]) - info<< sprintf( "# %-#{max_size + md_names_overhead}.#{max_size + md_names_overhead}s | %-#{md_type_allowance}.#{md_type_allowance}s | %s\n", 'Name', 'Type', 'Attributes' ) - info<< "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n" + 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] @@ -185,20 +229,20 @@ def get_schema_info(klass, header, options = {}) 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? || hide_default?(col_type, options) + attrs << 'unsigned' if col.respond_to?(:unsigned?) && col.unsigned? + attrs << 'not null' unless col.null + attrs << 'primary key' if klass.primary_key && (klass.primary_key.is_a?(Array) ? klass.primary_key.collect(&:to_sym).include?(col.name.to_sym) : col.name.to_sym == klass.primary_key.to_sym) - if col_type == "decimal" + 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 @@ -208,38 +252,41 @@ def get_schema_info(klass, header, options = {}) 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) + indices = retrieve_indexes_from_table(klass) 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| + next if ind.columns.is_a?(String) 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 - + col_name = if with_comment + "#{col.name}(#{col.comment})" + else + col.name + end if options[:format_rdoc] - info << sprintf("# %-#{max_size}.#{max_size}s%s", "*#{col.name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n" + info << sprintf("# %-#{max_size}.#{max_size}s%s", "*#{col_name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n" elsif options[:format_markdown] - name_remainder = max_size - col.name.length + name_remainder = max_size - col_name.length type_remainder = (md_type_allowance - 2) - col_type.length - info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col.name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n" + info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col_name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n" else - info << sprintf("# %-#{max_size}.#{max_size}s:%-#{bare_type_allowance}.#{bare_type_allowance}s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n" + info << sprintf("# %-#{max_size}.#{max_size}s:%-#{bare_type_allowance}.#{bare_type_allowance}s %s", col_name, col_type, attrs.join(", ")).rstrip + "\n" end end @@ -251,6 +298,23 @@ def get_schema_info(klass, header, options = {}) info << get_foreign_key_info(klass, options) end + info << get_schema_footer_text(klass, options) + end + + def get_schema_header_text(klass, options = {}) + info = "#\n" + if options[:format_markdown] + info << "# Table name: `#{klass.table_name}`\n" + info << "#\n" + info << "# ### Columns\n" + else + info << "# Table name: #{klass.table_name}\n" + end + info << "#\n" + end + + def get_schema_footer_text(_klass, options = {}) + info = '' if options[:format_rdoc] info << "#--\n" info << "# #{END_MARK}\n" @@ -260,29 +324,30 @@ def get_schema_info(klass, header, options = {}) end end - def get_index_info(klass, options={}) - if(options[:format_markdown]) - index_info = "#\n# ### Indexes\n#\n" - else - index_info = "#\n# Indexes\n#\n" - end + def get_index_info(klass, options = {}) + index_info = if options[:format_markdown] + "#\n# ### Indexes\n#\n" + else + "#\n# Indexes\n#\n" + end - indexes = klass.connection.indexes(klass.table_name) - return "" if indexes.empty? + indexes = retrieve_indexes_from_table(klass) + 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]) - 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 + indexes.sort_by(&:name).each do |index| + index_info << if options[:format_markdown] + sprintf("# * `%s`%s:\n# * **`%s`**\n", index.name, index.unique ? " (_unique_)" : "", Array(index.columns).join("`**\n# * **`")) + else + sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{Array(index.columns).join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n" + end end - 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 @@ -292,45 +357,67 @@ def hide_limit?(col_type, options) excludes.include?(col_type) end - def get_foreign_key_info(klass, options={}) - if(options[:format_markdown]) - fk_info = "#\n# ### Foreign Keys\n#\n" - else - fk_info = "#\n# Foreign Keys\n#\n" - end + def hide_default?(col_type, options) + excludes = + if options[:hide_default_column_types].blank? + NO_DEFAULT_COL_TYPES + else + options[:hide_default_column_types].split(',') + end + + excludes.include?(col_type) + end + + def get_foreign_key_info(klass, options = {}) + fk_info = if options[:format_markdown] + "#\n# ### Foreign Keys\n#\n" + else + "#\n# Foreign Keys\n#\n" + end - return "" unless klass.connection.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? + + format_name = ->(fk) { options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, '...') } - max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1 - foreign_keys.sort_by{|fk| fk.name}.each do |fk| + max_size = foreign_keys.map(&format_name).map(&:size).max + 1 + foreign_keys.sort_by {|fk| [format_name.call(fk), fk.column]}.each do |fk| ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}" - if(options[:format_markdown]) - fk_info << sprintf("# * `%s`:\n# * **`%s`**\n", fk.name, ref_info) - else - fk_info << sprintf("# %-#{max_size}.#{max_size}s %s", fk.name, "(#{ref_info})").rstrip + "\n" - end + 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! + + fk_info << if options[:format_markdown] + sprintf("# * `%s`%s:\n# * **`%s`**\n", format_name.call(fk), constraints_info.blank? ? '' : " (_#{constraints_info}_)", ref_info) + else + sprintf("# %-#{max_size}.#{max_size}s %s %s", format_name.call(fk), "(#{ref_info})", constraints_info).rstrip + "\n" + end end - 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. + # a schema info block (a comment starting with "== Schema Information"), + # check if it matches the block that is already there. If so, leave it be. + # If not, remove the old info block and write a new one. + # + # == Returns: + # true or false depending on whether the file was modified. # # === Options (opts) # :force:: whether to update the file even if it doesn't seem to need it. # :position_in_*:: where to place the annotated section in fixture or model file, # :before, :top, :after or :bottom. Default is :before. # - def annotate_one_file(file_name, info_block, position, options={}) + def annotate_one_file(file_name, info_block, position, options = {}) if File.exist?(file_name) old_content = File.read(file_name) - return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/) + 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]?)/ @@ -341,17 +428,17 @@ def annotate_one_file(file_name, info_block, position, options={}) old_columns = old_header && old_header.scan(column_pattern).sort new_columns = new_header && new_header.scan(column_pattern).sort - magic_comment_matcher= Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)|(^# -\*- encoding\s?:.*\n)|(^#\s*frozen_string_literal:.+\n)|(^# -\*- frozen_string_literal\s*:.+-\*-\n)/) - magic_comments= old_content.scan(magic_comment_matcher).flatten.compact + magic_comment_matcher = Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)|(^# -\*- encoding\s?:.*\n)|(^#\s*frozen_string_literal:.+\n)|(^# -\*- frozen_string_literal\s*:.+-\*-\n)/) + magic_comments = old_content.scan(magic_comment_matcher).flatten.compact if old_columns == new_columns && !options[:force] return false else # Replace inline the old schema info with the new schema info - 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" : "" @@ -361,41 +448,48 @@ def annotate_one_file(file_name, info_block, position, options={}) # 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) + new_content = if %w(after bottom).include?(options[position].to_s) + magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block) + else + magic_comments.join + wrapped_info_block + "\n" + old_content + end end - File.open(file_name, "wb") { |f| f.puts new_content } + 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:: where to place the annotated section in model file @@ -410,41 +504,54 @@ def remove_annotation_of_file(file_name) # :exclude_scaffolds:: whether to skip modification of scaffold files # :exclude_controllers:: whether to skip modification of controller files # :exclude_helpers:: whether to skip modification of helper files + # :exclude_sti_subclasses:: whether to skip modification of files for STI subclasses + # + # == Returns: + # an array of file names that were annotated. # - def annotate(klass, file, header, options={}) + def annotate(klass, file, header, options = {}) begin + klass.reset_column_information info = get_schema_info(klass, header, options) - 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 - unless options[exclusion_key] - did_annotate = 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 + # Same options for active_admin models + if key == 'admin' + exclusion_key = 'exclude_class'.to_sym + position_key = 'position_in_class'.to_sym end - end - return did_annotate - rescue Exception => e + next if options[exclusion_key] + get_patterns(key) + .map { |f| resolve_filename(f, model_name, table_name) } + .each do |f| + if annotate_one_file(f, info, position_key, options_with_position(options, position_key)) + annotated << f + end + end + end + rescue StandardError => 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. @@ -453,8 +560,8 @@ def options_with_position(options, position_in) # in the model_dir directory. def get_model_files(options) models = [] - if(!options[:is_rake]) - models = ARGV.dup.reject{|m| m.match(/^(.*)=/)} + unless options[:is_rake] + models = ARGV.dup.reject { |m| m.match(/^(.*)=/) } end if models.empty? @@ -488,13 +595,13 @@ 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 @@ -505,43 +612,42 @@ def get_model_class(file) # Retrieve loaded model class by path to the file where it's supposed to be defined. def get_loaded_model(model_path) - begin - ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.camelize(model_path)) - rescue - # Revert to the old way but it is not really robust - ObjectSpace.each_object(::Class). - select do |c| - Class === c 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 - c.ancestors.include?(ActiveRecord::Base) - end. - detect { |c| ActiveSupport::Inflector.underscore(c.to_s) == model_path } - end + ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.camelize(model_path)) + rescue + # Revert to the old way but it is not really robust + ObjectSpace.each_object(::Class) + .select do |c| + Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a? + c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82 + c.ancestors.include?(ActiveRecord::Base) + end.detect { |c| ActiveSupport::Inflector.underscore(c.to_s) == model_path } + end + + def parse_options(options = {}) + 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 + def do_annotations(options = {}) + 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 @@ -549,27 +655,29 @@ def do_annotations(options={}) 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 - end + do_annotate = klass && + klass < ActiveRecord::Base && + (!options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name)) && + !klass.abstract_class? && + klass.table_exists? + + annotated.concat(annotate(klass, file, header, options)) if do_annotate rescue BadModelFileError => e unless options[:ignore_unknown_models] puts "Unable to annotate #{file}: #{e.message}" puts "\t" + e.backtrace.join("\n\t") if options[:trace] end - rescue Exception => e + rescue StandardError => e puts "Unable to annotate #{file}: #{e.message}" puts "\t" + e.backtrace.join("\n\t") if options[:trace] end end - def remove_annotations(options={}) - self.model_dir = options[:model_dir] if options[:model_dir] - self.root_dir = options[:root_dir] if options[:root_dir] + def remove_annotations(options = {}) + parse_options(options) + deannotated = [] deannotated_klass = false get_model_files(options).each do |file| @@ -580,19 +688,19 @@ def remove_annotations(options={}) 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. - map { |f| resolve_filename(f, model_name, table_name) }. - each do |f| + get_patterns(matched_types(options)) + .map { |f| resolve_filename(f, model_name, table_name) } + .each do |f| if File.exist?(f) - remove_annotation_of_file(f) + remove_annotation_of_file(f, options) deannotated_klass = true end end end - deannotated << klass if(deannotated_klass) - rescue Exception => e + deannotated << klass if deannotated_klass + rescue StandardError => e puts "Unable to deannotate #{File.join(file)}: #{e.message}" puts "\t" + e.backtrace.join("\n\t") if options[:trace] end @@ -601,10 +709,10 @@ def remove_annotations(options={}) 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) @@ -613,25 +721,26 @@ def classified_sort(cols) 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) } + [rest_cols, timestamps, associations].each { |a| a.sort_by!(&:name) } - return ([id] << rest_cols << timestamps << associations).flatten + ([id] << rest_cols << timestamps << associations).flatten.compact end # Ignore warnings for the duration of the block () def silence_warnings - old_verbose, $VERBOSE = $VERBOSE, nil + old_verbose = $VERBOSE + $VERBOSE = nil yield ensure $VERBOSE = old_verbose diff --git a/lib/annotate/annotate_routes.rb b/lib/annotate/annotate_routes.rb index e5fea8351..c1b0a9628 100644 --- a/lib/annotate/annotate_routes.rb +++ b/lib/annotate/annotate_routes.rb @@ -18,139 +18,181 @@ # Released under the same license as Ruby. No Support. No Warranty. # module AnnotateRoutes - PREFIX = "# == Route Map" + PREFIX = '== Route Map'.freeze + PREFIX_MD = '## Route Map'.freeze + HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'] - def self.do_annotations(options={}) - return unless(routes_exists?) + class << self + def content(line, maxs, options = {}) + return line.rstrip unless options[:format_markdown] - position_after = ! %w(before top).include?(options[:position_in_routes]) + line.each_with_index.map do |elem, index| + min_length = maxs.map { |arr| arr[index] }.max || 0 - routes_map = `rake routes`.split(/\n/, -1) + sprintf("%-#{min_length}.#{min_length}s", elem.tr('|', '-')) + end.join(' | ') + end - # In old versions of Rake, the first line of output was the cwd. Not so - # much in newer ones. We ditch that line if it exists, and if not, we - # keep the line around. - routes_map.shift if(routes_map.first =~ /^\(in \//) - - header = [ - "#{PREFIX}" + (options[:timestamp] ? " (Updated #{Time.now.strftime("%Y-%m-%d %H:%M")})" : ""), - "#" - ] + routes_map.map { |line| "# #{line}".rstrip } - - 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 - # a number. If the number is > 0, the - # annotation was found somewhere in the - # middle of the file. If the number is - # zero, no annotation was found. - - if(position_after) - # Ensure we have adequate trailing newlines at the end of the file to - # ensure a blank line separating the content from the annotation. - content << '' if(content.last != '') + def header(options = {}) + routes_map = app_routes_map(options) - # We're moving something from the top of the file to the bottom, so ditch - # the spacer we put in the first time around. - if(changed && where_header_found == :before) - content.shift if(content.first == '') + out = ["# #{options[:format_markdown] ? PREFIX_MD : PREFIX}" + (options[:timestamp] ? " (Updated #{Time.now.strftime('%Y-%m-%d %H:%M')})" : '')] + out += ['#'] + return out if routes_map.size.zero? + + maxs = [HEADER_ROW.map(&:size)] + routes_map[1..-1].map { |line| line.split.map(&:size) } + + if options[:format_markdown] + max = maxs.map(&:max).compact.max + + out += ["# #{content(HEADER_ROW, maxs, options)}"] + out += ["# #{content(['-' * max, '-' * max, '-' * max, '-' * max], maxs, options)}"] + else + out += ["# #{content(routes_map[0], maxs, options)}"] end - else - header = header << '' if(content.first != '' || changed) + + out + routes_map[1..-1].map { |line| "# #{content(options[:format_markdown] ? line.split(' ') : line, maxs, options)}" } end - content = position_after ? (content + header) : header + content + def do_annotations(options = {}) + return unless routes_exists? + existing_text = File.read(routes_file) - if write_contents(existing_text, content) - puts "#{routes_file} annotated." - else - puts "#{routes_file} unchanged." + if write_contents(existing_text, header(options), options) + puts "#{routes_file} annotated." + end end - end - def self.remove_annotations(options={}) - return unless(routes_exists?) - existing_text = File.read(routes_file) - (content, where_header_found) = strip_annotations(existing_text) + def remove_annotations(options={}) + return unless routes_exists? + existing_text = 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) - if write_contents(existing_text, content) - puts "Removed annotations from #{routes_file}." - else - puts "#{routes_file} unchanged." + if write_contents(existing_text, content, options) + puts "Removed annotations from #{routes_file}." + end end end -protected + private + + def self.app_routes_map(options) + routes_map = `rake routes`.split(/\n/, -1) + + # In old versions of Rake, the first line of output was the cwd. Not so + # much in newer ones. We ditch that line if it exists, and if not, we + # keep the line around. + routes_map.shift if routes_map.first =~ /^\(in \// + + # Skip routes which match given regex + # Note: it matches the complete line (route_name, path, controller/action) + if options[:ignore_routes] + routes_map.reject! { |line| line =~ /#{options[:ignore_routes]}/ } + end + + routes_map + end def self.routes_file - @routes_rb ||= File.join("config", "routes.rb") + @routes_rb ||= File.join('config', 'routes.rb') end def self.routes_exists? routes_exists = File.exists?(routes_file) - puts "Can`t find routes.rb" if(!routes_exists) - return routes_exists + puts "Can't find routes.rb" unless routes_exists + + routes_exists end - def self.write_contents(existing_text, new_content) + def self.write_contents(existing_text, header, options = {}) + content, where_header_found = strip_annotations(existing_text) + new_content = annotate_routes(header, content, where_header_found, options) + # Make sure we end on a trailing newline. - new_content << '' unless(new_content.last == '') + new_content << '' unless new_content.last == '' new_text = new_content.join("\n") - return false if existing_text == new_text + if existing_text == new_text + puts "#{routes_file} unchanged." + false + else + File.open(routes_file, 'wb') { |f| f.puts(new_text) } + true + end + end + + def self.annotate_routes(header, content, where_header_found, options = {}) + if %w(before top).include?(options[:position_in_routes]) + header = header << '' if content.first != '' + new_content = header + content + else + # Ensure we have adequate trailing newlines at the end of the file to + # ensure a blank line separating the content from the annotation. + content << '' unless content.last == '' + + # We're moving something from the top of the file to the bottom, so ditch + # the spacer we put in the first time around. + content.shift if where_header_found == :before && content.first == '' + + new_content = content + header + end - File.open(routes_file, "wb") { |f| f.puts(new_text) } - return true + new_content end + # TODO: write the method doc using ruby rdoc formats + # where_header_found => This will either be :before, :after, or + # a number. If the number is > 0, the + # annotation was found somewhere in the + # middle of the file. If the number is + # zero, no annotation was found. def self.strip_annotations(content) real_content = [] mode = :content - line_number = 0 header_found_at = 0 - content.split(/\n/, -1).each do |line| - line_number += 1 - begin - if(mode == :header) - if(line !~ /\s*#/) - mode = :content - raise unless (line == '') - end - elsif(mode == :content) - if(line =~ /^\s*#\s*== Route.*$/) - header_found_at = line_number - mode = :header - else - real_content << line - end + + content.split(/\n/, -1).each_with_index do |line, line_number| + if mode == :header && line !~ /\s*#/ + mode = :content + next unless line == '' + elsif mode == :content + if line =~ /^\s*#\s*== Route.*$/ + header_found_at = line_number + 1 # index start's at 0 + mode = :header + else + real_content << line end - rescue - retry end end - content_lines = real_content.count - # By default assume the annotation was found in the middle of the file... - where_header_found = header_found_at + where_header_found(real_content, header_found_at) + end + + def self.where_header_found(real_content, header_found_at) + # By default assume the annotation was found in the middle of the file + # ... unless we have evidence it was at the beginning ... - where_header_found = :before if(header_found_at == 1) + return real_content, :before if header_found_at == 1 + # ... or that it was at the end. - where_header_found = :after if(header_found_at >= content_lines) + return real_content, :after if header_found_at >= real_content.count - return real_content, where_header_found + # and the default + return real_content, header_found_at end def self.strip_on_removal(content, where_header_found) - if(where_header_found == :before) - content.shift while(content.first == '') - elsif(where_header_found == :after) - content.pop while(content.last == '') + if where_header_found == :before + content.shift while content.first == '' + elsif where_header_found == :after + content.pop while content.last == '' end + # TODO: If the user buried it in the middle, we should probably see about # TODO: preserving a single line of space between the content above and # TODO: below... - return content + content end end diff --git a/lib/annotate/version.rb b/lib/annotate/version.rb index ebcbe77de..fd53beb47 100644 --- a/lib/annotate/version.rb +++ b/lib/annotate/version.rb @@ -1,5 +1,5 @@ module Annotate def self.version - '2.7.0' + '2.7.2' end end diff --git a/lib/generators/annotate/install_generator.rb b/lib/generators/annotate/install_generator.rb index 903fda786..2d2d6ec97 100644 --- a/lib/generators/annotate/install_generator.rb +++ b/lib/generators/annotate/install_generator.rb @@ -1,14 +1,13 @@ module Annotate module Generators class InstallGenerator < Rails::Generators::Base - desc "Copy annotate_models rakefiles for automatic annotation" + desc 'Copy annotate_models rakefiles for automatic annotation' source_root File.expand_path('../templates', __FILE__) # copy rake tasks def copy_tasks - template "auto_annotate_models.rake", "lib/tasks/auto_annotate_models.rake" + template 'auto_annotate_models.rake', 'lib/tasks/auto_annotate_models.rake' end - end end end diff --git a/lib/generators/annotate/templates/auto_annotate_models.rake b/lib/generators/annotate/templates/auto_annotate_models.rake index fc9c2d6a7..6f7c00641 100644 --- a/lib/generators/annotate/templates/auto_annotate_models.rake +++ b/lib/generators/annotate/templates/auto_annotate_models.rake @@ -2,44 +2,50 @@ # NOTE: are sensitive to local FS writes, and besides -- it's just not proper # NOTE: to have a dev-mode tool do its thing in production. if Rails.env.development? + require 'annotate' task :set_annotation_options do # You can override any of these by setting an environment variable of the # same name. Annotate.set_defaults( - 'routes' => 'false', - 'position_in_routes' => 'before', - 'position_in_class' => 'before', - 'position_in_test' => 'before', - 'position_in_fixture' => 'before', - 'position_in_factory' => 'before', - 'position_in_serializer' => 'before', - 'show_foreign_keys' => 'true', - 'show_indexes' => 'true', - 'simple_indexes' => 'false', - 'model_dir' => 'app/models', - 'root_dir' => '', - 'include_version' => 'false', - 'require' => '', - 'exclude_tests' => 'false', - 'exclude_fixtures' => 'false', - 'exclude_factories' => 'false', - 'exclude_serializers' => 'false', - 'exclude_scaffolds' => 'false', - 'exclude_controllers' => 'false', - 'exclude_helpers' => 'false', - 'ignore_model_sub_dir' => 'false', - 'ignore_columns' => nil, - '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, + 'routes' => 'false', + 'position_in_routes' => 'before', + 'position_in_class' => 'before', + 'position_in_test' => 'before', + 'position_in_fixture' => 'before', + 'position_in_factory' => 'before', + 'position_in_serializer' => 'before', + 'show_foreign_keys' => 'true', + 'show_complete_foreign_keys' => 'false', + 'show_indexes' => 'true', + 'simple_indexes' => 'false', + 'model_dir' => 'app/models', + 'root_dir' => '', + 'include_version' => 'false', + 'require' => '', + 'exclude_tests' => 'false', + 'exclude_fixtures' => 'false', + 'exclude_factories' => 'false', + 'exclude_serializers' => 'false', + 'exclude_scaffolds' => 'true', + 'exclude_controllers' => 'true', + 'exclude_helpers' => 'true', + 'exclude_sti_subclasses' => 'false', + 'ignore_model_sub_dir' => 'false', + 'ignore_columns' => nil, + 'ignore_routes' => nil, + 'ignore_unknown_models' => 'false', + 'hide_limit_column_types' => '<%= AnnotateModels::NO_LIMIT_COL_TYPES.join(",") %>', + 'hide_default_column_types' => '<%= AnnotateModels::NO_DEFAULT_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, + 'with_comment' => true ) end diff --git a/lib/tasks/annotate_models.rake b/lib/tasks/annotate_models.rake index 45fee7428..95cee4768 100644 --- a/lib/tasks/annotate_models.rake +++ b/lib/tasks/annotate_models.rake @@ -1,16 +1,16 @@ annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) -if !ENV['is_cli'] +unless ENV['is_cli'] task :set_annotation_options - task :annotate_models => :set_annotation_options + task annotate_models: :set_annotation_options end -desc "Add schema information (as comments) to model and fixture files" -task :annotate_models => :environment do +desc 'Add schema information (as comments) to model and fixture files' +task annotate_models: :environment do require "#{annotate_lib}/annotate/annotate_models" require "#{annotate_lib}/annotate/active_record_patch" - options={ :is_rake => true } + options = {is_rake: true} ENV['position'] = options[:position] = Annotate.fallback(ENV['position'], 'before') options[:position_in_class] = Annotate.fallback(ENV['position_in_class'], ENV['position']) options[:position_in_fixture] = Annotate.fallback(ENV['position_in_fixture'], ENV['position']) @@ -18,10 +18,11 @@ task :annotate_models => :environment do 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[:show_foreign_keys] = Annotate.true?(ENV['show_foreign_keys']) + options[:show_complete_foreign_keys] = Annotate.true?(ENV['show_complete_foreign_keys']) options[:show_indexes] = Annotate.true?(ENV['show_indexes']) options[:simple_indexes] = Annotate.true?(ENV['simple_indexes']) options[:model_dir] = ENV['model_dir'] ? ENV['model_dir'].split(',') : ['app/models'] - options[:root_dir] = ENV['root_dir'] ? ENV['root_dir'].split(',') : [''] + options[:root_dir] = ENV['root_dir'] options[:include_version] = Annotate.true?(ENV['include_version']) options[:require] = ENV['require'] ? ENV['require'].split(',') : [] options[:exclude_tests] = Annotate.true?(ENV['exclude_tests']) @@ -29,8 +30,9 @@ task :annotate_models => :environment do options[:exclude_fixtures] = Annotate.true?(ENV['exclude_fixtures']) 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[:exclude_controllers] = Annotate.true?(ENV.fetch('exclude_controllers', 'true')) + options[:exclude_helpers] = Annotate.true?(ENV.fetch('exclude_helpers', 'true')) + options[:exclude_sti_subclasses] = Annotate.true?(ENV['exclude_sti_subclasses']) options[:ignore_model_sub_dir] = Annotate.true?(ENV['ignore_model_sub_dir']) options[:format_bare] = Annotate.true?(ENV['format_bare']) options[:format_rdoc] = Annotate.true?(ENV['format_rdoc']) @@ -42,16 +44,19 @@ task :annotate_models => :environment do options[:wrapper_open] = Annotate.fallback(ENV['wrapper_open'], ENV['wrapper']) options[:wrapper_close] = Annotate.fallback(ENV['wrapper_close'], ENV['wrapper']) options[:ignore_columns] = ENV.fetch('ignore_columns', nil) + 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'], '') AnnotateModels.do_annotations(options) end -desc "Remove schema information from model and fixture files" -task :remove_annotation => :environment do +desc 'Remove schema information from model and fixture files' +task remove_annotation: :environment do require "#{annotate_lib}/annotate/annotate_models" require "#{annotate_lib}/annotate/active_record_patch" - options={ :is_rake => true } + options = {is_rake: true} options[:model_dir] = ENV['model_dir'] options[:root_dir] = ENV['root_dir'] options[:require] = ENV['require'] ? ENV['require'].split(',') : [] diff --git a/lib/tasks/migrate.rake b/lib/tasks/annotate_models_migrate.rake similarity index 100% rename from lib/tasks/migrate.rake rename to lib/tasks/annotate_models_migrate.rake diff --git a/lib/tasks/annotate_routes.rake b/lib/tasks/annotate_routes.rake index 9605fd339..26a99fecc 100644 --- a/lib/tasks/annotate_routes.rake +++ b/lib/tasks/annotate_routes.rake @@ -6,6 +6,7 @@ task :annotate_routes => :environment do options={} ENV['position'] = options[:position] = Annotate.fallback(ENV['position'], 'before') options[:position_in_routes] = Annotate.fallback(ENV['position_in_routes'], ENV['position']) + options[:ignore_routes] = Annotate.fallback(ENV['ignore_routes'], nil) options[:require] = ENV['require'] ? ENV['require'].split(',') : [] AnnotateRoutes.do_annotations(options) end diff --git a/potato.md b/potato.md new file mode 100644 index 000000000..278088f5c --- /dev/null +++ b/potato.md @@ -0,0 +1,41 @@ +Colons can be used to align columns. + +| Tables | Are | Cool | +| ------------- |:-------------:| -----:| +| col 3 is | right-aligned | $1600 | +| col 2 is | centered | $12 | +| zebra stripes | are neat | $1 | + +There must be at least 3 dashes separating each header cell. +The outer pipes (|) are optional, and you don't need to make the +raw Markdown line up prettily. You can also use inline Markdown. + +Markdown | Less | Pretty +--- | --- | --- +*Still* | `renders` | **nicely** +1 | 2 | 3 + + + ## Route Map + +  Prefix | Verb | URI Pattern | Controller#Action + --------- | ---------- | --------------- | -------------------- + myaction1 | GET | /url1(.:format) | mycontroller1#action + myaction2 | POST | /url2(.:format) | mycontroller2#action +  myaction3 | DELETE-GET | /url3(.:format) | mycontroller3#action \n") + + + + Table name: `users` + + ### Columns + + Name | Type | Attributes + ----------------------- | ------------------ | --------------------------- + **`id`** | `integer` | `not null, primary key` + **`foreign_thing_id`** | `integer` | `not null` + + ### Foreign Keys + + * `fk_rails_...` (_ON DELETE => on_delete_value ON UPDATE => on_update_value_): + * **`foreign_thing_id => foreign_things.id`** diff --git a/spec/annotate/annotate_models_spec.rb b/spec/annotate/annotate_models_spec.rb old mode 100755 new mode 100644 index b56bc34e5..ccbc53a46 --- a/spec/annotate/annotate_models_spec.rb +++ b/spec/annotate/annotate_models_spec.rb @@ -1,74 +1,83 @@ -#encoding: utf-8 +# encoding: utf-8 require File.dirname(__FILE__) + '/../spec_helper.rb' require 'annotate/annotate_models' require 'annotate/active_record_patch' require 'active_support/core_ext/string' describe AnnotateModels do - def mock_foreign_key(name, from_column, to_table, to_column = 'id') - double("ForeignKeyDefinition", - :name => name, - :column => from_column, - :to_table => to_table, - :primary_key => to_column, - ) + def mock_index(name, columns = [], unique = false) + double('IndexKeyDefinition', + name: name, + columns: columns, + unique: unique) + end + + def mock_foreign_key(name, from_column, to_table, to_column = 'id', constraints = {}) + double('ForeignKeyDefinition', + name: name, + column: from_column, + to_table: to_table, + primary_key: to_column, + on_delete: constraints[:on_delete], + on_update: constraints[:on_update]) end def mock_connection(indexes = [], foreign_keys = []) - double("Conn", - :indexes => indexes, - :foreign_keys => foreign_keys, - :supports_foreign_keys? => true, - ) + double('Conn', + indexes: indexes, + foreign_keys: foreign_keys, + supports_foreign_keys?: true) end - def mock_class(table_name, primary_key, columns, foreign_keys = []) + def mock_class(table_name, primary_key, columns, indexes = [], foreign_keys = []) options = { - :connection => mock_connection([], foreign_keys), - :table_exists? => true, - :table_name => table_name, - :primary_key => primary_key, - :column_names => columns.map { |col| col.name.to_s }, - :columns => columns, - :column_defaults => Hash[columns.map { |col| - [col.name, col.default] - }] + connection: mock_connection(indexes, foreign_keys), + table_exists?: true, + table_name: table_name, + primary_key: primary_key, + column_names: columns.map { |col| col.name.to_s }, + columns: columns, + column_defaults: Hash[columns.map { |col| [col.name, col.default] }], + table_name_prefix: '' } - double("An ActiveRecord class", options) + double('An ActiveRecord class', options) end - def mock_column(name, type, options={}) + def mock_column(name, type, options = {}) default_options = { - :limit => nil, - :null => false, - :default => nil + limit: nil, + null: false, + default: nil } stubs = default_options.dup stubs.merge!(options) - stubs.merge!(:name => name, :type => type) + stubs[:name] = name + stubs[:type] = type - double("Column", stubs) + double('Column', stubs) end - it { expect(AnnotateModels.quote(nil)).to eql("NULL") } - it { expect(AnnotateModels.quote(true)).to eql("TRUE") } - it { expect(AnnotateModels.quote(false)).to eql("FALSE") } - it { expect(AnnotateModels.quote(25)).to eql("25") } - 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(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 with default options" do - klass = mock_class(:users, :id, [ - mock_column(:id, :integer, :limit => 8), - mock_column(:name, :string, :limit => 50), - mock_column(:notes, :text, :limit => 55), - ]) - - expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS) + it { expect(AnnotateModels.quote(nil)).to eql('NULL') } + it { expect(AnnotateModels.quote(true)).to eql('TRUE') } + it { expect(AnnotateModels.quote(false)).to eql('FALSE') } + it { expect(AnnotateModels.quote(25)).to eql('25') } + 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(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 with default options' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer, limit: 8), + mock_column(:name, :string, limit: 50), + mock_column(:notes, :text, limit: 55) + ]) + + expect(AnnotateModels.get_schema_info(klass, 'Schema Info')).to eql(<<-EOS) # Schema Info # # Table name: users @@ -80,13 +89,15 @@ def mock_column(name, type, options={}) EOS end - it "should get schema info even if the primary key is not set" do - klass = mock_class(:users, nil, [ - mock_column(:id, :integer), - mock_column(:name, :string, :limit => 50) - ]) + it 'should get schema info even if the primary key is not set' do + klass = mock_class(:users, + nil, + [ + mock_column(:id, :integer), + mock_column(:name, :string, limit: 50) + ]) - expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info')).to eql(<<-EOS) # Schema Info # # Table name: users @@ -97,14 +108,16 @@ def mock_column(name, type, options={}) EOS end - it "should get schema info even if the primary key is array, if using composite_primary_keys" do - klass = mock_class(:users, [:a_id, :b_id], [ - mock_column(:a_id, :integer), - mock_column(:b_id, :integer), - mock_column(:name, :string, :limit => 50) - ]) + it 'should get schema info even if the primary key is array, if using composite_primary_keys' do + klass = mock_class(:users, + [:a_id, :b_id], + [ + mock_column(:a_id, :integer), + mock_column(:b_id, :integer), + mock_column(:name, :string, limit: 50) + ]) - expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info')).to eql(<<-EOS) # Schema Info # # Table name: users @@ -115,13 +128,16 @@ def mock_column(name, type, options={}) # EOS end - it "should get schema info with enum type " do - klass = mock_class(:users, nil, [ - mock_column(:id, :integer), - mock_column(:name, :enum, :limit => [:enum1, :enum2]) - ]) - expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS) + it 'should get schema info with enum type' do + klass = mock_class(:users, + nil, + [ + mock_column(:id, :integer), + mock_column(:name, :enum, limit: [:enum1, :enum2]) + ]) + + expect(AnnotateModels.get_schema_info(klass, 'Schema Info')).to eql(<<-EOS) # Schema Info # # Table name: users @@ -132,13 +148,40 @@ def mock_column(name, type, options={}) EOS end - it "should get schema info for integer and boolean with default" do - klass = mock_class(:users, :id, [ - mock_column(:id, :integer), - mock_column(:size, :integer, :default => 20), - mock_column(:flag, :boolean, :default => false) - ]) - expect(AnnotateModels.get_schema_info(klass, "Schema Info")).to eql(<<-EOS) + it 'should get schema info with unsigned' do + klass = mock_class(:users, + nil, + [ + mock_column(:id, :integer), + mock_column(:integer, :integer, unsigned?: true), + mock_column(:bigint, :bigint, unsigned?: true), + mock_column(:float, :float, unsigned?: true), + mock_column(:decimal, :decimal, unsigned?: true, precision: 10, scale: 2), + ]) + + expect(AnnotateModels.get_schema_info(klass, 'Schema Info')).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null +# integer :integer unsigned, not null +# bigint :bigint unsigned, not null +# float :float unsigned, not null +# decimal :decimal(10, 2) unsigned, not null +# +EOS + end + + it 'should get schema info for integer and boolean with default' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:size, :integer, default: 20), + mock_column(:flag, :boolean, default: false) + ]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info')).to eql(<<-EOS) # Schema Info # # Table name: users @@ -150,19 +193,26 @@ def mock_column(name, type, options={}) EOS end - it "should get foreign key info" do - klass = mock_class(:users, :id, [ - mock_column(:id, :integer), - mock_column(:foreign_thing_id, :integer), - ], - [ - mock_foreign_key( - 'fk_rails_02e851e3b7', - 'foreign_thing_id', - 'foreign_things' - ) - ]) - expect(AnnotateModels.get_schema_info(klass, "Schema Info", :show_foreign_keys => true)).to eql(<<-EOS) + it 'should get foreign key info' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], + [], + [ + mock_foreign_key('fk_rails_cf2568e89e', + 'foreign_thing_id', + 'foreign_things'), + mock_foreign_key('custom_fk_name', + 'other_thing_id', + 'other_things'), + mock_foreign_key('fk_rails_a70234b26c', + 'third_thing_id', + 'third_things') + ]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_foreign_keys: true)).to eql(<<-EOS) # Schema Info # # Table name: users @@ -172,17 +222,168 @@ def mock_column(name, type, options={}) # # Foreign Keys # -# fk_rails_02e851e3b7 (foreign_thing_id => foreign_things.id) +# custom_fk_name (other_thing_id => other_things.id) +# fk_rails_... (foreign_thing_id => foreign_things.id) +# fk_rails_... (third_thing_id => third_things.id) # EOS end - it "should get schema info as RDoc" do - klass = mock_class(:users, :id, [ - mock_column(:id, :integer), - mock_column(:name, :string, :limit => 50) - ]) - expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, :format_rdoc => true)).to eql(<<-EOS) + it 'should get complete foreign key info' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], + [], + [ + mock_foreign_key('fk_rails_cf2568e89e', + 'foreign_thing_id', + 'foreign_things'), + mock_foreign_key('custom_fk_name', + 'other_thing_id', + 'other_things'), + mock_foreign_key('fk_rails_a70234b26c', + 'third_thing_id', + 'third_things') + ]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_foreign_keys: true, show_complete_foreign_keys: true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key +# foreign_thing_id :integer not null +# +# Foreign Keys +# +# custom_fk_name (other_thing_id => other_things.id) +# fk_rails_a70234b26c (third_thing_id => third_things.id) +# fk_rails_cf2568e89e (foreign_thing_id => foreign_things.id) +# + EOS + end + + it 'should get foreign key info if on_delete/on_update options present' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], + [], + [ + mock_foreign_key('fk_rails_02e851e3b7', + 'foreign_thing_id', + 'foreign_things', + 'id', + on_delete: 'on_delete_value', + on_update: 'on_update_value') + ]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_foreign_keys: true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key +# foreign_thing_id :integer not null +# +# Foreign Keys +# +# fk_rails_... (foreign_thing_id => foreign_things.id) ON DELETE => on_delete_value ON UPDATE => on_update_value +# +EOS + end + + it 'should get indexes keys' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], [mock_index('index_rails_02e851e3b7', ['id']), + mock_index('index_rails_02e851e3b8', ['foreign_thing_id'])]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_indexes: true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key +# foreign_thing_id :integer not null +# +# Indexes +# +# index_rails_02e851e3b7 (id) +# index_rails_02e851e3b8 (foreign_thing_id) +# +EOS + end + + it 'should get simple indexes keys' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], [mock_index('index_rails_02e851e3b7', ['id']), + mock_index('index_rails_02e851e3b8', ['foreign_thing_id'])]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', simple_indexes: true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key +# foreign_thing_id :integer not null +# +EOS + end + + it 'should get simple indexes keys if one is in string form' do + klass = mock_class(:users, + :id, + [ + mock_column("id", :integer), + mock_column("name", :string) + ], [mock_index('index_rails_02e851e3b7', ['id']), + mock_index('index_rails_02e851e3b8', 'LOWER(name)')]) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', simple_indexes: true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key, indexed +# name :string not null +# +EOS + end + + it 'should not crash getting indexes keys' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], []) + expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_indexes: true)).to eql(<<-EOS) +# Schema Info +# +# Table name: users +# +# id :integer not null, primary key +# foreign_thing_id :integer not null +# +EOS + end + + it 'should get schema info as RDoc' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:name, :string, limit: 50) + ]) + expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_rdoc: true)).to eql(<<-EOS) # #{AnnotateModels::PREFIX} # # Table name: users @@ -195,76 +396,302 @@ def mock_column(name, type, options={}) EOS end - describe "#get_schema_info with custom options" do + it 'should get schema info as Markdown' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:name, :string, limit: 50) + ]) + expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true)).to eql(<<-EOS) +# #{AnnotateModels::PREFIX} +# +# Table name: `users` +# +# ### Columns +# +# Name | Type | Attributes +# ----------- | ------------------ | --------------------------- +# **`id`** | `integer` | `not null, primary key` +# **`name`** | `string(50)` | `not null` +# +EOS + end + + it 'should get schema info as Markdown with foreign keys' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:foreign_thing_id, :integer) + ], + [], + [ + mock_foreign_key('fk_rails_02e851e3b7', + 'foreign_thing_id', + 'foreign_things', + 'id', + on_delete: 'on_delete_value', + on_update: 'on_update_value') + ]) + expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_foreign_keys: true)).to eql(<<-EOS) +# #{AnnotateModels::PREFIX} +# +# Table name: `users` +# +# ### Columns +# +# Name | Type | Attributes +# ----------------------- | ------------------ | --------------------------- +# **`id`** | `integer` | `not null, primary key` +# **`foreign_thing_id`** | `integer` | `not null` +# +# ### Foreign Keys +# +# * `fk_rails_...` (_ON DELETE => on_delete_value ON UPDATE => on_update_value_): +# * **`foreign_thing_id => foreign_things.id`** +# +EOS + end + + it 'should get schema info as Markdown with indexes' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:name, :string, limit: 50) + ], [mock_index('index_rails_02e851e3b7', ['id']), + mock_index('index_rails_02e851e3b8', ['foreign_thing_id'])]) + expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_indexes: true)).to eql(<<-EOS) +# #{AnnotateModels::PREFIX} +# +# Table name: `users` +# +# ### Columns +# +# Name | Type | Attributes +# ----------- | ------------------ | --------------------------- +# **`id`** | `integer` | `not null, primary key` +# **`name`** | `string(50)` | `not null` +# +# ### Indexes +# +# * `index_rails_02e851e3b7`: +# * **`id`** +# * `index_rails_02e851e3b8`: +# * **`foreign_thing_id`** +# +EOS + end + + describe '#get_schema_info with custom options' do def self.when_called_with(options = {}) expected = options.delete(:returns) + default_columns = [ + [:id, :integer, { limit: 8 }], + [:active, :boolean, { limit: 1 }], + [:name, :string, { limit: 50 }], + [:notes, :text, { limit: 55 }] + ] 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) + with_columns = (options.delete(:with_columns) || default_columns).map do |column| + mock_column(column[0], column[1], column[2]) + end + + klass = mock_class(:users, :id, with_columns) + + 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 + describe 'hide_limit_column_types option' do + 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 + 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 - require "tmpdir" + describe 'hide_default_column_types option' do + mocked_columns_without_id = [ + [:profile, :json, default: {}], + [:settings, :jsonb, default: {}], + [:parameters, :hstore, default: {}] + ] + + when_called_with hide_default_column_types: '', + with_columns: mocked_columns_without_id, + returns: + <<-EOS.strip_heredoc + # Schema Info + # + # Table name: users + # + # profile :json not null + # settings :jsonb not null + # parameters :hstore not null + # + EOS - module ::ActiveRecord - class Base - def self.has_many name - end + when_called_with hide_default_column_types: 'skip', + with_columns: mocked_columns_without_id, + returns: + <<-EOS.strip_heredoc + # Schema Info + # + # Table name: users + # + # profile :json default({}), not null + # settings :jsonb default({}), not null + # parameters :hstore default({}), not null + # + EOS + + when_called_with hide_default_column_types: 'json', + with_columns: mocked_columns_without_id, + returns: + <<-EOS.strip_heredoc + # Schema Info + # + # Table name: users + # + # profile :json not null + # settings :jsonb default({}), not null + # parameters :hstore default({}), not null + # + EOS + end + + describe 'classified_sort option' do + mocked_columns_without_id = [ + [:active, :boolean, { limit: 1 }], + [:name, :string, { limit: 50 }], + [:notes, :text, { limit: 55 }] + ] + + when_called_with classified_sort: 'yes', + with_columns: mocked_columns_without_id, returns: + <<-EOS.strip_heredoc + # Schema Info + # + # Table name: users + # + # active :boolean not null + # name :string(50) not null + # notes :text(55) not null + # + EOS + end + + describe 'with_comment option' do + mocked_columns_with_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' }] + ] + + when_called_with with_comment: 'yes', + with_columns: mocked_columns_with_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 + # + EOS + + it 'should get schema info as RDoc' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer, comment: 'ID'), + mock_column(:name, :string, limit: 50, comment: 'Name') + ]) + expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_rdoc: true, with_comment: true)).to eql(<<-EOS.strip_heredoc) + # #{AnnotateModels::PREFIX} + # + # Table name: users + # + # *id(ID)*:: integer, not null, primary key + # *name(Name)*:: string(50), not null + #-- + # #{AnnotateModels::END_MARK} + #++ + EOS + end + + it 'should get schema info as Markdown' do + klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer, comment: 'ID'), + mock_column(:name, :string, limit: 50, comment: 'Name') + ]) + expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, with_comment: true)).to eql(<<-EOS.strip_heredoc) + # #{AnnotateModels::PREFIX} + # + # Table name: `users` + # + # ### Columns + # + # Name | Type | Attributes + # ----------------- | ------------------ | --------------------------- + # **`id(ID)`** | `integer` | `not null, primary key` + # **`name(Name)`** | `string(50)` | `not null` + # + EOS end end + end + + describe '#get_model_class' do + require 'tmpdir' - # todo: use 'files' gem instead - def create(file, body="hi") + # TODO: use 'files' gem instead + def create(file, body = 'hi') file_path = File.join(AnnotateModels.model_dir[0], file) FileUtils.mkdir_p(File.dirname(file_path)) - File.open(file_path, "wb") do |f| + File.open(file_path, 'wb') do |f| f.puts(body) end file_path @@ -281,7 +708,7 @@ def check_class_name(file, class_name) AnnotateModels.model_dir = Dir.mktmpdir 'annotate_models' end - it "should work" do + it 'should work' do create 'foo.rb', <<-EOS class Foo < ActiveRecord::Base end @@ -289,7 +716,7 @@ class Foo < ActiveRecord::Base check_class_name 'foo.rb', 'Foo' end - it "should find models with non standard capitalization" do + it 'should find models with non standard capitalization' do create 'foo_with_capitals.rb', <<-EOS class FooWithCAPITALS < ActiveRecord::Base end @@ -297,7 +724,7 @@ class FooWithCAPITALS < ActiveRecord::Base check_class_name 'foo_with_capitals.rb', 'FooWithCAPITALS' end - it "should find models inside modules" do + it 'should find models inside modules' do create 'bar/foo_inside_bar.rb', <<-EOS module Bar class FooInsideBar < ActiveRecord::Base @@ -307,7 +734,7 @@ class FooInsideBar < ActiveRecord::Base check_class_name 'bar/foo_inside_bar.rb', 'Bar::FooInsideBar' end - it "should find AR model when duplicated by a nested model" do + it 'should find AR model when duplicated by a nested model' do create 'foo.rb', <<-EOS class Foo < ActiveRecord::Base end @@ -321,7 +748,7 @@ class Bar::Foo check_class_name 'foo.rb', 'Foo' end - it "should find AR model nested inside a class" do + it 'should find AR model nested inside a class' do create 'voucher.rb', <<-EOS class Voucher < ActiveRecord::Base end @@ -338,8 +765,7 @@ class Foo check_class_name 'voucher/foo.rb', 'Voucher::Foo' end - - it "should not care about unknown macros" do + it 'should not care about unknown macros' do create 'foo_with_macro.rb', <<-EOS class FooWithMacro < ActiveRecord::Base acts_as_awesome :yah @@ -348,7 +774,7 @@ class FooWithMacro < ActiveRecord::Base check_class_name 'foo_with_macro.rb', 'FooWithMacro' end - it "should not care about known macros" do + it 'should not care about known macros' do create('foo_with_known_macro.rb', <<-EOS) class FooWithKnownMacro < ActiveRecord::Base has_many :yah @@ -357,7 +783,7 @@ class FooWithKnownMacro < ActiveRecord::Base check_class_name 'foo_with_known_macro.rb', 'FooWithKnownMacro' end - it "should work with class names with ALL CAPS segments" do + it 'should work with class names with ALL CAPS segments' do create('foo_with_capitals.rb', <<-EOS) class FooWithCAPITALS < ActiveRecord::Base acts_as_awesome :yah @@ -366,7 +792,7 @@ class FooWithCAPITALS < ActiveRecord::Base check_class_name 'foo_with_capitals.rb', 'FooWithCAPITALS' end - it "should not complain of invalid multibyte char (USASCII)" do + it 'should not complain of invalid multibyte char (USASCII)' do create 'foo_with_utf8.rb', <<-EOS #encoding: utf-8 class FooWithUtf8 < ActiveRecord::Base @@ -376,7 +802,7 @@ class FooWithUtf8 < ActiveRecord::Base check_class_name 'foo_with_utf8.rb', 'FooWithUtf8' end - it "should find models inside modules with non standard capitalization" do + it 'should find models inside modules with non standard capitalization' do create 'bar/foo_inside_capitals_bar.rb', <<-EOS module BAR class FooInsideCapitalsBAR < ActiveRecord::Base @@ -386,7 +812,7 @@ class FooInsideCapitalsBAR < ActiveRecord::Base check_class_name 'bar/foo_inside_capitals_bar.rb', 'BAR::FooInsideCapitalsBAR' end - it "should find non-namespaced models inside subdirectories" do + it 'should find non-namespaced models inside subdirectories' do create 'bar/non_namespaced_foo_inside_bar.rb', <<-EOS class NonNamespacedFooInsideBar < ActiveRecord::Base end @@ -394,7 +820,7 @@ class NonNamespacedFooInsideBar < ActiveRecord::Base check_class_name 'bar/non_namespaced_foo_inside_bar.rb', 'NonNamespacedFooInsideBar' end - it "should find non-namespaced models with non standard capitalization inside subdirectories" do + it 'should find non-namespaced models with non standard capitalization inside subdirectories' do create 'bar/non_namespaced_foo_with_capitals_inside_bar.rb', <<-EOS class NonNamespacedFooWithCapitalsInsideBar < ActiveRecord::Base end @@ -402,7 +828,7 @@ class NonNamespacedFooWithCapitalsInsideBar < ActiveRecord::Base check_class_name 'bar/non_namespaced_foo_with_capitals_inside_bar.rb', 'NonNamespacedFooWithCapitalsInsideBar' end - it "should allow known macros" do + it 'should allow known macros' do create('foo_with_known_macro.rb', <<-EOS) class FooWithKnownMacro < ActiveRecord::Base has_many :yah @@ -410,10 +836,10 @@ class FooWithKnownMacro < ActiveRecord::Base EOS expect(capturing(:stderr) do check_class_name 'foo_with_known_macro.rb', 'FooWithKnownMacro' - end).to eq("") + end).to eq('') end - it "should not require model files twice" do + it 'should not require model files twice' do create 'loaded_class.rb', <<-EOS class LoadedClass < ActiveRecord::Base CONSTANT = 1 @@ -423,21 +849,22 @@ class LoadedClass < ActiveRecord::Base Kernel.load "#{path}.rb" expect(Kernel).not_to receive(:require).with(path) - expect(capturing(:stderr) { + expect(capturing(:stderr) do check_class_name 'loaded_class.rb', 'LoadedClass' - }).not_to include("warning: already initialized constant LoadedClass::CONSTANT") + end).not_to include('warning: already initialized constant LoadedClass::CONSTANT') end end - describe "#remove_annotation_of_file" do - require "tmpdir" + describe '#remove_annotation_of_file' do + require 'tmpdir' - def create(file, body="hi") + def create(file, body = 'hi') path = File.join(@dir, file) - File.open(path, "w") do |f| + File.open(path, 'w') do |f| f.puts(body) end - return path + + path end def content(path) @@ -448,8 +875,8 @@ def content(path) @dir = Dir.mktmpdir 'annotate_models' end - it "should remove before annotate" do - path = create "before.rb", <<-EOS + it 'should remove before annotate' do + path = create 'before.rb', <<-EOS # == Schema Information # # Table name: foo @@ -471,8 +898,8 @@ class Foo < ActiveRecord::Base EOS end - it "should remove after annotate" do - path = create "after.rb", <<-EOS + it 'should remove after annotate' do + path = create 'after.rb', <<-EOS class Foo < ActiveRecord::Base end @@ -491,13 +918,61 @@ class Foo < ActiveRecord::Base expect(content(path)).to eq <<-EOS class Foo < ActiveRecord::Base +end + EOS + end + + it 'should remove opening wrapper' do + path = create 'opening_wrapper.rb', <<-EOS +# wrapper +# == Schema Information +# +# Table name: foo +# +# id :integer not null, primary key +# created_at :datetime +# updated_at :datetime +# + +class Foo < ActiveRecord::Base +end + EOS + + AnnotateModels.remove_annotation_of_file(path, wrapper_open: 'wrapper') + + expect(content(path)).to eq <<-EOS +class Foo < ActiveRecord::Base +end + EOS + end + + it 'should remove closing wrapper' do + path = create 'closing_wrapper.rb', <<-EOS +class Foo < ActiveRecord::Base +end + +# == Schema Information +# +# Table name: foo +# +# id :integer not null, primary key +# created_at :datetime +# updated_at :datetime +# +# wrapper + + EOS + + AnnotateModels.remove_annotation_of_file(path, wrapper_close: 'wrapper') + + expect(content(path)).to eq <<-EOS +class Foo < ActiveRecord::Base end EOS 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' @@ -525,30 +1000,33 @@ class Foo < ActiveRecord::Base expect(filename). to eq 'test/fixtures/parent/children.yml' end end - describe "annotating a file" do + describe 'annotating a file' do before do @model_dir = Dir.mktmpdir('annotate_models') - (@model_file_name, @file_content) = write_model "user.rb", <<-EOS + (@model_file_name, @file_content) = write_model 'user.rb', <<-EOS class User < ActiveRecord::Base end EOS - @klass = mock_class(:users, :id, [ - mock_column(:id, :integer), - mock_column(:name, :string, :limit => 50) - ]) - @schema_info = AnnotateModels.get_schema_info(@klass, "== Schema Info") + @klass = mock_class(:users, + :id, + [ + mock_column(:id, :integer), + mock_column(:name, :string, limit: 50) + ]) + @schema_info = AnnotateModels.get_schema_info(@klass, '== Schema Info') Annotate.reset_options end - def write_model file_name, file_content + def write_model(file_name, file_content) fname = File.join(@model_dir, file_name) FileUtils.mkdir_p(File.dirname(fname)) - File.open(fname, "wb") { |f| f.write file_content } - return fname, file_content + File.open(fname, 'wb') { |f| f.write file_content } + + [fname, file_content] end - def annotate_one_file options = {} + def annotate_one_file(options = {}) Annotate.set_defaults(options) options = Annotate.setup_options(options) AnnotateModels.annotate_one_file(@model_file_name, @schema_info, :position_in_class, options) @@ -572,135 +1050,112 @@ def magic_comments_list_each "# 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 - - it "should put annotation before class if :position == 'before'" do - annotate_one_file :position => "before" - expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") - end - - it "should put annotation before class if :position => :before" do - annotate_one_file :position => :before - expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") - end - - it "should put annotation before class if :position == 'top'" do - annotate_one_file :position => "top" - expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") - end - - it "should put annotation before class if :position => :top" do - annotate_one_file :position => :top - expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") - end - - it "should put annotation after class if :position => 'after'" do - annotate_one_file :position => 'after' - expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") - end - - it "should put annotation after class if :position => :after" do - annotate_one_file :position => :after - expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") + '# -*- frozen_string_literal : true -*-' + ].each { |magic_comment| yield magic_comment } end - it "should put annotation after class if :position => 'bottom'" do - annotate_one_file :position => 'bottom' - expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") + ['before', :before, 'top', :top].each do |position| + it "should put annotation before class if :position == #{position}" do + annotate_one_file position: position + expect(File.read(@model_file_name)) + .to eq("#{@schema_info}\n#{@file_content}") + end end - it "should put annotation after class if :position => :bottom" do - annotate_one_file :position => :bottom - expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") + ['after', :after, 'bottom', :bottom].each do |position| + it "should put annotation after class if position: #{position}" do + annotate_one_file position: position + expect(File.read(@model_file_name)) + .to eq("#{@file_content}\n#{@schema_info}") + end end it 'should wrap annotation if wrapper is specified' do - annotate_one_file :wrapper_open => 'START', :wrapper_close => 'END' - expect(File.read(@model_file_name)).to eq("# START\n#{@schema_info}# END\n\n#{@file_content}") + annotate_one_file wrapper_open: 'START', wrapper_close: 'END' + expect(File.read(@model_file_name)) + .to eq("# START\n#{@schema_info}# END\n\n#{@file_content}") end - describe "with existing annotation => :before" do + describe 'with existing annotation => :before' do before do - annotate_one_file :position => :before - another_schema_info = AnnotateModels.get_schema_info(mock_class(:users, :id, [mock_column(:id, :integer),]), - "== Schema Info") + annotate_one_file position: :before + another_schema_info = AnnotateModels.get_schema_info(mock_class(:users, :id, [mock_column(:id, :integer)]), '== Schema Info') @schema_info = another_schema_info end - it "should retain current position" do + it 'should retain current position' do annotate_one_file expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") end - it "should retain current position even when :position is changed to :after" do - annotate_one_file :position => :after + it 'should retain current position even when :position is changed to :after' do + annotate_one_file position: :after expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") end - it "should change position to :after when :force => true" do - annotate_one_file :position => :after, :force => true + it 'should change position to :after when force: true' do + annotate_one_file position: :after, force: true expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") end end - describe "with existing annotation => :after" do + describe 'with existing annotation => :after' do before do - annotate_one_file :position => :after - another_schema_info = AnnotateModels.get_schema_info(mock_class(:users, :id, [mock_column(:id, :integer),]), - "== Schema Info") + annotate_one_file position: :after + another_schema_info = AnnotateModels.get_schema_info(mock_class(:users, :id, [mock_column(:id, :integer)]), '== Schema Info') @schema_info = another_schema_info end - it "should retain current position" do + it 'should retain current position' do annotate_one_file expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") end - it "should retain current position even when :position is changed to :before" do - annotate_one_file :position => :before + it 'should retain current position even when :position is changed to :before' do + annotate_one_file position: :before expect(File.read(@model_file_name)).to eq("#{@file_content}\n#{@schema_info}") end - it "should change position to :before when :force => true" do - annotate_one_file :position => :before, :force => true + it 'should change position to :before when force: true' do + annotate_one_file position: :before, force: true expect(File.read(@model_file_name)).to eq("#{@schema_info}\n#{@file_content}") end end it 'should skip columns with option[:ignore_columns] set' do - output = AnnotateModels.get_schema_info(@klass, "== Schema Info", + output = AnnotateModels.get_schema_info(@klass, '== Schema Info', :ignore_columns => '(id|updated_at|created_at)') expect(output.match(/id/)).to be_nil end - it "works with namespaced models (i.e. models inside modules/subdirectories)" do - (model_file_name, file_content) = write_model "foo/user.rb", <<-EOS + it 'works with namespaced models (i.e. models inside modules/subdirectories)' do + (model_file_name, file_content) = write_model 'foo/user.rb', <<-EOS class Foo::User < ActiveRecord::Base end EOS - klass = mock_class(:'foo_users', :id, [ - mock_column(:id, :integer), - mock_column(:name, :string, :limit => 50) - ]) - schema_info = AnnotateModels.get_schema_info(klass, "== Schema Info") - AnnotateModels.annotate_one_file(model_file_name, schema_info, :position => :before) + klass = mock_class(:'foo_users', + :id, + [ + mock_column(:id, :integer), + mock_column(:name, :string, limit: 50) + ]) + schema_info = AnnotateModels.get_schema_info(klass, '== Schema Info') + AnnotateModels.annotate_one_file(model_file_name, schema_info, position: :before) expect(File.read(model_file_name)).to eq("#{schema_info}\n#{file_content}") end - it "should not touch magic comments" do + it 'should not touch magic comments' do magic_comments_list_each do |magic_comment| - write_model "user.rb", <<-EOS + write_model 'user.rb', <<-EOS #{magic_comment} class User < ActiveRecord::Base end EOS - annotate_one_file :position => :before + annotate_one_file position: :before - lines= magic_comment.split("\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" @@ -710,69 +1165,69 @@ class User < ActiveRecord::Base end describe "if a file can't be annotated" do - before do - allow(AnnotateModels).to receive(:get_loaded_model).with('user').and_return(nil) - - write_model('user.rb', <<-EOS) - class User < ActiveRecord::Base - raise "oops" - end - EOS - end - - it "displays an error message" do - expect(capturing(:stdout) { - AnnotateModels.do_annotations :model_dir => @model_dir, :is_rake => true - }).to include("Unable to annotate #{@model_dir}/user.rb: oops") - end - - it "displays the full stack trace with --trace" do - expect(capturing(:stdout) { - AnnotateModels.do_annotations :model_dir => @model_dir, :trace => true, :is_rake => true - }).to include("/spec/annotate/annotate_models_spec.rb:") - end - - it "omits the full stack trace without --trace" do - expect(capturing(:stdout) { - AnnotateModels.do_annotations :model_dir => @model_dir, :trace => false, :is_rake => true - }).not_to include("/spec/annotate/annotate_models_spec.rb:") - end + before do + allow(AnnotateModels).to receive(:get_loaded_model).with('user').and_return(nil) + + write_model('user.rb', <<-EOS) + class User < ActiveRecord::Base + raise "oops" + end + EOS + end + + it 'displays an error message' do + expect(capturing(:stdout) do + AnnotateModels.do_annotations model_dir: @model_dir, is_rake: true + end).to include("Unable to annotate #{@model_dir}/user.rb: oops") + end + + it 'displays the full stack trace with --trace' do + expect(capturing(:stdout) do + AnnotateModels.do_annotations model_dir: @model_dir, trace: true, is_rake: true + end).to include('/spec/annotate/annotate_models_spec.rb:') + end + + it 'omits the full stack trace without --trace' do + expect(capturing(:stdout) do + AnnotateModels.do_annotations model_dir: @model_dir, trace: false, is_rake: true + end).not_to include('/spec/annotate/annotate_models_spec.rb:') + end end describe "if a file can't be deannotated" do - before do - allow(AnnotateModels).to receive(:get_loaded_model).with('user').and_return(nil) - - write_model('user.rb', <<-EOS) - class User < ActiveRecord::Base - raise "oops" - end - EOS - end - - it "displays an error message" do - expect(capturing(:stdout) { - AnnotateModels.remove_annotations :model_dir => @model_dir, :is_rake => true - }).to include("Unable to deannotate #{@model_dir}/user.rb: oops") - end - - it "displays the full stack trace" do - expect(capturing(:stdout) { - AnnotateModels.remove_annotations :model_dir => @model_dir, :trace => true, :is_rake => true - }).to include("/user.rb:2:in `'") - end - - it "omits the full stack trace without --trace" do - expect(capturing(:stdout) { - AnnotateModels.remove_annotations :model_dir => @model_dir, :trace => false, :is_rake => true - }).not_to include("/user.rb:2:in `'") - end + before do + allow(AnnotateModels).to receive(:get_loaded_model).with('user').and_return(nil) + + write_model('user.rb', <<-EOS) + class User < ActiveRecord::Base + raise "oops" + end + EOS + end + + it 'displays an error message' do + expect(capturing(:stdout) do + AnnotateModels.remove_annotations model_dir: @model_dir, is_rake: true + end).to include("Unable to deannotate #{@model_dir}/user.rb: oops") + end + + it 'displays the full stack trace' do + expect(capturing(:stdout) do + AnnotateModels.remove_annotations model_dir: @model_dir, trace: true, is_rake: true + end).to include("/user.rb:2:in `'") + end + + it 'omits the full stack trace without --trace' do + expect(capturing(:stdout) do + AnnotateModels.remove_annotations model_dir: @model_dir, trace: false, is_rake: true + end).not_to include("/user.rb:2:in `'") + end end end describe '.annotate_model_file' do before do - class Foo < ActiveRecord::Base; end; + class Foo < ActiveRecord::Base; end allow(AnnotateModels).to receive(:get_model_class).with('foo.rb') { Foo } allow(Foo).to receive(:table_exists?) { false } end @@ -780,7 +1235,7 @@ class Foo < ActiveRecord::Base; end; after { Object.send :remove_const, 'Foo' } it 'skips attempt to annotate if no table exists for model' do - annotate_model_file = AnnotateModels.annotate_model_file([], 'foo.rb', nil, nil) + annotate_model_file = AnnotateModels.annotate_model_file([], 'foo.rb', nil, {}) expect(annotate_model_file).to eq nil end diff --git a/spec/annotate/annotate_routes_spec.rb b/spec/annotate/annotate_routes_spec.rb index 27f3ac351..037a4a2bf 100644 --- a/spec/annotate/annotate_routes_spec.rb +++ b/spec/annotate/annotate_routes_spec.rb @@ -2,118 +2,168 @@ require 'annotate/annotate_routes' 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." + ROUTE_FILE = 'config/routes.rb'.freeze + ANNOTATION_ADDED = "#{ROUTE_FILE} annotated.".freeze + ANNOTATION_REMOVED = "Removed annotations from #{ROUTE_FILE}.".freeze + FILE_UNCHANGED = "#{ROUTE_FILE} unchanged.".freeze - def mock_file(stubs={}) + def mock_file(stubs = {}) @mock_file ||= double(File, stubs) end - it "should check if routes.rb exists" do + it 'should check if routes.rb exists' do 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 end - describe "When adding" do + describe 'Annotate#example' do before(:each) do expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(true) - expect(AnnotateRoutes).to receive(:`).with("rake routes").and_return("") + + expect(File).to receive(:read).with(ROUTE_FILE).and_return("") + expect(AnnotateRoutes).to receive(:`).with('rake routes').and_return(' Prefix Verb URI Pattern Controller#Action + myaction1 GET /url1(.:format) mycontroller1#action + myaction2 POST /url2(.:format) mycontroller2#action + myaction3 DELETE|GET /url3(.:format) mycontroller3#action') + + expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED) + end + + it 'annotate normal' do + expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) + expect(@mock_file).to receive(:puts).with(" +# == Route Map +# +# Prefix Verb URI Pattern Controller#Action +# myaction1 GET /url1(.:format) mycontroller1#action +# myaction2 POST /url2(.:format) mycontroller2#action +# myaction3 DELETE|GET /url3(.:format) mycontroller3#action\n") + + AnnotateRoutes.do_annotations + end + + it 'annotate markdown' do + expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) + expect(@mock_file).to receive(:puts).with(" +# ## Route Map +# +# Prefix | Verb | URI Pattern | Controller#Action +# --------- | ---------- | --------------- | -------------------- +# myaction1 | GET | /url1(.:format) | mycontroller1#action +# myaction2 | POST | /url2(.:format) | mycontroller2#action +# myaction3 | DELETE-GET | /url3(.:format) | mycontroller3#action\n") + + AnnotateRoutes.do_annotations(format_markdown: true) end + end - it "should insert annotations if file does not contain annotations" 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(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 + it 'should insert annotations if file does not contain annotations and ignore routes' 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(ignore_routes: 'my_route') + end + + it 'should insert annotations if file does not contain annotations and position top' 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("# == Route Map\n#\n") + expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED) + + AnnotateRoutes.do_annotations(position_in_routes: 'top') + 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 - + describe 'When adding with older Rake versions' do before(:each) do 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(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file) + expect(AnnotateRoutes).to receive(:`).with('rake routes').and_return("(in /bad/line)\ngood line") + expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED) end - it "should annotate and add a newline!" do + it 'should annotate and add a newline!' do 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/) AnnotateRoutes.do_annotations 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(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/) AnnotateRoutes.do_annotations end - end - describe "When adding with newer Rake versions" do - + describe 'When adding with newer Rake versions' do before(:each) do 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(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file) + expect(AnnotateRoutes).to receive(:`).with('rake routes').and_return("another good line\ngood line") + expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_ADDED) end - - it "should annotate and add a newline!" do + it 'should annotate and add a newline!' do 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/) AnnotateRoutes.do_annotations 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(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/) AnnotateRoutes.do_annotations 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(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/) - AnnotateRoutes.do_annotations :timestamp => true + AnnotateRoutes.do_annotations timestamp: true end - end - describe "When removing" do - + describe 'When removing' do before(:each) do expect(File).to receive(:exists?).with(ROUTE_FILE).and_return(true) - expect(File).to receive(:open).with(ROUTE_FILE, "wb").and_yield(mock_file) + expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) expect(AnnotateRoutes).to receive(:puts).with(ANNOTATION_REMOVED) 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(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/) AnnotateRoutes.remove_annotations 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(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/) AnnotateRoutes.remove_annotations end - end - end diff --git a/spec/annotate_spec.rb b/spec/annotate_spec.rb index e1ce2dbc9..542ac63ba 100644 --- a/spec/annotate_spec.rb +++ b/spec/annotate_spec.rb @@ -1,9 +1,7 @@ require File.dirname(__FILE__) + '/spec_helper.rb' describe Annotate do - - it "should have a version" do + it 'should have a version' do expect(Annotate.version).to be_instance_of(String) end - end diff --git a/spec/fixtures/rails_32_schema.rb b/spec/fixtures/rails_32_schema.rb index 758d09c60..26ccdc90e 100644 --- a/spec/fixtures/rails_32_schema.rb +++ b/spec/fixtures/rails_32_schema.rb @@ -12,11 +12,9 @@ # It's strongly recommended to check this file into your version control system. ActiveRecord::Schema.define(:version => 20120816164927) do - create_table "tasks", :force => true do |t| t.string "content" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end - end diff --git a/spec/integration/common_validation.rb b/spec/integration/common_validation.rb index c941a7f4c..6145e851a 100644 --- a/spec/integration/common_validation.rb +++ b/spec/integration/common_validation.rb @@ -2,10 +2,7 @@ module Annotate module Validations module Common def self.test_commands - return %q{ - bin/annotate && - bin/annotate --routes - } + 'bin/annotate && bin/annotate --routes' end def self.verify_output(output) @@ -13,54 +10,52 @@ def self.verify_output(output) output.should =~ /Route file annotated./ end - def self.verify_files(which_files, test_rig, schema_annotation, routes_annotation, place_before=true) - check_task_model(test_rig, schema_annotation, place_before) if(which_files[:model]) - check_task_unittest(test_rig, schema_annotation, place_before) if(which_files[:test]) - check_task_fixture(test_rig, schema_annotation, place_before) if(which_files[:fixture]) - check_task_factory(test_rig, schema_annotation, place_before) if(which_files[:factory]) - check_routes(test_rig, routes_annotation, place_before) if(which_files[:routes]) + def self.verify_files(which_files, test_rig, schema_annotation, routes_annotation, place_before = true) + check_task_model(test_rig, schema_annotation, place_before) if which_files[:model] + check_task_unittest(test_rig, schema_annotation, place_before) if which_files[:test] + check_task_fixture(test_rig, schema_annotation, place_before) if which_files[:fixture] + check_task_factory(test_rig, schema_annotation, place_before) if which_files[:factory] + check_routes(test_rig, routes_annotation, place_before) if which_files[:routes] end - def self.check_task_model(test_rig, annotation, place_before=true) - model = apply_annotation(test_rig, "app/models/task.rb", annotation, place_before) - File.read("app/models/task.rb").should == model + def self.check_task_model(test_rig, annotation, place_before = true) + model = apply_annotation(test_rig, 'app/models/task.rb', annotation, place_before) + File.read('app/models/task.rb').should == model end - def self.check_routes(test_rig, annotation, place_before=true) - routes = apply_annotation(test_rig, "config/routes.rb", annotation, place_before) - File.read("config/routes.rb"). - sub(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/, 'YYYY-MM-DD HH:MM'). - should == routes + def self.check_routes(test_rig, annotation, place_before = true) + routes = apply_annotation(test_rig, 'config/routes.rb', annotation, place_before) + File.read('config/routes.rb') + .sub(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/, 'YYYY-MM-DD HH:MM') + .should == routes end - def self.check_task_unittest(test_rig, annotation, place_before=true) - unittest = apply_annotation(test_rig, "test/unit/task_test.rb", annotation, place_before) - File.read("test/unit/task_test.rb").should == unittest + def self.check_task_unittest(test_rig, annotation, place_before = true) + unittest = apply_annotation(test_rig, 'test/unit/task_test.rb', annotation, place_before) + File.read('test/unit/task_test.rb').should == unittest end def self.check_task_modeltest(test_rig, annotation, place_before=true) - unittest = apply_annotation(test_rig, "test/models/task_test.rb", annotation, place_before) - File.read("test/models/task_test.rb").should == unittest + unittest = apply_annotation(test_rig, 'test/models/task_test.rb', annotation, place_before) + File.read('test/models/task_test.rb').should == unittest end def self.check_task_factory(test_rig, annotation, place_before=true) - fixture = apply_annotation(test_rig, "test/factories/tasks.rb", annotation, place_before) - File.read("test/factories/tasks.rb").should == fixture + fixture = apply_annotation(test_rig, 'test/factories/tasks.rb', annotation, place_before) + File.read('test/factories/tasks.rb').should == fixture end - def self.check_task_fixture(test_rig, annotation, place_before=true) - fixture = apply_annotation(test_rig, "test/fixtures/tasks.yml", annotation, place_before) - File.read("test/fixtures/tasks.yml").should == fixture + def self.check_task_fixture(test_rig, annotation, place_before = true) + fixture = apply_annotation(test_rig, 'test/fixtures/tasks.yml', annotation, place_before) + File.read('test/fixtures/tasks.yml').should == fixture end - protected - - def self.apply_annotation(test_rig, fname, annotation, place_before=true) + def self.apply_annotation(test_rig, fname, annotation, place_before = true) corpus = File.read(File.join(test_rig, fname)) - if(place_before) - corpus = annotation + "\n" + corpus + if place_before + annotation + "\n" + corpus else - corpus = corpus + "\n" + annotation + corpus + "\n" + annotation end end end @@ -71,27 +66,28 @@ module Annotate module Validations class Base def self.test_commands - return Annotate::Validations::Common.test_commands + Annotate::Validations::Common.test_commands end def self.verify_output(output) - return Annotate::Validations::Common.verify_output(output) + Annotate::Validations::Common.verify_output(output) end def self.verify_files(test_rig) - return Annotate::Validations::Common.verify_files({ - :model => true, - :test => true, - :fixture => true, - :factory => false, - :routes => true - }, test_rig, self.schema_annotation, self.route_annotation, true) + Annotate::Validations::Common.verify_files( + { + model: true, + test: true, + fixture: true, + factory: false, + routes: true + }, test_rig, schema_annotation, route_annotation, true + ) end def self.foo require 'pry' require 'pry-coolline' - binding.pry end end end diff --git a/spec/integration/integration_spec.rb b/spec/integration/integration_spec.rb index d76b4277b..79f57493d 100644 --- a/spec/integration/integration_spec.rb +++ b/spec/integration/integration_spec.rb @@ -1,5 +1,5 @@ # Smoke test to assure basic functionality works on a variety of Rails versions. -$:.unshift(File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.dirname(__FILE__)) require 'spec_helper' require 'files' require 'wrong' @@ -7,7 +7,7 @@ include Files include Wrong::D -BASEDIR=File.expand_path(File.join(File.dirname(__FILE__), '..', '..')) +BASEDIR = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')) RVM_BIN = `which rvm`.chomp USING_RVM = (RVM_BIN != '') @@ -16,25 +16,24 @@ ENV['BUNDLE_GEMFILE'] = './Gemfile' describe "annotate inside Rails, using #{CURRENT_RUBY}" do - - here = File.expand_path('..', __FILE__) chosen_scenario = nil - if(!ENV['SCENARIO'].blank?) + unless ENV['SCENARIO'].blank? chosen_scenario = File.expand_path(ENV['SCENARIO']) - raise "Can't find specified scenario '#{chosen_scenario}'!" unless(File.directory?(chosen_scenario)) + raise "Can't find specified scenario '#{chosen_scenario}'!" unless File.directory?(chosen_scenario) end + Annotate::Integration::SCENARIOS.each do |test_rig, base_dir, test_name| - next if(chosen_scenario && chosen_scenario != test_rig) + next if chosen_scenario && chosen_scenario != test_rig it "works under #{test_name}" do - if(!USING_RVM) - skip "Must have RVM installed." + unless USING_RVM + skip 'Must have RVM installed.' next end # Don't proceed if the working copy is dirty! - expect(Annotate::Integration.is_clean?(test_rig)).to eq(true) + expect(Annotate::Integration.clean?(test_rig)).to eq(true) - skip "temporarily ignored until Travis can run them" + skip 'temporarily ignored until Travis can run them' Bundler.with_clean_env do dir base_dir do @@ -53,10 +52,10 @@ # By default, rvm_ruby_string isn't inherited over properly, so let's # make sure it's there so our .rvmrc will work. - ENV['rvm_ruby_string']=CURRENT_RUBY + ENV['rvm_ruby_string'] = CURRENT_RUBY - require "#{base_dir}" # Will get "#{base_dir}.rb"... - klass = "Annotate::Validations::#{base_dir.gsub('.', '_').classify}".constantize + require base_dir.to_s # Will get "#{base_dir}.rb"... + klass = "Annotate::Validations::#{base_dir.tr('.', '_').classify}".constantize Dir.chdir(temp_dir) do # bash is required by rvm diff --git a/spec/integration/rails_2.3_with_bundler.rb b/spec/integration/rails_2.3_with_bundler.rb index ffbd2fe9c..9c86871cf 100644 --- a/spec/integration/rails_2.3_with_bundler.rb +++ b/spec/integration/rails_2.3_with_bundler.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Rails23WithBundler < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,7 +18,7 @@ def self.schema_annotation end def self.route_annotation - return <<-RUBY + <<-RUBY # == Route Map (Updated YYYY-MM-DD HH:MM) # # tasks GET /tasks(.:format) {:controller=>"tasks", :action=>"index"} diff --git a/spec/integration/rails_3.2.2.rb b/spec/integration/rails_3.2.2.rb index 2a47e1ff6..e1087c403 100644 --- a/spec/integration/rails_3.2.2.rb +++ b/spec/integration/rails_3.2.2.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Rails322 < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,7 +18,7 @@ def self.schema_annotation end def self.route_annotation - return <<-RUBY + <<-RUBY # == Route Map (Updated YYYY-MM-DD HH:MM) # # tasks GET /tasks(.:format) tasks#index diff --git a/spec/integration/rails_3.2.8.rb b/spec/integration/rails_3.2.8.rb index d55c11c44..35b3137a5 100644 --- a/spec/integration/rails_3.2.8.rb +++ b/spec/integration/rails_3.2.8.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Rails328 < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,7 +18,7 @@ def self.schema_annotation end def self.route_annotation - return <<-RUBY + <<-RUBY # == Route Map (Updated YYYY-MM-DD HH:MM) # # tasks GET /tasks(.:format) tasks#index diff --git a/spec/integration/rails_3.2_autoloading_factory_girl.rb b/spec/integration/rails_3.2_autoloading_factory_girl.rb index 83aa022d7..e582f32fe 100644 --- a/spec/integration/rails_3.2_autoloading_factory_girl.rb +++ b/spec/integration/rails_3.2_autoloading_factory_girl.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Rails32AutoloadingFactoryGirl < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,7 +18,7 @@ def self.schema_annotation end def self.route_annotation - return <<-RUBY + <<-RUBY # == Route Map (Updated YYYY-MM-DD HH:MM) # # tasks GET /tasks(.:format) tasks#index @@ -33,13 +33,15 @@ def self.route_annotation end def self.verify_files(test_rig) - return Annotate::Validations::Common.verify_files({ - :model => true, - :test => true, - :fixture => false, - :factory => true, - :routes => true - }, test_rig, self.schema_annotation, self.route_annotation, true) + Annotate::Validations::Common.verify_files( + { + model: true, + test: true, + fixture: false, + factory: true, + routes: true + }, test_rig, schema_annotation, route_annotation, true + ) end end end diff --git a/spec/integration/rails_3.2_custom_inflections.rb b/spec/integration/rails_3.2_custom_inflections.rb index 253985ea4..d1cab92c5 100644 --- a/spec/integration/rails_3.2_custom_inflections.rb +++ b/spec/integration/rails_3.2_custom_inflections.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Rails32CustomInflection < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,7 +18,7 @@ def self.schema_annotation end def self.route_annotation - return <<-RUBY + <<-RUBY # == Route Map (Updated YYYY-MM-DD HH:MM) # # tasks GET /tasks(.:format) tasks#index diff --git a/spec/integration/rails_3.2_with_asset_pipeline.rb b/spec/integration/rails_3.2_with_asset_pipeline.rb index ecd8365aa..4d35010d4 100644 --- a/spec/integration/rails_3.2_with_asset_pipeline.rb +++ b/spec/integration/rails_3.2_with_asset_pipeline.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Rails32WithAssetPipeline < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,7 +18,7 @@ def self.schema_annotation end def self.route_annotation - return <<-RUBY + <<-RUBY # == Route Map (Updated YYYY-MM-DD HH:MM) # # tasks GET /tasks(.:format) tasks#index diff --git a/spec/integration/standalone.rb b/spec/integration/standalone.rb index 2b45442b7..14545304d 100644 --- a/spec/integration/standalone.rb +++ b/spec/integration/standalone.rb @@ -4,7 +4,7 @@ module Annotate module Validations class Standalone < Base def self.schema_annotation - return <<-RUBY + <<-RUBY # == Schema Information # # Table name: tasks @@ -18,9 +18,7 @@ def self.schema_annotation end def self.test_commands - return %q{ - bin/annotate --require ./config/init.rb - } + 'bin/annotate --require ./config/init.rb' end def self.verify_output(output) @@ -28,13 +26,15 @@ def self.verify_output(output) end def self.verify_files(test_rig) - return Annotate::Validations::Common.verify_files({ - :model => true, - :test => false, - :fixture => false, - :factory => false, - :routes => false - }, test_rig, self.schema_annotation, nil, true) + Annotate::Validations::Common.verify_files( + { + model: true, + test: false, + fixture: false, + factory: false, + routes: false + }, test_rig, schema_annotation, nil, true + ) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d15c2322..6362aeec4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,59 +1,79 @@ +require 'coveralls' +require 'codeclimate-test-reporter' +require 'simplecov' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( + [ + Coveralls::SimpleCov::Formatter, + SimpleCov::Formatter::HTMLFormatter, + CodeClimate::TestReporter::Formatter + ] +) + +SimpleCov.start + require 'rubygems' require 'bundler' Bundler.setup +require 'rake' require 'rspec' require 'wrong/adapters/rspec' -$:.unshift(File.join(File.dirname(__FILE__), '../lib')) -$:.unshift(File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../lib')) +$LOAD_PATH.unshift(File.dirname(__FILE__)) require 'active_support' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/class/subclasses' require 'active_support/core_ext/string/inflections' require 'annotate' +require 'byebug' module Annotate module Integration - ABSOLUTE_GEM_ROOT=File.expand_path('../../', __FILE__) + ABSOLUTE_GEM_ROOT = File.expand_path('../../', __FILE__) - CRUFT_PATTERNS=[ - "%SCENARIO%/bin/*", "%SCENARIO%/log/*", "%SCENARIO%/tmp/*", - "%SCENARIO%/.bundle" - ] - SCENARIO_HOME=File.join(File.dirname(__FILE__), 'integration') - SCENARIOS=Dir.glob("#{SCENARIO_HOME}/*"). - select { |candidate| File.directory?(candidate) }. - map do |test_rig| - base_dir = File.basename(test_rig) - [test_rig, base_dir, base_dir.titlecase] - end + CRUFT_PATTERNS = %w( + %SCENARIO%/bin/* + %SCENARIO%/log/* + %SCENARIO%/tmp/* + %SCENARIO%/.bundle + ).freeze + + SCENARIO_HOME = File.join(File.dirname(__FILE__), 'integration') + SCENARIOS = Dir.glob("#{SCENARIO_HOME}/*").select do |candidate| + File.directory?(candidate) + end.map do |test_rig| + base_dir = File.basename(test_rig) + [test_rig, base_dir, base_dir.titlecase] + end def self.nuke_cruft(test_rig) FileList[ - Annotate::Integration::CRUFT_PATTERNS. - map { |pattern| pattern.sub('%SCENARIO%', test_rig) } + Annotate::Integration::CRUFT_PATTERNS.map do |pattern| + pattern.sub('%SCENARIO%', test_rig) + end ].each do |fname| FileUtils.rm_rf(fname) end end def self.nuke_all_cruft - SCENARIOS.each do |test_rig, base_dir, test_name| + SCENARIOS.each do |test_rig, _base_dir, _test_name| nuke_cruft(test_rig) end end def self.empty_gemset(test_rig) Dir.chdir(test_rig) do - system(%q{ + system(' ( export SKIP_BUNDLER=1 source .rvmrc && rvm --force gemset empty ) 2>&1 - }) + ') end end @@ -65,8 +85,8 @@ def self.clear_untracked_files system("git clean -dfx #{SCENARIO_HOME}/*/") end - def self.is_clean?(test_rig) - return `git status --porcelain #{test_rig}/ | wc -l`.strip.to_i == 0 + def self.clean?(test_rig) + `git status --porcelain #{test_rig}/ | wc -l`.strip.to_i.zero? end end end