├── .dockerignore ├── .document ├── .github ├── ISSUE_TEMPLATE.md ├── release-drafter.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── release-drafter.yml │ └── release.yml ├── .gitignore ├── .overcommit.yml ├── .rbenv-gemsets ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .tool-versions ├── .yardopts ├── AUTHORS.md ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── RELEASE.md ├── Rakefile ├── annotate.gemspec ├── bin └── annotate ├── lib ├── annotate.rb ├── annotate │ ├── active_record_patch.rb │ ├── annotate_models.rb │ ├── annotate_models │ │ └── file_patterns.rb │ ├── annotate_routes.rb │ ├── annotate_routes │ │ ├── header_generator.rb │ │ └── helpers.rb │ ├── constants.rb │ ├── helpers.rb │ ├── parser.rb │ ├── tasks.rb │ └── version.rb ├── generators │ └── annotate │ │ ├── USAGE │ │ ├── install_generator.rb │ │ └── templates │ │ └── auto_annotate_models.rake └── tasks │ ├── annotate_models.rake │ ├── annotate_models_migrate.rake │ └── annotate_routes.rake ├── potato.md └── spec ├── lib ├── annotate │ ├── annotate_models │ │ └── file_patterns_spec.rb │ ├── annotate_models_spec.rb │ ├── annotate_routes_spec.rb │ ├── helpers_spec.rb │ └── parser_spec.rb ├── annotate_spec.rb └── tasks │ ├── annotate_models_migrate_spec.rb │ └── annotate_models_spec.rb └── spec_helper.rb /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | coverage 3 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | spec/**/*.rb 2 | lib/**/*.rb 3 | - 4 | README.md 5 | CHANGELOG.md 6 | TODO.md 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Describe your problem here. 2 | 3 | ## Commands 4 | 5 | ``` 6 | $ show your commands here. 7 | ``` 8 | 9 | ## Version 10 | 11 | - annotate version 12 | - rails version 13 | - ruby version 14 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What's Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - '*' 6 | 7 | push: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: ['2.7'] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup Ruby 24 | uses: ruby/setup-ruby@v1 25 | with: 26 | ruby-version: ${{ matrix.ruby }} 27 | bundler-cache: true 28 | 29 | - name: Run Tests 30 | run: bundle exec rspec 31 | 32 | - name: Rubocop 33 | run: bundle exec rubocop 34 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "develop" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "develop" ] 9 | schedule: 10 | - cron: '26 5 * * 4' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'ruby' ] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v3 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v2 33 | with: 34 | languages: ${{ matrix.language }} 35 | # If you wish to specify custom queries, you can do so here or in a config file. 36 | # By default, queries listed here will override any specified in a config file. 37 | # Prefix the list here with "+" to use these queries and those in the config file. 38 | 39 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 40 | # queries: security-extended,security-and-quality 41 | 42 | 43 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 44 | # If this step fails, then you should remove it and run the build manually (see below) 45 | - name: Autobuild 46 | uses: github/codeql-action/autobuild@v2 47 | 48 | # ℹ️ Command-line programs to run using the OS shell. 49 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 50 | 51 | # If the Autobuild fails above, remove it and uncomment the following three lines. 52 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 53 | 54 | # - run: | 55 | # echo "Run, Build Application using script" 56 | # ./location_of_script_within_repo/buildscript.sh 57 | 58 | - name: Perform CodeQL Analysis 59 | uses: github/codeql-action/analyze@v2 60 | with: 61 | category: "/language:${{matrix.language}}" 62 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Drafts your next Release notes as Pull Requests are merged into "develop" 13 | - uses: release-drafter/release-drafter@v5 14 | with: 15 | config-name: release-drafter.yml 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | branches: 7 | - 'release/*' 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v1 16 | 17 | - name: Setup Ruby 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | ruby-version: '2.6' 21 | 22 | - name: Bundle 23 | run: | 24 | gem update --system 25 | gem update bundler 26 | bundle install --jobs 4 --retry 3 27 | 28 | - name: Publish to RubyGems 29 | run: | 30 | mkdir -p $HOME/.gem 31 | touch $HOME/.gem/credentials 32 | chmod 0600 $HOME/.gem/credentials 33 | printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 34 | gem build *.gemspec 35 | gem push *.gem 36 | env: 37 | GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_AUTH_TOKEN }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .DS_Store 3 | .bundle 4 | .gems 5 | .rbenv-version 6 | .ruby-* 7 | /.idea/ 8 | /.rbx 9 | /.rvmrc 10 | /.yardoc/* 11 | /Gemfile.lock 12 | /coverage/* 13 | /dist 14 | /doc/* 15 | /pkg/* 16 | /spec/debug.log 17 | .byebug_history 18 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Use this file to configure the Overcommit hooks you wish to use. This will 2 | # extend the default configuration defined in: 3 | # https://github.com/brigade/overcommit/blob/master/config/default.yml 4 | # 5 | # At the topmost level of this YAML file is a key representing type of hook 6 | # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can 7 | # customize each hook, such as whether to only run it on certain files (via 8 | # `include`), whether to only display output if it fails (via `quiet`), etc. 9 | # 10 | # For a complete list of hooks, see: 11 | # https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook 12 | # 13 | # For a complete list of options that you can use to customize hooks, see: 14 | # https://github.com/brigade/overcommit#configuration 15 | # 16 | # Uncomment the following lines to make the configuration take effect. 17 | 18 | PreCommit: 19 | RuboCop: 20 | enabled: true 21 | on_warn: fail # Treat all warnings as failures 22 | 23 | PrePush: 24 | RSpec: 25 | enabled: true 26 | on_warn: fail # Treat all warnings as failures 27 | # 28 | # TrailingWhitespace: 29 | # enabled: true 30 | # exclude: 31 | # - '**/db/structure.sql' # Ignore trailing whitespace in generated files 32 | # 33 | #PostCheckout: 34 | # ALL: # Special hook name that customizes all hooks of this type 35 | # quiet: true # Change all post-checkout hooks to only display output on failure 36 | # 37 | # IndexTags: 38 | # enabled: true # Generate a tags file with `ctags` each time HEAD changes 39 | -------------------------------------------------------------------------------- /.rbenv-gemsets: -------------------------------------------------------------------------------- 1 | .gems 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - .rubocop_todo.yml 3 | 4 | require: 5 | - rubocop-rake 6 | - rubocop-rspec 7 | 8 | AllCops: 9 | Exclude: 10 | - 'vendor/**/*' 11 | - 'spec/fixtures/**/*' 12 | - 'tmp/**/*' 13 | - 'spec/integration/**/*' 14 | NewCops: enable 15 | 16 | Metrics/BlockLength: 17 | Exclude: 18 | - 'spec/**/*.rb' 19 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2022-06-14 03:17:11 UTC using RuboCop version 1.12.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Cop supports --auto-correct. 11 | # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. 12 | # Include: **/*.gemspec 13 | Gemspec/OrderedDependencies: 14 | Exclude: 15 | - 'annotate.gemspec' 16 | 17 | # Offense count: 1 18 | # Configuration parameters: Include. 19 | # Include: **/*.gemspec 20 | Gemspec/RequiredRubyVersion: 21 | Exclude: 22 | - 'annotate.gemspec' 23 | 24 | # Offense count: 1 25 | # Cop supports --auto-correct. 26 | # Configuration parameters: EnforcedStyleAlignWith. 27 | # SupportedStylesAlignWith: either, start_of_block, start_of_line 28 | Layout/BlockAlignment: 29 | Exclude: 30 | - 'lib/annotate/annotate_models.rb' 31 | 32 | # Offense count: 9 33 | # Cop supports --auto-correct. 34 | Layout/EmptyLineAfterGuardClause: 35 | Exclude: 36 | - 'Rakefile' 37 | - 'lib/annotate.rb' 38 | - 'lib/annotate/annotate_models.rb' 39 | 40 | # Offense count: 2 41 | # Cop supports --auto-correct. 42 | Layout/EmptyLineAfterMagicComment: 43 | Exclude: 44 | - 'annotate.gemspec' 45 | - 'spec/lib/annotate/annotate_models_spec.rb' 46 | 47 | # Offense count: 3 48 | # Cop supports --auto-correct. 49 | # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. 50 | Layout/ExtraSpacing: 51 | Exclude: 52 | - 'Guardfile' 53 | - 'lib/annotate/annotate_models.rb' 54 | - 'lib/tasks/annotate_routes.rake' 55 | 56 | # Offense count: 14 57 | # Cop supports --auto-correct. 58 | # Configuration parameters: IndentationWidth. 59 | # SupportedStyles: special_inside_parentheses, consistent, align_brackets 60 | Layout/FirstArrayElementIndentation: 61 | EnforcedStyle: consistent 62 | 63 | # Offense count: 65 64 | # Cop supports --auto-correct. 65 | # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. 66 | # SupportedHashRocketStyles: key, separator, table 67 | # SupportedColonStyles: key, separator, table 68 | # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit 69 | Layout/HashAlignment: 70 | Exclude: 71 | - 'lib/generators/annotate/templates/auto_annotate_models.rake' 72 | - 'spec/lib/annotate/annotate_models_spec.rb' 73 | 74 | # Offense count: 5 75 | # Cop supports --auto-correct. 76 | # Configuration parameters: EnforcedStyle, IndentationWidth. 77 | # SupportedStyles: aligned, indented 78 | Layout/MultilineOperationIndentation: 79 | Exclude: 80 | - 'lib/annotate/annotate_models.rb' 81 | 82 | # Offense count: 1 83 | # Cop supports --auto-correct. 84 | # Configuration parameters: EnforcedStyle. 85 | # SupportedStyles: space, no_space 86 | Layout/SpaceAroundEqualsInParameterDefault: 87 | Exclude: 88 | - 'lib/annotate/annotate_routes.rb' 89 | 90 | # Offense count: 4 91 | # Cop supports --auto-correct. 92 | # Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. 93 | # SupportedStylesForExponentOperator: space, no_space 94 | Layout/SpaceAroundOperators: 95 | Exclude: 96 | - 'lib/annotate/annotate_models.rb' 97 | - 'lib/tasks/annotate_routes.rake' 98 | 99 | # Offense count: 1 100 | # Cop supports --auto-correct. 101 | # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. 102 | # SupportedStyles: space, no_space 103 | # SupportedStylesForEmptyBraces: space, no_space 104 | Layout/SpaceBeforeBlockBraces: 105 | Exclude: 106 | - 'lib/annotate/annotate_models.rb' 107 | 108 | # Offense count: 1 109 | # Cop supports --auto-correct. 110 | Layout/SpaceBeforeComment: 111 | Exclude: 112 | - 'lib/annotate/annotate_models.rb' 113 | 114 | # Offense count: 4 115 | # Cop supports --auto-correct. 116 | # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. 117 | # SupportedStyles: space, no_space 118 | # SupportedStylesForEmptyBraces: space, no_space 119 | Layout/SpaceInsideBlockBraces: 120 | Exclude: 121 | - 'lib/annotate/annotate_models.rb' 122 | 123 | # Offense count: 4 124 | # Cop supports --auto-correct. 125 | # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. 126 | # SupportedStyles: space, no_space, compact 127 | # SupportedStylesForEmptyBraces: space, no_space 128 | Layout/SpaceInsideHashLiteralBraces: 129 | Exclude: 130 | - 'lib/tasks/annotate_models.rake' 131 | 132 | # Offense count: 4 133 | # Cop supports --auto-correct. 134 | # Configuration parameters: EnforcedStyle. 135 | # SupportedStyles: space, no_space 136 | Layout/SpaceInsideParens: 137 | Exclude: 138 | - 'lib/annotate/annotate_models.rb' 139 | 140 | # Offense count: 4 141 | # Cop supports --auto-correct. 142 | # Configuration parameters: EnforcedStyle. 143 | # SupportedStyles: space, no_space 144 | Layout/SpaceInsideStringInterpolation: 145 | Exclude: 146 | - 'lib/annotate/annotate_models.rb' 147 | 148 | # Offense count: 2 149 | # Cop supports --auto-correct. 150 | # Configuration parameters: AllowInHeredoc. 151 | Layout/TrailingWhitespace: 152 | Exclude: 153 | - 'spec/lib/annotate/annotate_routes_spec.rb' 154 | 155 | # Offense count: 2 156 | # Configuration parameters: AllowSafeAssignment. 157 | Lint/AssignmentInCondition: 158 | Exclude: 159 | - 'lib/annotate/annotate_models.rb' 160 | 161 | # Offense count: 7 162 | # Configuration parameters: AllowedMethods. 163 | # AllowedMethods: enums 164 | Lint/ConstantDefinitionInBlock: 165 | Exclude: 166 | - 'spec/lib/annotate/annotate_models_spec.rb' 167 | - 'spec/lib/annotate/annotate_routes_spec.rb' 168 | 169 | # Offense count: 1 170 | # Cop supports --auto-correct. 171 | # Configuration parameters: EnforcedStyle. 172 | # SupportedStyles: runtime_error, standard_error 173 | Lint/InheritException: 174 | Exclude: 175 | - 'lib/annotate/annotate_models.rb' 176 | 177 | # Offense count: 1 178 | # Configuration parameters: MaximumRangeSize. 179 | Lint/MissingCopEnableDirective: 180 | Exclude: 181 | - 'lib/annotate/annotate_models.rb' 182 | 183 | # Offense count: 2 184 | Lint/RescueException: 185 | Exclude: 186 | - 'Rakefile' 187 | 188 | # Offense count: 1 189 | Lint/ShadowingOuterLocalVariable: 190 | Exclude: 191 | - 'Rakefile' 192 | 193 | # Offense count: 1 194 | # Configuration parameters: AllowComments, AllowNil. 195 | Lint/SuppressedException: 196 | Exclude: 197 | - 'bin/annotate' 198 | 199 | # Offense count: 3 200 | # Cop supports --auto-correct. 201 | # Configuration parameters: EnforcedStyle. 202 | # SupportedStyles: strict, consistent 203 | Lint/SymbolConversion: 204 | Exclude: 205 | - 'lib/annotate/annotate_models.rb' 206 | - 'spec/lib/annotate/annotate_models_spec.rb' 207 | 208 | # Offense count: 20 209 | # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. 210 | Metrics/AbcSize: 211 | Max: 155 212 | 213 | # Offense count: 7 214 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. 215 | # IgnoredMethods: refine 216 | Metrics/BlockLength: 217 | Max: 53 218 | 219 | # Offense count: 1 220 | # Configuration parameters: CountBlocks. 221 | Metrics/BlockNesting: 222 | Max: 4 223 | 224 | # Offense count: 16 225 | # Configuration parameters: IgnoredMethods. 226 | Metrics/CyclomaticComplexity: 227 | Max: 30 228 | 229 | # Offense count: 31 230 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. 231 | Metrics/MethodLength: 232 | Max: 40 233 | 234 | # Offense count: 15 235 | # Configuration parameters: IgnoredMethods. 236 | Metrics/PerceivedComplexity: 237 | Max: 33 238 | 239 | # Offense count: 1 240 | Naming/AccessorMethodName: 241 | Exclude: 242 | - 'lib/annotate.rb' 243 | 244 | # Offense count: 105 245 | # Configuration parameters: ForbiddenDelimiters. 246 | # ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) 247 | Naming/HeredocDelimiterNaming: 248 | Exclude: 249 | - 'spec/lib/annotate/annotate_models_spec.rb' 250 | - 'spec/lib/annotate/annotate_routes_spec.rb' 251 | 252 | # Offense count: 1 253 | # Configuration parameters: EnforcedStyleForLeadingUnderscores. 254 | # SupportedStylesForLeadingUnderscores: disallowed, required, optional 255 | Naming/MemoizedInstanceVariableName: 256 | Exclude: 257 | - 'lib/annotate/annotate_routes.rb' 258 | 259 | # Offense count: 1 260 | # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. 261 | # AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to 262 | Naming/MethodParameterName: 263 | Exclude: 264 | - 'Rakefile' 265 | 266 | # Offense count: 13 267 | # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. 268 | # SupportedStyles: snake_case, normalcase, non_integer 269 | # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 270 | Naming/VariableNumber: 271 | Exclude: 272 | - 'spec/lib/annotate/annotate_models_spec.rb' 273 | - 'spec/lib/annotate/helpers_spec.rb' 274 | 275 | # Offense count: 1 276 | RSpec/BeforeAfterAll: 277 | Exclude: 278 | - 'spec/spec_helper.rb' 279 | - 'spec/rails_helper.rb' 280 | - 'spec/support/**/*.rb' 281 | - 'spec/lib/annotate/annotate_models_spec.rb' 282 | 283 | # Offense count: 46 284 | # Configuration parameters: Prefixes. 285 | # Prefixes: when, with, without 286 | RSpec/ContextWording: 287 | Exclude: 288 | - 'spec/lib/annotate/annotate_models_spec.rb' 289 | - 'spec/lib/annotate/annotate_routes_spec.rb' 290 | - 'spec/lib/annotate/parser_spec.rb' 291 | 292 | # Offense count: 1 293 | # Configuration parameters: IgnoredMetadata. 294 | RSpec/DescribeClass: 295 | Exclude: 296 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 297 | 298 | # Offense count: 149 299 | # Cop supports --auto-correct. 300 | # Configuration parameters: SkipBlocks, EnforcedStyle. 301 | # SupportedStyles: described_class, explicit 302 | RSpec/DescribedClass: 303 | Exclude: 304 | - 'spec/lib/annotate/annotate_models/file_patterns_spec.rb' 305 | - 'spec/lib/annotate/annotate_models_spec.rb' 306 | - 'spec/lib/annotate/annotate_routes_spec.rb' 307 | - 'spec/lib/annotate/parser_spec.rb' 308 | - 'spec/lib/annotate_spec.rb' 309 | 310 | # Offense count: 32 311 | # Cop supports --auto-correct. 312 | RSpec/EmptyLineAfterFinalLet: 313 | Exclude: 314 | - 'spec/lib/annotate/annotate_models_spec.rb' 315 | - 'spec/lib/annotate/helpers_spec.rb' 316 | - 'spec/lib/annotate/parser_spec.rb' 317 | 318 | # Offense count: 1 319 | # Cop supports --auto-correct. 320 | RSpec/EmptyLineAfterSubject: 321 | Exclude: 322 | - 'spec/lib/annotate/helpers_spec.rb' 323 | 324 | # Offense count: 14 325 | # Configuration parameters: Max. 326 | RSpec/ExampleLength: 327 | Exclude: 328 | - 'spec/lib/annotate/annotate_models/file_patterns_spec.rb' 329 | - 'spec/lib/annotate/annotate_models_spec.rb' 330 | - 'spec/lib/annotate/parser_spec.rb' 331 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 332 | 333 | # Offense count: 22 334 | # Cop supports --auto-correct. 335 | # Configuration parameters: CustomTransform, IgnoredWords. 336 | RSpec/ExampleWording: 337 | Exclude: 338 | - 'spec/lib/annotate/annotate_models_spec.rb' 339 | - 'spec/lib/annotate/annotate_routes_spec.rb' 340 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 341 | 342 | # Offense count: 9 343 | RSpec/ExpectInHook: 344 | Exclude: 345 | - 'spec/lib/annotate/annotate_models_spec.rb' 346 | - 'spec/lib/annotate/annotate_routes_spec.rb' 347 | 348 | # Offense count: 10 349 | # Cop supports --auto-correct. 350 | # Configuration parameters: EnforcedStyle. 351 | # SupportedStyles: implicit, each, example 352 | RSpec/HookArgument: 353 | Exclude: 354 | - 'spec/lib/annotate/annotate_models_spec.rb' 355 | - 'spec/lib/annotate/annotate_routes_spec.rb' 356 | - 'spec/lib/annotate/parser_spec.rb' 357 | 358 | # Offense count: 1 359 | # Cop supports --auto-correct. 360 | RSpec/HooksBeforeExamples: 361 | Exclude: 362 | - 'spec/lib/annotate/annotate_models_spec.rb' 363 | 364 | # Offense count: 78 365 | # Cop supports --auto-correct. 366 | # Configuration parameters: EnforcedStyle. 367 | # SupportedStyles: single_line_only, single_statement_only, disallow 368 | RSpec/ImplicitSubject: 369 | Exclude: 370 | - 'spec/lib/annotate/annotate_models/file_patterns_spec.rb' 371 | - 'spec/lib/annotate/annotate_models_spec.rb' 372 | - 'spec/lib/annotate/helpers_spec.rb' 373 | 374 | # Offense count: 51 375 | # Configuration parameters: AssignmentOnly. 376 | RSpec/InstanceVariable: 377 | Exclude: 378 | - 'spec/lib/annotate/annotate_models_spec.rb' 379 | 380 | # Offense count: 4 381 | # Cop supports --auto-correct. 382 | RSpec/LeadingSubject: 383 | Exclude: 384 | - 'spec/lib/annotate/annotate_models_spec.rb' 385 | 386 | # Offense count: 9 387 | RSpec/LeakyConstantDeclaration: 388 | Exclude: 389 | - 'spec/lib/annotate/annotate_models_spec.rb' 390 | - 'spec/lib/annotate/annotate_routes_spec.rb' 391 | 392 | # Offense count: 108 393 | # Configuration parameters: EnforcedStyle. 394 | # SupportedStyles: have_received, receive 395 | RSpec/MessageSpies: 396 | Exclude: 397 | - 'spec/lib/annotate/annotate_models_spec.rb' 398 | - 'spec/lib/annotate/annotate_routes_spec.rb' 399 | - 'spec/lib/annotate/helpers_spec.rb' 400 | - 'spec/lib/annotate/parser_spec.rb' 401 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 402 | 403 | # Offense count: 36 404 | RSpec/MultipleExpectations: 405 | Max: 4 406 | 407 | # Offense count: 66 408 | # Configuration parameters: AllowSubject. 409 | RSpec/MultipleMemoizedHelpers: 410 | Max: 9 411 | 412 | # Offense count: 6 413 | # Configuration parameters: IgnoreSharedExamples. 414 | RSpec/NamedSubject: 415 | Exclude: 416 | - 'spec/lib/annotate/annotate_models_spec.rb' 417 | - 'spec/lib/annotate/helpers_spec.rb' 418 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 419 | 420 | # Offense count: 140 421 | RSpec/NestedGroups: 422 | Max: 9 423 | 424 | # Offense count: 2 425 | # Cop supports --auto-correct. 426 | # Configuration parameters: EnforcedStyle. 427 | # SupportedStyles: not_to, to_not 428 | RSpec/NotToNot: 429 | Exclude: 430 | - 'spec/lib/annotate/annotate_models_spec.rb' 431 | - 'spec/lib/annotate/helpers_spec.rb' 432 | 433 | # Offense count: 5 434 | RSpec/RepeatedExampleGroupBody: 435 | Exclude: 436 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 437 | 438 | # Offense count: 2 439 | RSpec/RepeatedExampleGroupDescription: 440 | Exclude: 441 | - 'spec/lib/annotate/annotate_models_spec.rb' 442 | 443 | # Offense count: 3 444 | # Cop supports --auto-correct. 445 | # Configuration parameters: EnforcedStyle. 446 | # SupportedStyles: and_return, block 447 | RSpec/ReturnFromStub: 448 | Exclude: 449 | - 'spec/lib/annotate/annotate_models_spec.rb' 450 | 451 | # Offense count: 7 452 | # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. 453 | RSpec/VerifiedDoubles: 454 | Exclude: 455 | - 'spec/lib/annotate/annotate_models_spec.rb' 456 | - 'spec/lib/annotate/annotate_routes_spec.rb' 457 | 458 | # Offense count: 16 459 | # Cop supports --auto-correct. 460 | Rake/Desc: 461 | Exclude: 462 | - 'Rakefile' 463 | - 'lib/generators/annotate/templates/auto_annotate_models.rake' 464 | - 'lib/tasks/annotate_models.rake' 465 | - 'lib/tasks/annotate_routes.rake' 466 | 467 | # Offense count: 6 468 | Rake/DuplicateTask: 469 | Exclude: 470 | - 'Rakefile' 471 | - 'lib/tasks/annotate_models.rake' 472 | - 'lib/tasks/annotate_routes.rake' 473 | 474 | # Offense count: 3 475 | # Cop supports --auto-correct. 476 | # Configuration parameters: EnforcedStyle. 477 | # SupportedStyles: separated, grouped 478 | Style/AccessorGrouping: 479 | Exclude: 480 | - 'lib/annotate/annotate_models.rb' 481 | 482 | # Offense count: 1 483 | # Cop supports --auto-correct. 484 | # Configuration parameters: AllowOnConstant. 485 | Style/CaseEquality: 486 | Exclude: 487 | - 'lib/annotate/annotate_models.rb' 488 | 489 | # Offense count: 1 490 | # Cop supports --auto-correct. 491 | Style/CaseLikeIf: 492 | Exclude: 493 | - 'lib/annotate/annotate_routes.rb' 494 | 495 | # Offense count: 2 496 | Style/ClassVars: 497 | Exclude: 498 | - 'lib/tasks/annotate_models_migrate.rake' 499 | 500 | # Offense count: 1 501 | # Cop supports --auto-correct. 502 | # Configuration parameters: Keywords. 503 | # Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE 504 | Style/CommentAnnotation: 505 | Exclude: 506 | - 'lib/annotate/annotate_models.rb' 507 | 508 | # Offense count: 1 509 | # Cop supports --auto-correct. 510 | Style/Dir: 511 | Exclude: 512 | - 'bin/annotate' 513 | 514 | # Offense count: 10 515 | # Configuration parameters: AllowedConstants. 516 | Style/Documentation: 517 | Exclude: 518 | - 'spec/**/*' 519 | - 'test/**/*' 520 | - 'lib/annotate.rb' 521 | - 'lib/annotate/active_record_patch.rb' 522 | - 'lib/annotate/annotate_models.rb' 523 | - 'lib/annotate/annotate_routes.rb' 524 | - 'lib/annotate/annotate_routes/header_generator.rb' 525 | - 'lib/annotate/annotate_routes/helpers.rb' 526 | - 'lib/annotate/version.rb' 527 | - 'lib/generators/annotate/install_generator.rb' 528 | - 'lib/tasks/annotate_models_migrate.rake' 529 | 530 | # Offense count: 2 531 | # Cop supports --auto-correct. 532 | Style/Encoding: 533 | Exclude: 534 | - 'annotate.gemspec' 535 | - 'spec/lib/annotate/annotate_models_spec.rb' 536 | 537 | # Offense count: 1 538 | # Cop supports --auto-correct. 539 | Style/ExpandPathArguments: 540 | Exclude: 541 | - 'annotate.gemspec' 542 | 543 | # Offense count: 9 544 | # Cop supports --auto-correct. 545 | # Configuration parameters: EnforcedStyle. 546 | # SupportedStyles: format, sprintf, percent 547 | Style/FormatString: 548 | Exclude: 549 | - 'lib/annotate/annotate_models.rb' 550 | 551 | # Offense count: 12 552 | # Configuration parameters: MaxUnannotatedPlaceholdersAllowed, IgnoredMethods. 553 | # SupportedStyles: annotated, template, unannotated 554 | Style/FormatStringToken: 555 | EnforcedStyle: unannotated 556 | 557 | # Offense count: 30 558 | # Cop supports --auto-correct. 559 | # Configuration parameters: EnforcedStyle. 560 | # SupportedStyles: always, always_true, never 561 | Style/FrozenStringLiteralComment: 562 | Enabled: false 563 | 564 | # Offense count: 1 565 | # Configuration parameters: MinBodyLength. 566 | Style/GuardClause: 567 | Exclude: 568 | - 'lib/tasks/annotate_models_migrate.rake' 569 | 570 | # Offense count: 1 571 | # Cop supports --auto-correct. 572 | # Configuration parameters: AllowSplatArgument. 573 | Style/HashConversion: 574 | Exclude: 575 | - 'spec/lib/annotate/annotate_models_spec.rb' 576 | 577 | # Offense count: 3 578 | # Cop supports --auto-correct. 579 | # Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. 580 | # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys 581 | Style/HashSyntax: 582 | Exclude: 583 | - 'lib/tasks/annotate_routes.rake' 584 | - 'spec/lib/annotate/annotate_models_spec.rb' 585 | 586 | # Offense count: 7 587 | # Cop supports --auto-correct. 588 | Style/IfUnlessModifier: 589 | Exclude: 590 | - 'Rakefile' 591 | - 'bin/annotate' 592 | - 'lib/annotate/annotate_models.rb' 593 | 594 | # Offense count: 1 595 | # Cop supports --auto-correct. 596 | # Configuration parameters: InverseMethods, InverseBlocks. 597 | Style/InverseMethods: 598 | Exclude: 599 | - 'Rakefile' 600 | 601 | # Offense count: 1 602 | Style/MissingRespondToMissing: 603 | Exclude: 604 | - 'lib/annotate/active_record_patch.rb' 605 | 606 | # Offense count: 1 607 | Style/MixinUsage: 608 | Exclude: 609 | - 'Rakefile' 610 | 611 | # Offense count: 2 612 | Style/MultilineBlockChain: 613 | Exclude: 614 | - 'Rakefile' 615 | - 'lib/annotate/annotate_models.rb' 616 | 617 | # Offense count: 2 618 | # Cop supports --auto-correct. 619 | Style/MultilineIfModifier: 620 | Exclude: 621 | - 'spec/lib/annotate/annotate_models_spec.rb' 622 | - 'spec/lib/annotate/annotate_routes_spec.rb' 623 | 624 | # Offense count: 2 625 | # Cop supports --auto-correct. 626 | Style/NegatedIfElseCondition: 627 | Exclude: 628 | - 'lib/annotate.rb' 629 | 630 | # Offense count: 1 631 | # Cop supports --auto-correct. 632 | # Configuration parameters: AllowedMethods. 633 | # AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with 634 | Style/NestedParenthesizedCalls: 635 | Exclude: 636 | - 'bin/annotate' 637 | 638 | # Offense count: 3 639 | # Cop supports --auto-correct. 640 | # Configuration parameters: EnforcedStyle, IgnoredMethods. 641 | # SupportedStyles: predicate, comparison 642 | Style/NumericPredicate: 643 | Exclude: 644 | - 'spec/**/*' 645 | - 'lib/annotate.rb' 646 | - 'lib/annotate/annotate_models.rb' 647 | 648 | # Offense count: 13 649 | # Cop supports --auto-correct. 650 | # Configuration parameters: PreferredDelimiters. 651 | Style/PercentLiteralDelimiters: 652 | Exclude: 653 | - 'annotate.gemspec' 654 | - 'lib/annotate/annotate_models.rb' 655 | - 'lib/annotate/annotate_routes.rb' 656 | - 'lib/tasks/annotate_models_migrate.rake' 657 | - 'spec/lib/annotate/annotate_models_spec.rb' 658 | - 'spec/lib/tasks/annotate_models_migrate_spec.rb' 659 | 660 | # Offense count: 1 661 | # Cop supports --auto-correct. 662 | # Configuration parameters: EnforcedStyle, AllowedCompactTypes. 663 | # SupportedStyles: compact, exploded 664 | Style/RaiseArgs: 665 | Exclude: 666 | - 'lib/annotate/annotate_models.rb' 667 | 668 | # Offense count: 2 669 | # Cop supports --auto-correct. 670 | # Configuration parameters: Methods. 671 | Style/RedundantArgument: 672 | Exclude: 673 | - 'lib/annotate/annotate_routes/header_generator.rb' 674 | 675 | # Offense count: 3 676 | # Cop supports --auto-correct. 677 | Style/RedundantBegin: 678 | Exclude: 679 | - 'lib/annotate/annotate_models.rb' 680 | - 'spec/lib/annotate/annotate_models_spec.rb' 681 | 682 | # Offense count: 1 683 | # Cop supports --auto-correct. 684 | Style/RedundantParentheses: 685 | Exclude: 686 | - 'lib/annotate/annotate_models.rb' 687 | 688 | # Offense count: 2 689 | # Cop supports --auto-correct. 690 | Style/RedundantPercentQ: 691 | Exclude: 692 | - 'annotate.gemspec' 693 | 694 | # Offense count: 3 695 | # Cop supports --auto-correct. 696 | Style/RedundantRegexpCharacterClass: 697 | Exclude: 698 | - 'lib/annotate/annotate_models.rb' 699 | 700 | # Offense count: 3 701 | # Cop supports --auto-correct. 702 | Style/RedundantRegexpEscape: 703 | Exclude: 704 | - 'lib/annotate/annotate_models.rb' 705 | - 'lib/annotate/annotate_routes/header_generator.rb' 706 | 707 | # Offense count: 1 708 | # Cop supports --auto-correct. 709 | # Configuration parameters: AllowMultipleReturnValues. 710 | Style/RedundantReturn: 711 | Exclude: 712 | - 'lib/annotate/annotate_routes/helpers.rb' 713 | 714 | # Offense count: 2 715 | # Cop supports --auto-correct. 716 | Style/RedundantSelf: 717 | Exclude: 718 | - 'lib/tasks/annotate_models_migrate.rake' 719 | 720 | # Offense count: 12 721 | # Cop supports --auto-correct. 722 | # Configuration parameters: EnforcedStyle, AllowInnerSlashes. 723 | # SupportedStyles: slashes, percent_r, mixed 724 | Style/RegexpLiteral: 725 | Exclude: 726 | - 'Rakefile' 727 | - 'lib/annotate/annotate_models.rb' 728 | 729 | # Offense count: 1 730 | # Cop supports --auto-correct. 731 | Style/RescueModifier: 732 | Exclude: 733 | - 'lib/annotate/annotate_models.rb' 734 | 735 | # Offense count: 1 736 | # Cop supports --auto-correct. 737 | # Configuration parameters: EnforcedStyle. 738 | # SupportedStyles: implicit, explicit 739 | Style/RescueStandardError: 740 | Exclude: 741 | - 'lib/annotate.rb' 742 | 743 | # Offense count: 2 744 | # Cop supports --auto-correct. 745 | # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. 746 | # AllowedMethods: present?, blank?, presence, try, try! 747 | Style/SafeNavigation: 748 | Exclude: 749 | - 'lib/annotate/annotate_models.rb' 750 | 751 | # Offense count: 3 752 | # Cop supports --auto-correct. 753 | Style/SlicingWithRange: 754 | Exclude: 755 | - 'lib/annotate/annotate_models.rb' 756 | - 'lib/annotate/annotate_routes/header_generator.rb' 757 | 758 | # Offense count: 15 759 | # Cop supports --auto-correct. 760 | Style/StderrPuts: 761 | Exclude: 762 | - 'Rakefile' 763 | - 'lib/annotate.rb' 764 | - 'lib/annotate/annotate_models.rb' 765 | 766 | # Offense count: 13 767 | # Cop supports --auto-correct. 768 | Style/StringConcatenation: 769 | Exclude: 770 | - 'lib/annotate/annotate_models.rb' 771 | 772 | # Offense count: 57 773 | # Cop supports --auto-correct. 774 | # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. 775 | # SupportedStyles: single_quotes, double_quotes 776 | Style/StringLiterals: 777 | Exclude: 778 | - 'annotate.gemspec' 779 | - 'lib/annotate/annotate_models.rb' 780 | - 'lib/annotate/parser.rb' 781 | - 'lib/tasks/annotate_models_migrate.rake' 782 | - 'lib/tasks/annotate_routes.rake' 783 | - 'spec/lib/annotate/annotate_models_spec.rb' 784 | - 'spec/lib/annotate/parser_spec.rb' 785 | 786 | # Offense count: 2 787 | # Cop supports --auto-correct. 788 | # Configuration parameters: EnforcedStyle. 789 | # SupportedStyles: single_quotes, double_quotes 790 | Style/StringLiteralsInInterpolation: 791 | Exclude: 792 | - 'lib/annotate/annotate_models.rb' 793 | 794 | # Offense count: 8 795 | # Cop supports --auto-correct. 796 | # Configuration parameters: MinSize. 797 | # SupportedStyles: percent, brackets 798 | Style/SymbolArray: 799 | EnforcedStyle: brackets 800 | 801 | # Offense count: 1 802 | # Cop supports --auto-correct. 803 | Style/SymbolLiteral: 804 | Exclude: 805 | - 'spec/lib/annotate/annotate_models_spec.rb' 806 | 807 | # Offense count: 3 808 | # Cop supports --auto-correct. 809 | # Configuration parameters: EnforcedStyleForMultiline. 810 | # SupportedStylesForMultiline: comma, consistent_comma, no_comma 811 | Style/TrailingCommaInArrayLiteral: 812 | Exclude: 813 | - 'spec/lib/annotate/annotate_models_spec.rb' 814 | 815 | # Offense count: 52 816 | # Cop supports --auto-correct. 817 | # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 818 | # URISchemes: http, https 819 | Layout/LineLength: 820 | Max: 264 821 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 2.7.3 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --no-private 2 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | ## Authors 2 | 3 | - Original code by: Dave Thomas -- Pragmatic Programmers, LLC <http://agilewebdevelopment.com/plugins/annotate_models> 4 | - Overhauled by: Alex Chaffee <http://alexch.github.com> alex@stinky.com 5 | - Gemmed by: Cuong Tran <http://github.com/ctran> ctran@pragmaquest.com 6 | - Maintained by: Alex Chaffee and Cuong Tran 7 | - Homepage: http://github.com/ctran/annotate_models 8 | 9 | ### With help from: 10 | 11 | - Jack Danger - http://github.com/JackDanger 12 | - Michael Bumann - http://github.com/bumi 13 | - Henrik Nyh - http://github.com/henrik 14 | - Marcos Piccinini - http://github.com/nofxx 15 | - Neal Clark - http://github.com/nclark 16 | - Jacqui Maher - http://github.com/jacqui 17 | - Nick Plante - http://github.com/zapnap - http://blog.zerosum.org 18 | - Pedro Visintin - http://github.com/peterpunk - http://www.pedrovisintin.com 19 | - Bob Potter - http://github.com/bpot 20 | - Gavin Montague - http://github.com/govan 21 | - Alexander Semyonov - http://github.com/rotuka 22 | - Nathan Brazil - http://github.com/bitaxis 23 | - Ian Duggan http://github.com/ijcd 24 | - Jon Frisby http://github.com/mrjoy 25 | - Tsutomu Kuroda 26 | - Kevin Moore 27 | - Philip Hallstrom 28 | - Brent Greeff 29 | - Paul Alexander 30 | - Dmitry Lihachev 31 | - qichunren 32 | - Guillermo Guerrero - http://github.com/ryanfox1985 33 | 34 | and many others that I may have forgotten to add. 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Please see https://github.com/ctran/annotate_models/releases for changes between releases. 2 | 3 | ## 3.1.1 4 | Changes 5 | - Bump required ruby version to >= 2.4 [#772](https://github.com/ctran/annotate_models/pull/772) 6 | - [Revert #677] Fix column default annotations [#768](https://github.com/ctran/annotate_models/pull/768) 7 | 8 | Project Improvements 9 | - Refactor by adding AnnotateRoutes::Helpers [#770](https://github.com/ctran/annotate_models/pull/770) 10 | - Bump puma from 4.3.1 to 4.3.3 in /spec/integration/rails_6.0.2.1 [#771](https://github.com/ctran/annotate_models/pull/771) 11 | - Bump puma from 3.12.2 to 4.3.3 in /spec/integration/rails_5.2.4.1 [#769](https://github.com/ctran/annotate_models/pull/769) 12 | - Bump nokogiri from 1.10.7 to 1.10.8 in /spec/integration/rails_5.2.4.1 [#766](https://github.com/ctran/annotate_models/pull/766) 13 | - Bump nokogiri from 1.10.7 to 1.10.8 in /spec/integration/rails_6.0.2.1 [#765](https://github.com/ctran/annotate_models/pull/765) 14 | - Refactor test cases of AnnotateRoutes [#760](https://github.com/ctran/annotate_models/pull/760) 15 | - Rename FactoryGirl -> FactoryBot comment [#759](https://github.com/ctran/annotate_models/pull/759) 16 | 17 | ## 3.1.0 18 | Changes 19 | - Fix new lines after comments for rubocop compatibility [#757](https://github.com/ctran/annotate_models/pull/757) 20 | - Fix messages from AnnotateRoutes [#737](https://github.com/ctran/annotate_models/pull/737) 21 | - Support YARD notation [#724](https://github.com/ctran/annotate_models/pull/724) 22 | - Refactor AnnotateRoutes.routes_file_exist? [#716](https://github.com/ctran/annotate_models/pull/716) 23 | - Refactor namespace Annotate [#719](https://github.com/ctran/annotate_models/pull/719) 24 | - Add columns managed by Globalize gem [#602](https://github.com/ctran/annotate_models/pull/602) 25 | 26 | Bug Fixes 27 | - Fix additional_file_patterns parsing [#756](https://github.com/ctran/annotate_models/pull/756) 28 | - Fix typo in README [#752](https://github.com/ctran/annotate_models/pull/752) 29 | - Fix bin/annotate NoMethodError [#745](https://github.com/ctran/annotate_models/pull/745) 30 | - Fix README for YARD format [#740](https://github.com/ctran/annotate_models/pull/740) 31 | - Fix constant names that were not renamed in #721 [#739](https://github.com/ctran/annotate_models/pull/739) 32 | - Replace soft-deprecated constant `HashWithIndifferentAccess` to `ActiveSupport::HashWithIndifferentAccess` [#699](https://github.com/ctran/annotate_models/pull/699) 33 | - [Fix #570](https://github.com/ctran/annotate_models/issues/570) Change of foreign key should be considered as a column change 34 | - [Fix #430](https://github.com/ctran/annotate_models/issues/430) Handle columns from activerecord-postgis-adapter [#694](https://github.com/ctran/annotate_models/pull/694) 35 | - Add ActiveAdmin option to template [#693](https://github.com/ctran/annotate_models/pull/693) 36 | - Fix foreign key issue with Rails 6 and Sqlite3 [#695](https://github.com/ctran/annotate_models/pull/695) 37 | - Fix Serializers Test Directory [#625](https://github.com/ctran/annotate_models/pull/625) 38 | - [Fix #624](https://github.com/ctran/annotate_models/issues/624) Correct default values for columns when ActiveRecord::Enum is used [#677](https://github.com/ctran/annotate_models/pull/677) 39 | - [Fix #675](https://github.com/ctran/annotate_models/issues/675) Correct indentation for double-byte characters [#676](https://github.com/ctran/annotate_models/pull/676) 40 | - FIX: Ensure only one line is around the annotation [#669](https://github.com/ctran/annotate_models/pull/669) 41 | - Fix shifted when format_markdown option enabled and used non-ascii [#650](https://github.com/ctran/annotate_models/pull/650) 42 | 43 | Project improvements 44 | - Refactor RSpec for AnnotateModels - structuralize test cases [#755](https://github.com/ctran/annotate_models/pull/755) 45 | - Refactor test cases of AnnotateRoutes as for Rake versions [#754](https://github.com/ctran/annotate_models/pull/754) 46 | - Add integration tests to project [#747](https://github.com/ctran/annotate_models/pull/747) 47 | - Refactor test cases for AnnotateRoutes.remove_annotations [#748](https://github.com/ctran/annotate_models/pull/748) 48 | - Refactor RSpec for AnnotateModels - with Globalize gem [#749](https://github.com/ctran/annotate_models/pull/749) 49 | - Fixed CHANGELOG.md to add link to each PR [#751](https://github.com/ctran/annotate_models/pull/751) 50 | - Delete integration test fixtures [#746](https://github.com/ctran/annotate_models/pull/746) 51 | - Remove remaining integration test files [#744](https://github.com/ctran/annotate_models/pull/744) 52 | - Remove unworking integration tests [#725](https://github.com/ctran/annotate_models/pull/725) 53 | - Refactor Annotate::Parser [#742](https://github.com/ctran/annotate_models/pull/742) 54 | - Refactor RSpec for AnnotateModels (4) - AnnotateModels.get_schema_info (without custom options) [#735](https://github.com/ctran/annotate_models/pull/735) 55 | - Refactor RSpec for AnnotateRoutes (1) [#736](https://github.com/ctran/annotate_models/pull/736) 56 | - Refactor AnnotateRoutes.rewrite_contents [#734](https://github.com/ctran/annotate_models/pull/734) 57 | - AnnotateModels.get_schema_info (with custom options) [#732](https://github.com/ctran/annotate_models/pull/732) 58 | - Fix typo in RSpec of AnnotateModels [#731](https://github.com/ctran/annotate_models/pull/731) 59 | - Remove AnnotateRoutes.rewrite_contents_with_header [#730](https://github.com/ctran/annotate_models/pull/730) 60 | - Refactor AnnotateRoutes.annotate_routes and .rewrite_contents_with_header [#729](https://github.com/ctran/annotate_models/pull/729) 61 | - Refactor AnnotateModels::Parser [#728](https://github.com/ctran/annotate_models/pull/728) 62 | - Remove invalid document of AnnotateRoutes.rewrite_contents [#727](https://github.com/ctran/annotate_models/pull/727) 63 | - Refactor RSpec for AnnotateModels (1) [#726](https://github.com/ctran/annotate_models/pull/726) 64 | - Refactor AnnotateModels::Helpers [#723](https://github.com/ctran/annotate_models/pull/723) 65 | - Refactor AnnotateRoutes.remove_annotations [#715](https://github.com/ctran/annotate_models/pull/715) 66 | - Fix AnnotateRoutes.extract_magic_comments_from_array [#712](https://github.com/ctran/annotate_models/pull/712) 67 | - Rename FactoryGirl to FactoryBot [#721](https://github.com/ctran/annotate_models/pull/721) 68 | - Refactor AnnotateRoutes.header [#714](https://github.com/ctran/annotate_models/pull/714) 69 | - Freeze constant AnnotateRoutes::HEADER_ROW [#713](https://github.com/ctran/annotate_models/pull/713) 70 | - Add constants MAGIC_COMMENT_MATCHER [#711](https://github.com/ctran/annotate_models/pull/711) 71 | - Rename method and variable of AnnotateRoutes for readability [#709](https://github.com/ctran/annotate_models/pull/709) 72 | - Refactor lib/annotate.rb [#707](https://github.com/ctran/annotate_models/pull/707) 73 | - Delete TODO.md [#700](https://github.com/ctran/annotate_models/pull/700) 74 | - Tidy README [#701](https://github.com/ctran/annotate_models/pull/701) 75 | - Convert documentation files to Markdown [#697](https://github.com/ctran/annotate_models/pull/697) 76 | - Upgrade and fix CI [#698](https://github.com/ctran/annotate_models/pull/698) 77 | - Add upgrade instructions to README [#687](https://github.com/ctran/annotate_models/pull/687) 78 | - Fix Github release action [#682](https://github.com/ctran/annotate_models/pull/682) 79 | 80 | ## 3.0.3 81 | - Use a less error-prone way of specifying gem files [#662](https://github.com/ctran/annotate_models/pull/662) 82 | - Update rake requirement from `>= 10.4, < 13.0` to `>= 10.4, < 14.0` [#659](https://github.com/ctran/annotate_models/pull/659) 83 | - Bump nokogiri from 1.6.6.2 to 1.10.4 in /spec/integration/rails_4.2.0 [#655](https://github.com/ctran/annotate_models/pull/655) 84 | - Default annotate models to true in config generated by `rails g annotate:install` [#671](https://github.com/ctran/annotate_models/pull/671) 85 | - Bump loofah from 2.3.0 to 2.3.1 in /spec/integration/rails_4.2.0 [#681](https://github.com/ctran/annotate_models/pull/681) 86 | 87 | ## 3.0.2 88 | - Fixes `LoadError` due to gemspec not referencing `parser.rb`, issue [#657](https://github.com/ctran/annotate_models/issues/657) [#660](https://github.com/ctran/annotate_models/pull/660) 89 | - Changes `--additional_file_patterns` to use dashes `--additional-file-patterns` for consistency [#649](https://github.com/ctran/annotate_models/pull/649) 90 | - Refactor: moving constants into `constants.rb` [#653](https://github.com/ctran/annotate_models/pull/653) 91 | 92 | ## 3.0.1 93 | - Skipped as an official release, used the 3.0.1 patch for setting up Github Actions [#619](https://github.com/ctran/annotate_models/pull/619) 94 | 95 | ## 3.0.0 96 | - **Breaking:** when option `models` is not set - models will not be annotated by default. 97 | 98 | Add `'models'=>'true'` to your config manually or use `--models` option if using CLI. 99 | 100 | - Added `--models` CLI option fixing issue [#563](https://github.com/ctran/annotate_models/issues/563) [#647](https://github.com/ctran/annotate_models/pull/647) 101 | - Added `--additional_file_patterns` option for additional file patterns [#633](https://github.com/ctran/annotate_models/pull/633) [#636](https://github.com/ctran/annotate_models/pull/636) [#637](https://github.com/ctran/annotate_models/pull/637) 102 | - Refactored CLI parser [#646](https://github.com/ctran/annotate_models/pull/646) 103 | - Fixed `BigDecimal.new` deprecation warning [#634](https://github.com/ctran/annotate_models/pull/634) 104 | - Fixed annotations for columns with long data types [#622](https://github.com/ctran/annotate_models/pull/622) 105 | - Made methods private in AnnotateRoutes [#598](https://github.com/ctran/annotate_models/pull/598) 106 | 107 | See https://github.com/ctran/annotate_models/releases/tag/v3.0.0 108 | 109 | ## 2.7.5 110 | See https://github.com/ctran/annotate_models/releases/tag/v2.7.5 111 | 112 | ## 2.7.3 113 | See https://github.com/ctran/annotate_models/releases/tag/v2.7.3 114 | 115 | ## 2.7.2 116 | See https://github.com/ctran/annotate_models/releases/tag/v2.7.2 117 | 118 | ## 2.7.1 119 | See https://github.com/ctran/annotate_models/releases/tag/v2.7.1 120 | 121 | ## 2.7.0 122 | See https://github.com/ctran/annotate_models/releases/tag/v2.7.0 123 | 124 | ## 2.6.9 125 | - Support foreigh key [#241](https://github.com/ctran/annotate_models/pull/241) 126 | - Check if model has skip tag in annotate_model_file [#167](https://github.com/ctran/annotate_models/pull/167) 127 | - Fix issue where serializer-related flags weren't being honored [#246](https://github.com/ctran/annotate_models/issues/246) 128 | - Prefer SQL column type over normalized AR type [#231](https://github.com/ctran/annotate_models/issues/231) 129 | 130 | ## 2.6.8 131 | - Nothing annotated unless `options[:model_dir]` is specified, [#234](https://github.com/ctran/annotate_models/pull/234) 132 | 133 | ## 2.6.7 134 | - Nothing annotated unless `options[:model_dir]` is specified, [#234](https://github.com/ctran/annotate_models/pull/234) 135 | 136 | ## 2.6.6 137 | - Makes it possible to wrap annotations, [#225](https://github.com/ctran/annotate_models/pull/225) 138 | - Fix single model generation, [#214](https://github.com/ctran/annotate_models/pull/214) 139 | - Fix default value for Rails 4.2, [#212](https://github.com/ctran/annotate_models/issues/212) 140 | - Don't crash on inherited models in subdirectories, [#232](https://github.com/ctran/annotate_models/issues/232) 141 | - Process model_dir in rake task, [#197](https://github.com/ctran/annotate_models/pull/197) 142 | 143 | ## 2.6.4 144 | - Skip "models/concerns", [#194](https://github.com/ctran/annotate_models/pull/194) 145 | - Fix [#173](https://github.com/ctran/annotate_models/issues/173) where annotate says "Nothing to annotate" in rails 4.2 146 | - Display an error message if not run from the root of the project, [#186](https://github.com/ctran/annotate_models/pull/186) 147 | - Support rails 4.0 new default test directory, [#182](https://github.com/ctran/annotate_models/issues/182) 148 | - Add an option to show timestamp in routes "-timestamp", [#136](https://github.com/ctran/annotate_models/issues/136) 149 | - Skip plain ruby objects if they have the same class name as an ActiveRecord object, [#121](https://github.com/ctran/annotate_models/issues/121) 150 | 151 | ## 2.6.3 152 | - Fix bug of annotate position in routes [#158](https://github.com/ctran/annotate_models/issues/158) 153 | 154 | ## 2.6.2 155 | - Retain the current annotate block unless --force is specified 156 | - Always load models, since they may not be autoloaded by Rails 157 | - The pg array type is now detected (see [#158](https://github.com/ctran/annotate_models/pull/158)) 158 | 159 | ## 2.6.0.beta2 160 | - support for composite_primary_keys (garysweaver) 161 | - bug fix for annotate_one_file (vlado) 162 | 163 | 164 | ## 2.6.0.beta1 165 | 166 | - It's now possible to use Annotate in standalone ActiveRecord (non-Rails) projects again. 167 | - Adding note that Markdown is actually MultiMarkdown, and recommending the use 168 | of the `kramdown` engine for parsing it. 169 | - Improved Markdown formatting considerably. 170 | - Bugfix: Needed to use inline-code tag for column and table names, 171 | otherwise underscores would cause havok with the formatting. 172 | - Bugfix: Markdown syntax was incorrect 173 | (can't have trailing spaces before the closing marker for an emphasis tag). 174 | - Bugfix: Remove-annotations wasn't properly finding test/spec files, 175 | and wasn't even looking for FactoryGirl factories under the new naming convention. 176 | - Bugfix: Load the Rakefile from the current directory, not the first 177 | Rakefile in our load path. 178 | - Added support for new FactoryGirl naming convention. 179 | - Fix behavior of route annotations in newer versions of Rake that don't 180 | spit out the CWD as their first line of output. 181 | - Overhauled integration testing system to be much easier to work with, 182 | better compartmentalized, and so forth -- at the cost that you must be 183 | using RVM to utilize it. (It'll spit out appropriate pending messages if 184 | you don't.) Also includes a mode for "tinkering" by hand with a scenario, 185 | and won't let you run it through rspect if the repo is in a dirty state. 186 | Added appropriate rake tasks to help with all of this. 187 | - Routes can now be appended, pre-pended, or removed -- and do sane things in all cases. 188 | - Expose all `position_*` variables as CLI params. 189 | - Make `ENV ['position']` work as a default for all the `ENV ['position_*']` variables. 190 | - Make rake tasks more resilient to unusual circumstances / code loading behavior. 191 | - Resolve annotate vs. annotate_models ambiguity once and for all by 192 | settling on `annotate_models` *and* `annotate_routes`. This avoids a name 193 | collision with RMagick while not needlessly overloading the term. 194 | - Fixed that schema kept prepending additional newlines 195 | - Updates to make annotate smarter about when to touch a model 196 | - Recognize column+type, and don't change a file unless the column+type 197 | combination of the new schema are different than that of the old (i.e., 198 | don't regenerate if columns happen to be in a different order. That's just 199 | how life is sometimes) 200 | - Change annotate to use options hash instead of ENV. 201 | 202 | 203 | ## 2.5.0 204 | 205 | - Works better with Rails 3 206 | - Bugfix: schema kept prepending additional newlines 207 | - Updates to make annotate smarter about when to touch a model 208 | - Recognize column+type, and don't change a file unless the column+type 209 | combination of the new schema are different than that of the old (i.e., 210 | don't regenerate if columns happen to be in a different order. That's just 211 | how life is sometimes.) 212 | - Grab old specification even if it has `\r\n` as line endings rather than pure `\n`s 213 | - Various warning and specification fixes 214 | - Fix "no such file to load -- annotate/annotate_models (MissingSourceFile)" 215 | error (require statements in tasks now use full path to lib files) 216 | - warn about macros, to mitigate when we're included during a production 217 | run, not just a rakefile run -- possibly at the expense of too much noise 218 | - Adding rake as a runtime dependency 219 | - If the schema is already in the model file, it will be replaced into the same location. 220 | If it didn't previously exist, it'll be placed according to the "position", as before. 221 | - Allow task loading from Rakefile for gems (plugin installation already auto-detects). 222 | - Add skip_on_db_migrate option as well for people that don't want it 223 | - Fix options parsing to convert strings to proper booleans 224 | - Add support for Fabrication fabricators 225 | - Leave magic encoding comment intact 226 | - Fix issue #14 - RuntimeError: Already memoized 227 | - Count a model as 'annotated' if any of its tests/fixtures are annotated 228 | - Support FactoryGirl 229 | - Support :change migrations (Rails 3.1) 230 | - Allow models with non-standard capitalization 231 | - Widen type column so we can handle longtexts with chopping things off. 232 | - Skip trying to get list of models from commandline when running via Rake 233 | (was preventing the use of multiple rake tasks in one command if one of them was `db:migrate`). 234 | - Add ability to skip annotations for a model by adding 235 | `# -*- SkipSchemaAnnotations` anywhere in the file. 236 | - Don't show column limits for integer and boolean types. 237 | - Add sorting for columns and indexes. 238 | (Helpful for out-of-order migration execution. Use `--sort` if you want this.) 239 | - Annotate unit tests in subfolders. 240 | - Add generator to install rakefile that automatically annotates on `db:migrate`. 241 | - Correct Gemfile to clarify which environments need which gems. 242 | - Add an .rvmrc to facilitate clean development. 243 | - Refactor out ActiveRecord monkey-patch to permit extending without side-effects. 244 | - Use ObjectSpace to locate models to facilitate handling of models with 245 | non-standard capitalization. 246 | Note that this still requires that the inflector be configured to understand 247 | the special case. 248 | - Shore up test cases a bit. 249 | - Merge against many of the older branches on Github whose functionality is 250 | already reflected to reduce confusion about what is and is not implemented here. 251 | - Accept String or Symbol for :position (et al) options. 252 | - Add RDoc output formatting as an option. 253 | - Add Markdown output formatting as an option. 254 | - Add option to force annotation regeneration. 255 | - Add new configuration option for controlling where info is placed in 256 | fixtures/factories. 257 | - Fix for models without tables. 258 | - Fix gemspec generation now that Jeweler looks at Gemfile. 259 | - Fix warning: `NOTE: Gem::Specification#default_executable= is deprecated 260 | with no replacement. It will be removed on or after 2011-10-01.` 261 | - Fix handling of files with no trailing newline when putting annotations at 262 | the end of the file. 263 | - Now works on tables with no primary key. 264 | - `--format=markdown` option 265 | - `--trace` option to help debug "Unable to annotate" errors 266 | - "Table name" annotation (if table name is different from model name) 267 | - "Human name" annotation (enabling translation to non-English locales) 268 | - Fix JRuby ObjectSpace compatibility bug (https://github.com/ctran/annotate_models/pull/85) 269 | - Fix FactoryGirl compatibility bug (https://github.com/ctran/annotate_models/pull/82) 270 | 271 | 272 | ## 2.4.2 2009-11-21 273 | - Annotates `(spec|test)/factories/<model>_factory.rb` files 274 | 275 | ## 2.4.1 2009-11-20 276 | 277 | - Annotates thoughtbot's factory_girl factories (`test/factories/<model>_factory.rb`) 278 | - Move default annotation position back to top 279 | 280 | 281 | ## 2.4.0 2009-12-13 282 | - Incorporated lots of patches from the Github community, 283 | including support for Blueprints fixtures 284 | - Several bug fixes 285 | 286 | ## 2.1 2009-10-18 287 | 288 | - New options 289 | - `-R` to require additional files before loading the models 290 | - `-i` to show database indexes in annotations 291 | - `-e` to exclude annotating tests or fixtures 292 | - `-m` to include the migration version number in the annotation 293 | - `--model-dir` to annotate model files stored a different place than `app/models` 294 | 295 | - Ignore unknown macros ('acts_as_whatever') 296 | 297 | 298 | ## 2.0 2009-02-03 299 | 300 | - Add annotate_models plugin fork additions 301 | - Annotates Rspec and Test Unit models 302 | - Annotates Object Daddy exemplars 303 | - Annotates geometrical columns 304 | 305 | - Add AnnotateRoutes rake task 306 | - Up gem structure to newgem defaults 307 | 308 | 309 | ## 1.0.4 2008-09-04 310 | 311 | - Only update modified models since last run, thanks to sant0sk1 312 | 313 | ## 1.0.3 2008-05-02 314 | 315 | - Add misc changes from Dustin Sallings and Henrik N 316 | - Remove trailing whitespace 317 | - More intuitive info messages 318 | - Update README file with update-to-date example 319 | 320 | ## 1.0.2 2008-03-22 321 | 322 | - Add contributions from Michael Bumann (http://github.com/bumi) 323 | - added an option "position" to choose to put the annotation, 324 | - spec/fixtures now also get annotated 325 | - added a task to remove the annotations 326 | - these options can be specified from command line as `-d` and `-p [before|after]` 327 | 328 | 329 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '>= 2.4.0' 4 | 5 | gem 'activerecord', '>= 4.2.5', '< 6', require: false 6 | gem 'rake', require: false 7 | 8 | group :development do 9 | gem 'bump' 10 | gem 'mg', require: false 11 | gem 'travis', require: false 12 | platforms :mri, :mingw do 13 | gem 'yard', require: false 14 | end 15 | end 16 | 17 | group :development, :test do 18 | gem 'byebug' 19 | gem 'guard-rspec', require: false 20 | gem 'rspec', require: false 21 | 22 | gem 'rubocop', '~> 1.12.0', require: false 23 | gem 'rubocop-rake', require: false 24 | gem 'rubocop-rspec', '~> 2.2.0', require: false 25 | gem 'simplecov', require: false 26 | gem 'terminal-notifier-guard', require: false 27 | 28 | gem 'codeclimate-test-reporter' 29 | gem 'coveralls' 30 | 31 | gem 'overcommit' 32 | gem 'ruby_dep', '1.5.0' 33 | 34 | platforms :mri, :mingw do 35 | gem 'pry', require: false 36 | gem 'pry-byebug', require: false 37 | end 38 | end 39 | 40 | group :test do 41 | gem 'files', require: false 42 | gem 'git', require: false 43 | end 44 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # NOTE: The cmd option is now required due to the increasing number of ways 2 | # rspec may be run, below are examples of the most common uses. 3 | # * bundler: 'bundle exec rspec' 4 | # * bundler binstubs: 'bin/rspec' 5 | # * spring: 'bin/rsspec' (This will use spring if running and you have 6 | # installed the spring binstubs per the docs) 7 | # * zeus: 'zeus rspec' (requires the server to be started separetly) 8 | # * 'just' rspec: 'rspec' 9 | guard :rspec, cmd: 'bundle exec rspec' do 10 | watch(%r{^spec/.+_spec\.rb$}) 11 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 12 | watch('spec/spec_helper.rb') { 'spec' } 13 | 14 | # Rails example 15 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 16 | watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 17 | 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"] } 18 | watch(%r{^spec/support/(.+)\.rb$}) { 'spec' } 19 | watch('config/routes.rb') { 'spec/routing' } 20 | watch('app/controllers/application_controller.rb') { 'spec/controllers' } 21 | watch('spec/rails_helper.rb') { 'spec' } 22 | 23 | # Capybara features specs 24 | watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" } 25 | 26 | # Turnip features and steps 27 | watch(%r{^spec/acceptance/(.+)\.feature$}) 28 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | You can redistribute it and/or modify it under either the terms of the 2 | 2-clause BSDL (see the file BSDL), or the conditions below: 3 | 4 | 1. You may make and give away verbatim copies of the source form of the 5 | software without restriction, provided that you duplicate all of the 6 | original copyright notices and associated disclaimers. 7 | 8 | 2. You may modify your copy of the software in any way, provided that 9 | you do at least ONE of the following: 10 | 11 | a) place your modifications in the Public Domain or otherwise 12 | make them Freely Available, such as by posting said 13 | modifications to Usenet or an equivalent medium, or by allowing 14 | the author to include your modifications in the software. 15 | 16 | b) use the modified software only within your corporation or 17 | organization. 18 | 19 | c) give non-standard binaries non-standard names, with 20 | instructions on where to get the original software distribution. 21 | 22 | d) make other distribution arrangements with the author. 23 | 24 | 3. You may distribute the software in object code or binary form, 25 | provided that you do at least ONE of the following: 26 | 27 | a) distribute the binaries and library files of the software, 28 | together with instructions (in the manual page or equivalent) 29 | on where to get the original distribution. 30 | 31 | b) accompany the distribution with the machine-readable source of 32 | the software. 33 | 34 | c) give non-standard binaries non-standard names, with 35 | instructions on where to get the original software distribution. 36 | 37 | d) make other distribution arrangements with the author. 38 | 39 | 4. You may modify and include the part of the software into any other 40 | software (possibly commercial). But some files in the distribution 41 | are not written by the author, so that they are not under these terms. 42 | 43 | For the list of those files and their copying conditions, see the 44 | file LEGAL. 45 | 46 | 5. The scripts and library files supplied as input to or produced as 47 | output from the software do not automatically fall under the 48 | copyright of the software, but belong to whomever generated them, 49 | and may be sold commercially, and may be aggregated with this 50 | software. 51 | 52 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 53 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 54 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 55 | PURPOSE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Annotate (aka AnnotateModels) 2 | 3 | [](http://badge.fury.io/rb/annotate) 4 | [](https://rubygems.org/gems/annotate) 5 | [](https://github.com/ctran/annotate_models/actions?workflow=CI) 6 | [](https://coveralls.io/r/ctran/annotate_models?branch=develop) 7 | [](https://codeclimate.com/github/ctran/annotate_models) 8 | 9 | Add a comment summarizing the current schema to the top or bottom of each of your... 10 | 11 | - ActiveRecord models 12 | - Fixture files 13 | - Tests and Specs 14 | - Object Daddy exemplars 15 | - Machinist blueprints 16 | - Fabrication fabricators 17 | - Thoughtbot's factory_bot factories, i.e. the `(spec|test)/factories/<model>_factory.rb` files 18 | - `routes.rb` file (for Rails projects) 19 | 20 | 21 | The schema comment looks like this: 22 | 23 | ```ruby 24 | # == Schema Info 25 | # 26 | # Table name: line_items 27 | # 28 | # id :integer(11) not null, primary key 29 | # quantity :integer(11) not null 30 | # product_id :integer(11) not null 31 | # unit_price :float 32 | # order_id :integer(11) 33 | # 34 | 35 | class LineItem < ActiveRecord::Base 36 | belongs_to :product 37 | . . . 38 | ``` 39 | 40 | It also annotates geometrical columns, `geom` type and `srid`, 41 | when using `SpatialAdapter`, `PostgisAdapter` or `PostGISAdapter`: 42 | 43 | ```ruby 44 | # == Schema Info 45 | # 46 | # Table name: trips 47 | # 48 | # local :geometry point, 4326 49 | # path :geometry line_string, 4326 50 | ``` 51 | 52 | Also, if you pass the `-r` option, it'll annotate `routes.rb` with the output of `rake routes`. 53 | 54 | 55 | ## Upgrading to 3.X and annotate models not working? 56 | 57 | In versions 2.7.X the annotate gem defaulted to annotating models if no arguments were passed in. 58 | The annotate gem by default would not allow for routes and models to be annotated together. 59 | A [change was added in #647](https://github.com/ctran/annotate_models/pull/647). 60 | You [can read more here](https://github.com/ctran/annotate_models/issues/663). 61 | 62 | There are a few ways of fixing this: 63 | 64 | - If using CLI explicitly pass in models flag using `--models` 65 | 66 | OR 67 | 68 | a) Running `rails g annotate:install` will overwrite your defaults with the annotating `models` option set to `'true'`. 69 | 70 | b) In `lib/tasks/auto_annotate_models.rake` add the `models` key-value option: 71 | 72 | ```ruby 73 | Annotate.set_defaults( 74 | ... 75 | 'models' => 'true', 76 | ... 77 | ``` 78 | 79 | ## Install 80 | 81 | Into Gemfile from rubygems.org: 82 | 83 | ```ruby 84 | group :development do 85 | gem 'annotate' 86 | end 87 | ``` 88 | 89 | Into Gemfile from Github: 90 | 91 | ```ruby 92 | group :development do 93 | gem 'annotate', git: 'https://github.com/ctran/annotate_models.git' 94 | end 95 | ``` 96 | 97 | Into environment gems from rubygems.org: 98 | 99 | gem install annotate 100 | 101 | Into environment gems from Github checkout: 102 | 103 | git clone https://github.com/ctran/annotate_models.git annotate_models 104 | cd annotate_models 105 | rake gem 106 | gem install dist/annotate-*.gem 107 | 108 | ## Usage 109 | 110 | (If you used the Gemfile install, prefix the below commands with `bundle exec`.) 111 | 112 | ### Usage in Rails 113 | 114 | To annotate all your models, tests, fixtures, and factories: 115 | 116 | cd /path/to/app 117 | annotate 118 | 119 | To annotate just your models, tests, and factories: 120 | 121 | annotate --models --exclude fixtures 122 | 123 | To annotate just your models: 124 | 125 | annotate --models 126 | 127 | To annotate routes.rb: 128 | 129 | annotate --routes 130 | 131 | To remove model/test/fixture/factory/serializer annotations: 132 | 133 | annotate --delete 134 | 135 | To remove routes.rb annotations: 136 | 137 | annotate --routes --delete 138 | 139 | To automatically annotate every time you run `db:migrate`, 140 | either run `rails g annotate:install` 141 | or add `Annotate.load_tasks` to your `Rakefile`. 142 | 143 | See the [configuration in Rails](#configuration-in-rails) section for more info. 144 | 145 | ### Usage Outside of Rails 146 | 147 | Everything above applies, except that `--routes` is not meaningful, 148 | and you will probably need to explicitly set one or more `--require` option(s), and/or one or more `--model-dir` options 149 | to inform `annotate` about the structure of your project and help it bootstrap and load the relevant code. 150 | 151 | ## Configuration 152 | 153 | If you want to always skip annotations on a particular model, add this string 154 | anywhere in the file: 155 | 156 | # -*- SkipSchemaAnnotations 157 | 158 | ### Configuration in Rails 159 | 160 | To generate a configuration file (in the form of a `.rake` file), to set 161 | default options: 162 | 163 | rails g annotate:install 164 | 165 | Edit this file to control things like output format, where annotations are 166 | added (top or bottom of file), and in which artifacts. 167 | 168 | The generated rakefile `lib/tasks/auto_annotate_models.rake` also contains 169 | `Annotate.load_tasks`. This adds a few rake tasks which duplicate command-line 170 | functionality: 171 | 172 | rake annotate_models # Add schema information (as comments) to model and fixture files 173 | rake annotate_routes # Adds the route map to routes.rb 174 | rake remove_annotation # Remove schema information from model and fixture files 175 | 176 | By default, once you've generated a configuration file, annotate will be 177 | executed whenever you run `rake db:migrate` (but only in development mode). 178 | If you want to disable this behavior permanently, 179 | edit the `.rake` file and change: 180 | 181 | ```ruby 182 | 'skip_on_db_migrate' => 'false', 183 | ``` 184 | 185 | To: 186 | 187 | ```ruby 188 | 'skip_on_db_migrate' => 'true', 189 | ``` 190 | 191 | If you want to run `rake db:migrate` as a one-off without running annotate, 192 | you can do so with a simple environment variable, instead of editing the 193 | `.rake` file: 194 | 195 | ANNOTATE_SKIP_ON_DB_MIGRATE=1 rake db:migrate 196 | 197 | ## Options 198 | 199 | Usage: annotate [options] [model_file]* 200 | --additional-file-patterns Additional file paths or globs to annotate, separated by commas (e.g. `/foo/bar/%model_name%/*.rb,/baz/%model_name%.rb`) 201 | -d, --delete Remove annotations from all model files or the routes.rb file 202 | -p [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) 203 | --position 204 | --pc, --position-in-class [before|top|after|bottom] 205 | Place the annotations at the top (before) or the bottom (after) of the model file 206 | --pf, --position-in-factory [before|top|after|bottom] 207 | Place the annotations at the top (before) or the bottom (after) of any factory files 208 | --px, --position-in-fixture [before|top|after|bottom] 209 | Place the annotations at the top (before) or the bottom (after) of any fixture files 210 | --pt, --position-in-test [before|top|after|bottom] 211 | Place the annotations at the top (before) or the bottom (after) of any test files 212 | --pr, --position-in-routes [before|top|after|bottom] 213 | Place the annotations at the top (before) or the bottom (after) of the routes.rb file 214 | --ps, --position-in-serializer [before|top|after|bottom] 215 | Place the annotations at the top (before) or the bottom (after) of the serializer files 216 | --w, --wrapper STR Wrap annotation with the text passed as parameter. 217 | If --w option is used, the same text will be used as opening and closing 218 | --wo, --wrapper-open STR Annotation wrapper opening. 219 | --wc, --wrapper-close STR Annotation wrapper closing 220 | -r, --routes Annotate routes.rb with the output of 'rake routes' 221 | --models Annotate ActiveRecord models 222 | -a, --active-admin Annotate active_admin models 223 | -v, --version Show the current version of this gem 224 | -m, --show-migration Include the migration version number in the annotation 225 | -c, --show-check-constraints List the table's check constraints in the annotation 226 | -k, --show-foreign-keys List the table's foreign key constraints in the annotation 227 | --ck, --complete-foreign-keys 228 | Complete foreign key names in the annotation 229 | -i, --show-indexes List the table's database indexes in the annotation 230 | -s, --simple-indexes Concat the column's related indexes in the annotation 231 | --model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with commas 232 | --root-dir dir Annotate files stored within root dir projects, separate multiple dirs with commas 233 | --ignore-model-subdirects Ignore subdirectories of the models directory 234 | --sort Sort columns alphabetically, rather than in creation order 235 | --classified-sort Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns 236 | -R, --require path Additional file to require before loading models, may be used multiple times 237 | -e [tests,fixtures,factories,serializers], 238 | --exclude Do not annotate fixtures, test files, factories, and/or serializers 239 | -f [bare|rdoc|yard|markdown], Render Schema Infomation as plain/RDoc/YARD/Markdown 240 | --format 241 | --force Force new annotations even if there are no changes. 242 | --frozen Do not allow to change annotations. Exits non-zero if there are going to be changes to files. 243 | --timestamp Include timestamp in (routes) annotation 244 | --trace If unable to annotate a file, print the full stack trace, not just the exception message. 245 | -I, --ignore-columns REGEX don't annotate columns that match a given REGEX (e.g. `annotate -I '^(id|updated_at|created_at)'`) 246 | --ignore-routes REGEX don't annotate routes that match a given REGEX (e.g. `annotate -I '(mobile|resque|pghero)'`)_ 247 | --hide-limit-column-types VALUES 248 | don't show limit for given column types, separated by commas (e.g. `integer,boolean,text`) 249 | --hide-default-column-types VALUES 250 | don't show default for given column types, separated by commas (e.g. `json,jsonb,hstore`) 251 | --ignore-unknown-models don't display warnings for bad model files 252 | --with-comment include database comments in model annotations 253 | --with-comment-column include database comments in model annotations, as its own column, after all others 254 | 255 | ### Option: `additional_file_patterns` 256 | 257 | CLI: `--additional-file-patterns`<br> 258 | Ruby: `:additional_file_patterns` 259 | 260 | Provide additional paths for the gem to annotate. These paths can include 261 | globs. It is recommended to use absolute paths. Here are some examples: 262 | 263 | * `/app/lib/decorates/%MODEL_NAME%/*.rb` 264 | * `/app/lib/forms/%PLURALIZED_MODEL_NAME%/**/*.rb` 265 | * `/app/lib/forms/%TABLE_NAME%/*.rb` 266 | 267 | 268 | The appropriate model will be inferred using the `%*%` syntax, annotating any 269 | matching files. It works with existing filename resolutions (options for which 270 | can be found in the `resolve_filename` method of `annotate_models.rb`). 271 | 272 | When using in a Rails config, you can use the following: 273 | 274 | `File.join(Rails.application.root, 275 | 'app/lib/forms/%PLURALIZED_MODEL_NAME%/***/**.rb')` 276 | 277 | ## Sorting 278 | 279 | By default, columns will be sorted in database order (i.e. the order in which 280 | migrations were run). 281 | 282 | If you prefer to sort alphabetically so that the results of annotation are 283 | consistent regardless of what order migrations are executed in, use `--sort`. 284 | 285 | ## Markdown 286 | 287 | The format produced is actually MultiMarkdown, making use of the syntax 288 | extension for tables. It's recommended you use `kramdown` as your parser if 289 | you want to use this format. If you're using `yard` to generate 290 | documentation, specify a format of markdown with `kramdown` as the provider by 291 | adding this to your `.yardopts` file: 292 | 293 | --markup markdown 294 | --markup-provider kramdown 295 | 296 | Be sure to add this to your `Gemfile` as well: 297 | 298 | gem 'kramdown', groups => [:development], require => false 299 | 300 | ## WARNING 301 | 302 | **Don't add text after an automatically-created comment block.** This tool 303 | will blow away the initial/final comment block in your models if it looks like 304 | it was previously added by this gem. 305 | 306 | Be sure to check the changes that this tool makes! If you are using Git, you 307 | may simply check your project's status after running `annotate`: 308 | 309 | $ git status 310 | 311 | If you are not using a VCS (like Git, Subversion or similar), please tread 312 | extra carefully, and consider using one. 313 | 314 | ## Links 315 | 316 | * Factory Bot: http://github.com/thoughtbot/factory_bot 317 | * Object Daddy: http://github.com/flogic/object_daddy 318 | * Machinist: http://github.com/notahat/machinist 319 | * Fabrication: http://github.com/paulelliott/fabrication 320 | * SpatialAdapter: http://github.com/pdeffendol/spatial_adapter 321 | * PostgisAdapter: http://github.com/nofxx/postgis_adapter 322 | * PostGISAdapter: https://github.com/dazuma/activerecord-postgis-adapter 323 | 324 | 325 | ## License 326 | 327 | Released under the same license as Ruby. No Support. No Warranty. 328 | 329 | ## Authors 330 | 331 | [See AUTHORS.md](AUTHORS.md). 332 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Prerequisite 2 | 3 | - Install "git-flow" (`brew install git-flow`) 4 | - Install "bump" gem (`gem install bump`) 5 | 6 | 7 | ## Perform a release 8 | 9 | - `git flow release start <release>` 10 | - Update the `CHANGELOG.md` file 11 | - `bump current` 12 | - `bump patch` 13 | - `rm -rf dist` 14 | - `rake spec` 15 | - `rake gem` 16 | - `git flow release finish <release>` 17 | 18 | - `rake gem:publish` 19 | 20 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | def exit_exception(e) 2 | $stderr.puts e.message 3 | exit e.status_code 4 | end 5 | 6 | # NOTE: this causes annoying psych warnings under Ruby 1.9.2-p180; to fix, upgrade to 1.9.3 7 | begin 8 | require 'bundler' 9 | Bundler.setup(:default, :development) 10 | rescue Bundler::BundlerError => e 11 | $stderr.puts 'Run `bundle install` to install missing gems' 12 | exit_exception(e) 13 | end 14 | 15 | using_dsl = false 16 | begin 17 | require 'rake/dsl_definition' 18 | using_dsl = true 19 | rescue Exception => e 20 | # We might just be on an old version of Rake... 21 | exit_exception(e) 22 | end 23 | require 'rake' 24 | include Rake::DSL if using_dsl 25 | 26 | require './lib/annotate' 27 | require 'mg' 28 | begin 29 | MG.new('annotate.gemspec') 30 | rescue Exception 31 | $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.") 32 | # Gemspec is probably in a broken state, so let's give ourselves a chance to 33 | # build a new one... 34 | end 35 | DEVELOPMENT_GROUPS = [:development, :test].freeze 36 | RUNTIME_GROUPS = Bundler.definition.groups - DEVELOPMENT_GROUPS 37 | namespace :gem do 38 | task :gemspec do 39 | spec = Gem::Specification.new do |gem| 40 | # See http://docs.rubygems.org/read/chapter/20 41 | # for more options. 42 | gem.version = Annotate.version 43 | gem.name = 'annotate' 44 | gem.homepage = 'http://github.com/ctran/annotate_models' 45 | gem.rubyforge_project = 'annotate' 46 | gem.license = 'Ruby' 47 | gem.summary = 'Annotates Rails Models, routes, fixtures, and others based on the database schema.' 48 | gem.description = 'Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.' 49 | gem.email = ['alex@stinky.com', 'cuong@gmail.com', 'x@nofxx.com', 'turadg@aleahmad.net', 'jon@cloudability.com'] 50 | gem.authors = ['Alex Chaffee', 'Cuong Tran', 'Marcos Piccinini', 'Turadg Aleahmad', 'Jon Frisby'] 51 | gem.require_paths = ['lib'] 52 | # gem.rdoc_options = ["--charset=UTF-8"] 53 | # gem.required_ruby_version = "> 1.9.2" 54 | 55 | Bundler.load.dependencies_for(*RUNTIME_GROUPS).each do |dep| 56 | runtime_resolved = Bundler.definition.specs_for(RUNTIME_GROUPS).find { |spec| spec.name == dep.name } 57 | unless runtime_resolved.nil? 58 | gem.add_dependency(dep.name, dep.requirement) 59 | end 60 | end 61 | 62 | gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 63 | gem.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'TODO.md'] 64 | 65 | gem.files = `git ls-files -- .`.split("\n").reject do |fn| 66 | fn =~ /^Gemfile.*/ || 67 | fn =~ /^Rakefile/ || 68 | fn =~ /^\.rvmrc/ || 69 | fn =~ /^\.gitignore/ || 70 | fn =~ /^\.rspec/ || 71 | fn =~ /^\.document/ || 72 | fn =~ /^\.yardopts/ || 73 | fn =~ /^pkg/ || 74 | fn =~ /^spec/ || 75 | fn =~ /^doc/ || 76 | fn =~ /^vendor\/cache/ 77 | end.sort 78 | end 79 | File.open('annotate.gemspec', 'wb') do |fh| 80 | fh.write("# This file is auto-generated!\n") 81 | fh.write("# DO NOT EDIT THIS FILE DIRECTLY!\n") 82 | fh.write("# Instead, edit the Rakefile and run 'rake gems:gemspec'.") 83 | fh.write(spec.to_ruby) 84 | end 85 | end 86 | end 87 | 88 | namespace :jeweler do 89 | task :clobber do 90 | FileUtils.rm_f('pkg') 91 | end 92 | end 93 | task clobber: :'jeweler:clobber' 94 | 95 | require 'rspec/core/rake_task' # RSpec 2.0 96 | RSpec::Core::RakeTask.new(:spec) do |t| 97 | t.pattern = ['spec/*_spec.rb', 'spec/**/*_spec.rb'] 98 | t.rspec_opts = ['--backtrace', '--format d'] 99 | end 100 | 101 | # Placeholder for running bin/* in development... 102 | task :environment 103 | 104 | task :integration_environment do 105 | require './spec/spec_helper' 106 | end 107 | 108 | namespace :gemsets do 109 | desc "Completely empty any gemsets used by scenarios, so they'll be perfectly clean on the next run." 110 | task empty: [:integration_environment] do 111 | Annotate::Integration::SCENARIOS.each do |test_rig, _base_dir, _test_name| 112 | Annotate::Integration.empty_gemset(test_rig) 113 | end 114 | end 115 | end 116 | task clobber: :'gemsets:empty' 117 | 118 | namespace :integration do 119 | desc "Remove any cruft generated by manual debugging runs which is .gitignore'd." 120 | task clean: :integration_environment do 121 | Annotate::Integration.nuke_all_cruft 122 | end 123 | 124 | desc 'Reset any changed files, and remove any untracked files in spec/integration/*/, plus run integration:clean.' 125 | task clobber: [:integration_environment, :'integration:clean'] do 126 | Annotate::Integration.reset_dirty_files 127 | Annotate::Integration.clear_untracked_files 128 | end 129 | 130 | task symlink: [:integration_environment] do 131 | require 'digest/md5' 132 | 133 | integration_dir = File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'integration')) 134 | # fixture_dir = File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'fixtures')) 135 | target_dir = File.expand_path(ENV['TARGET']) if ENV['TARGET'] 136 | raise 'Must specify TARGET=x, where x is an integration test scenario!' unless target_dir && Dir.exist?(target_dir) 137 | raise 'TARGET directory must be within spec/integration/!' unless target_dir.start_with?(integration_dir) 138 | candidates = {} 139 | FileList[ 140 | "#{target_dir}/.rvmrc", 141 | "#{target_dir}/**/*" 142 | ].select { |fname| !(File.symlink?(fname) || File.directory?(fname)) } 143 | .map { |fname| fname.sub(integration_dir, '') } 144 | .reject do |fname| 145 | fname =~ /\/\.gitkeep$/ || 146 | fname =~ /\/app\/models\// || 147 | fname =~ /\/routes\.rb$/ || 148 | fname =~ /\/fixtures\// || 149 | fname =~ /\/factories\// || 150 | fname =~ /\.sqlite3$/ || 151 | (fname =~ /\/test\// && fname !~ /_helper\.rb$/) || 152 | (fname =~ /\/spec\// && fname !~ /_helper\.rb$/) 153 | end 154 | .map { |fname| "#{integration_dir}#{fname}" } 155 | .each do |fname| 156 | digest = Digest::MD5.hexdigest(File.read(fname)) 157 | candidates[digest] ||= [] 158 | candidates[digest] << fname 159 | end 160 | fixtures = {} 161 | FileList['spec/fixtures/**/*'].each do |fname| 162 | fixtures[Digest::MD5.hexdigest(File.read(fname))] = File.expand_path(fname) 163 | end 164 | 165 | candidates.each_key do |digest| 166 | next unless fixtures.key?(digest) 167 | candidates[digest].each do |fname| 168 | # Double-check contents in case of hash collision... 169 | next unless FileUtils.identical?(fname, fixtures[digest]) 170 | destination_dir = Pathname.new(File.dirname(fname)) 171 | relative_target = Pathname.new(fixtures[digest]).relative_path_from(destination_dir) 172 | Dir.chdir(destination_dir) do 173 | sh('ln', '-sfn', relative_target.to_s, File.basename(fname)) 174 | end 175 | end 176 | end 177 | end 178 | end 179 | task clobber: :'integration:clobber' 180 | 181 | require 'yard' 182 | YARD::Rake::YardocTask.new do |t| 183 | # t.files = ['features/**/*.feature', 'features/**/*.rb', 'lib/**/*.rb'] 184 | # t.options = ['--any', '--extra', '--opts'] # optional 185 | end 186 | 187 | namespace :yard do 188 | task :clobber do 189 | FileUtils.rm_f('.yardoc') 190 | FileUtils.rm_f('doc') 191 | end 192 | end 193 | task clobber: :'yard:clobber' 194 | 195 | namespace :rubinius do 196 | task :clobber do 197 | FileList['**/*.rbc'].each { |fname| FileUtils.rm_f(fname) } 198 | FileList['.rbx/**/*'].each { |fname| FileUtils.rm_f(fname) } 199 | end 200 | end 201 | task clobber: :'rubinius:clobber' 202 | 203 | # want other tests/tasks run by default? Add them to the list 204 | task default: [:spec] 205 | -------------------------------------------------------------------------------- /annotate.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'annotate/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'annotate' 8 | s.version = Annotate.version 9 | 10 | s.required_ruby_version = '>= 2.4.0' 11 | s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version= 12 | s.authors = ['Alex Chaffee', 'Cuong Tran', 'Marcos Piccinini', 'Turadg Aleahmad', 'Jon Frisby'] 13 | s.description = 'Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.' 14 | s.email = ['alex@stinky.com', 'cuong.tran@gmail.com', 'x@nofxx.com', 'turadg@aleahmad.net', 'jon@cloudability.com'] 15 | s.executables = ['annotate'] 16 | s.extra_rdoc_files = ['README.md', 'CHANGELOG.md'] 17 | s.files = `git ls-files -z LICENSE.txt *.md *.gemspec bin lib`.split("\x0") 18 | s.homepage = 'https://github.com/ctran/annotate_models' 19 | s.licenses = ['Ruby'] 20 | s.require_paths = ['lib'] 21 | s.rubygems_version = '2.1.11' 22 | s.summary = 'Annotates Rails Models, routes, fixtures, and others based on the database schema.' 23 | 24 | s.specification_version = 4 if s.respond_to? :specification_version 25 | s.add_runtime_dependency(%q<rake>, '>= 10.4', '< 14.0') 26 | s.add_runtime_dependency(%q<activerecord>, ['>= 3.2', '< 8.0']) 27 | 28 | s.metadata = { 29 | "bug_tracker_uri" => "https://github.com/ctran/annotate_models/issues/", 30 | "source_code_uri" => "https://github.com/ctran/annotate_models.git" 31 | } 32 | end 33 | -------------------------------------------------------------------------------- /bin/annotate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | unless File.exist?('./Rakefile') || File.exist?('./Gemfile') 4 | abort 'Please run annotate from the root of the project.' 5 | end 6 | 7 | require 'rubygems' 8 | begin 9 | require 'bundler' 10 | Bundler.setup 11 | rescue StandardError 12 | end 13 | 14 | here = File.expand_path(File.dirname __FILE__) 15 | $LOAD_PATH << "#{here}/../lib" 16 | 17 | require 'annotate' 18 | require 'annotate/parser' 19 | 20 | Annotate.bootstrap_rake 21 | 22 | options_result = Annotate::Parser.parse(ARGV) 23 | 24 | exit if options_result[:exit] 25 | 26 | options = Annotate.setup_options( 27 | is_rake: ENV['is_rake'] && !ENV['is_rake'].empty? 28 | ) 29 | Annotate.eager_load(options) if Annotate::Helpers.include_models? 30 | 31 | AnnotateModels.send(options_result[:target_action], options) if Annotate::Helpers.include_models? 32 | AnnotateRoutes.send(options_result[:target_action], options) if Annotate::Helpers.include_routes? 33 | -------------------------------------------------------------------------------- /lib/annotate.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | require 'annotate/version' 3 | require 'annotate/annotate_models' 4 | require 'annotate/annotate_routes' 5 | require 'annotate/constants' 6 | require 'annotate/helpers' 7 | 8 | begin 9 | # ActiveSupport 3.x... 10 | require 'active_support/hash_with_indifferent_access' 11 | require 'active_support/core_ext/object/blank' 12 | rescue StandardError 13 | # ActiveSupport 2.x... 14 | require 'active_support/core_ext/hash/indifferent_access' 15 | require 'active_support/core_ext/blank' 16 | end 17 | 18 | module Annotate 19 | ## 20 | # Set default values that can be overridden via environment variables. 21 | # 22 | def self.set_defaults(options = {}) 23 | return if @has_set_defaults 24 | @has_set_defaults = true 25 | 26 | options = ActiveSupport::HashWithIndifferentAccess.new(options) 27 | 28 | Constants::ALL_ANNOTATE_OPTIONS.flatten.each do |key| 29 | if options.key?(key) 30 | default_value = if options[key].is_a?(Array) 31 | options[key].join(',') 32 | else 33 | options[key] 34 | end 35 | end 36 | 37 | default_value = ENV[key.to_s] unless ENV[key.to_s].blank? 38 | ENV[key.to_s] = default_value.nil? ? nil : default_value.to_s 39 | end 40 | end 41 | 42 | ## 43 | # TODO: what is the difference between this and set_defaults? 44 | # 45 | def self.setup_options(options = {}) 46 | Constants::POSITION_OPTIONS.each do |key| 47 | options[key] = Annotate::Helpers.fallback(ENV[key.to_s], ENV['position'], 'before') 48 | end 49 | Constants::FLAG_OPTIONS.each do |key| 50 | options[key] = Annotate::Helpers.true?(ENV[key.to_s]) 51 | end 52 | Constants::OTHER_OPTIONS.each do |key| 53 | options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s] : nil 54 | end 55 | Constants::PATH_OPTIONS.each do |key| 56 | options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s].split(',') : [] 57 | end 58 | 59 | options[:additional_file_patterns] ||= [] 60 | options[:additional_file_patterns] = options[:additional_file_patterns].split(',') if options[:additional_file_patterns].is_a?(String) 61 | options[:model_dir] = ['app/models'] if options[:model_dir].empty? 62 | 63 | options[:wrapper_open] ||= options[:wrapper] 64 | options[:wrapper_close] ||= options[:wrapper] 65 | 66 | # These were added in 2.7.0 but so this is to revert to old behavior by default 67 | options[:exclude_scaffolds] = Annotate::Helpers.true?(ENV.fetch('exclude_scaffolds', 'true')) 68 | options[:exclude_controllers] = Annotate::Helpers.true?(ENV.fetch('exclude_controllers', 'true')) 69 | options[:exclude_helpers] = Annotate::Helpers.true?(ENV.fetch('exclude_helpers', 'true')) 70 | 71 | options 72 | end 73 | 74 | def self.load_tasks 75 | return if @tasks_loaded 76 | 77 | Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each do |rake| 78 | load rake 79 | end 80 | 81 | @tasks_loaded = true 82 | end 83 | 84 | def self.eager_load(options) 85 | load_requires(options) 86 | require 'annotate/active_record_patch' 87 | 88 | if defined?(Rails::Application) 89 | if Rails.version.split('.').first.to_i < 3 90 | Rails.configuration.eager_load_paths.each do |load_path| 91 | matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/ 92 | Dir.glob("#{load_path}/**/*.rb").sort.each do |file| 93 | require_dependency file.sub(matcher, '\1') 94 | end 95 | end 96 | else 97 | klass = Rails::Application.send(:subclasses).first 98 | klass.eager_load! 99 | end 100 | else 101 | options[:model_dir].each do |dir| 102 | FileList["#{dir}/**/*.rb"].each do |fname| 103 | require File.expand_path(fname) 104 | end 105 | end 106 | end 107 | end 108 | 109 | def self.bootstrap_rake 110 | begin 111 | require 'rake/dsl_definition' 112 | rescue StandardError => e 113 | # We might just be on an old version of Rake... 114 | $stderr.puts e.message 115 | exit e.status_code 116 | end 117 | require 'rake' 118 | 119 | load './Rakefile' if File.exist?('./Rakefile') 120 | begin 121 | Rake::Task[:environment].invoke 122 | rescue 123 | nil 124 | end 125 | unless defined?(Rails) 126 | # Not in a Rails project, so time to load up the parts of 127 | # ActiveSupport we need. 128 | require 'active_support' 129 | require 'active_support/core_ext/class/subclasses' 130 | require 'active_support/core_ext/string/inflections' 131 | end 132 | 133 | load_tasks 134 | Rake::Task[:set_annotation_options].invoke 135 | end 136 | 137 | class << self 138 | private 139 | 140 | def load_requires(options) 141 | options[:require].count > 0 && 142 | options[:require].each { |path| require path } 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /lib/annotate/active_record_patch.rb: -------------------------------------------------------------------------------- 1 | # monkey patches 2 | 3 | module ::ActiveRecord 4 | class Base 5 | def self.method_missing(_name, *_args) 6 | # ignore this, so unknown/unloaded macros won't cause parsing to fail 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/annotate/annotate_models/file_patterns.rb: -------------------------------------------------------------------------------- 1 | module AnnotateModels 2 | # This module provides module method to get file paths. 3 | module FilePatterns 4 | # Controller files 5 | CONTROLLER_DIR = File.join('app', 'controllers') 6 | 7 | # Active admin registry files 8 | ACTIVEADMIN_DIR = File.join('app', 'admin') 9 | 10 | # Helper files 11 | HELPER_DIR = File.join('app', 'helpers') 12 | 13 | # File.join for windows reverse bar compat? 14 | # I dont use windows, can`t test 15 | UNIT_TEST_DIR = File.join('test', 'unit') 16 | MODEL_TEST_DIR = File.join('test', 'models') # since rails 4.0 17 | SPEC_MODEL_DIR = File.join('spec', 'models') 18 | 19 | FIXTURE_TEST_DIR = File.join('test', 'fixtures') 20 | FIXTURE_SPEC_DIR = File.join('spec', 'fixtures') 21 | 22 | # Other test files 23 | CONTROLLER_TEST_DIR = File.join('test', 'controllers') 24 | CONTROLLER_SPEC_DIR = File.join('spec', 'controllers') 25 | REQUEST_SPEC_DIR = File.join('spec', 'requests') 26 | ROUTING_SPEC_DIR = File.join('spec', 'routing') 27 | 28 | # Object Daddy http://github.com/flogic/object_daddy/tree/master 29 | EXEMPLARS_TEST_DIR = File.join('test', 'exemplars') 30 | EXEMPLARS_SPEC_DIR = File.join('spec', 'exemplars') 31 | 32 | # Machinist http://github.com/notahat/machinist 33 | BLUEPRINTS_TEST_DIR = File.join('test', 'blueprints') 34 | BLUEPRINTS_SPEC_DIR = File.join('spec', 'blueprints') 35 | 36 | # Factory Bot https://github.com/thoughtbot/factory_bot 37 | FACTORY_BOT_TEST_DIR = File.join('test', 'factories') 38 | FACTORY_BOT_SPEC_DIR = File.join('spec', 'factories') 39 | 40 | # Fabrication https://github.com/paulelliott/fabrication.git 41 | FABRICATORS_TEST_DIR = File.join('test', 'fabricators') 42 | FABRICATORS_SPEC_DIR = File.join('spec', 'fabricators') 43 | 44 | # Serializers https://github.com/rails-api/active_model_serializers 45 | SERIALIZERS_DIR = File.join('app', 'serializers') 46 | SERIALIZERS_TEST_DIR = File.join('test', 'serializers') 47 | SERIALIZERS_SPEC_DIR = File.join('spec', 'serializers') 48 | 49 | class << self 50 | def generate(root_directory, pattern_type, options) 51 | case pattern_type 52 | when 'test' then test_files(root_directory) 53 | when 'fixture' then fixture_files(root_directory) 54 | when 'scaffold' then scaffold_files(root_directory) 55 | when 'factory' then factory_files(root_directory) 56 | when 'serializer' then serialize_files(root_directory) 57 | when 'additional_file_patterns' 58 | [options[:additional_file_patterns] || []].flatten 59 | when 'controller' 60 | [File.join(root_directory, CONTROLLER_DIR, '%PLURALIZED_MODEL_NAME%_controller.rb')] 61 | when 'admin' 62 | [ 63 | File.join(root_directory, ACTIVEADMIN_DIR, '%MODEL_NAME%.rb'), 64 | File.join(root_directory, ACTIVEADMIN_DIR, '%PLURALIZED_MODEL_NAME%.rb') 65 | ] 66 | when 'helper' 67 | [File.join(root_directory, HELPER_DIR, '%PLURALIZED_MODEL_NAME%_helper.rb')] 68 | else 69 | [] 70 | end 71 | end 72 | 73 | private 74 | 75 | def test_files(root_directory) 76 | [ 77 | File.join(root_directory, UNIT_TEST_DIR, '%MODEL_NAME%_test.rb'), 78 | File.join(root_directory, MODEL_TEST_DIR, '%MODEL_NAME%_test.rb'), 79 | File.join(root_directory, SPEC_MODEL_DIR, '%MODEL_NAME%_spec.rb') 80 | ] 81 | end 82 | 83 | def fixture_files(root_directory) 84 | [ 85 | File.join(root_directory, FIXTURE_TEST_DIR, '%TABLE_NAME%.yml'), 86 | File.join(root_directory, FIXTURE_SPEC_DIR, '%TABLE_NAME%.yml'), 87 | File.join(root_directory, FIXTURE_TEST_DIR, '%PLURALIZED_MODEL_NAME%.yml'), 88 | File.join(root_directory, FIXTURE_SPEC_DIR, '%PLURALIZED_MODEL_NAME%.yml') 89 | ] 90 | end 91 | 92 | def scaffold_files(root_directory) 93 | [ 94 | File.join(root_directory, CONTROLLER_TEST_DIR, '%PLURALIZED_MODEL_NAME%_controller_test.rb'), 95 | File.join(root_directory, CONTROLLER_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_controller_spec.rb'), 96 | File.join(root_directory, REQUEST_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_spec.rb'), 97 | File.join(root_directory, ROUTING_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_routing_spec.rb') 98 | ] 99 | end 100 | 101 | def factory_files(root_directory) 102 | [ 103 | File.join(root_directory, EXEMPLARS_TEST_DIR, '%MODEL_NAME%_exemplar.rb'), 104 | File.join(root_directory, EXEMPLARS_SPEC_DIR, '%MODEL_NAME%_exemplar.rb'), 105 | File.join(root_directory, BLUEPRINTS_TEST_DIR, '%MODEL_NAME%_blueprint.rb'), 106 | File.join(root_directory, BLUEPRINTS_SPEC_DIR, '%MODEL_NAME%_blueprint.rb'), 107 | File.join(root_directory, FACTORY_BOT_TEST_DIR, '%MODEL_NAME%_factory.rb'), # (old style) 108 | File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%MODEL_NAME%_factory.rb'), # (old style) 109 | File.join(root_directory, FACTORY_BOT_TEST_DIR, '%TABLE_NAME%.rb'), # (new style) 110 | File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%TABLE_NAME%.rb'), # (new style) 111 | File.join(root_directory, FACTORY_BOT_TEST_DIR, '%PLURALIZED_MODEL_NAME%.rb'), # (new style) 112 | File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%PLURALIZED_MODEL_NAME%.rb'), # (new style) 113 | File.join(root_directory, FABRICATORS_TEST_DIR, '%MODEL_NAME%_fabricator.rb'), 114 | File.join(root_directory, FABRICATORS_SPEC_DIR, '%MODEL_NAME%_fabricator.rb') 115 | ] 116 | end 117 | 118 | def serialize_files(root_directory) 119 | [ 120 | File.join(root_directory, SERIALIZERS_DIR, '%MODEL_NAME%_serializer.rb'), 121 | File.join(root_directory, SERIALIZERS_TEST_DIR, '%MODEL_NAME%_serializer_test.rb'), 122 | File.join(root_directory, SERIALIZERS_SPEC_DIR, '%MODEL_NAME%_serializer_spec.rb') 123 | ] 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/annotate/annotate_routes.rb: -------------------------------------------------------------------------------- 1 | # == Annotate Routes 2 | # 3 | # Based on: 4 | # 5 | # 6 | # 7 | # Prepends the output of "rake routes" to the top of your routes.rb file. 8 | # Yes, it's simple but I'm thick and often need a reminder of what my routes 9 | # mean. 10 | # 11 | # Running this task will replace any existing route comment generated by the 12 | # task. Best to back up your routes file before running: 13 | # 14 | # Author: 15 | # Gavin Montague 16 | # gavin@leftbrained.co.uk 17 | # 18 | # Released under the same license as Ruby. No Support. No Warranty. 19 | # 20 | 21 | require_relative './annotate_routes/helpers' 22 | require_relative './annotate_routes/header_generator' 23 | 24 | module AnnotateRoutes 25 | class << self 26 | def do_annotations(options = {}) 27 | if routes_file_exist? 28 | existing_text = File.read(routes_file) 29 | content, header_position = Helpers.strip_annotations(existing_text) 30 | new_content = annotate_routes(HeaderGenerator.generate(options), content, header_position, options) 31 | new_text = new_content.join("\n") 32 | if rewrite_contents(existing_text, new_text, options[:frozen]) 33 | puts "#{routes_file} was annotated." 34 | else 35 | puts "#{routes_file} was not changed." 36 | end 37 | else 38 | puts "#{routes_file} could not be found." 39 | end 40 | end 41 | 42 | def remove_annotations(options={}) 43 | if routes_file_exist? 44 | existing_text = File.read(routes_file) 45 | content, header_position = Helpers.strip_annotations(existing_text) 46 | new_content = strip_on_removal(content, header_position) 47 | new_text = new_content.join("\n") 48 | if rewrite_contents(existing_text, new_text, options[:frozen]) 49 | puts "Annotations were removed from #{routes_file}." 50 | else 51 | puts "#{routes_file} was not changed (Annotation did not exist)." 52 | end 53 | else 54 | puts "#{routes_file} could not be found." 55 | end 56 | end 57 | 58 | private 59 | 60 | def routes_file_exist? 61 | File.exist?(routes_file) 62 | end 63 | 64 | def routes_file 65 | @routes_rb ||= File.join('config', 'routes.rb') 66 | end 67 | 68 | def strip_on_removal(content, header_position) 69 | if header_position == :before 70 | content.shift while content.first == '' 71 | elsif header_position == :after 72 | content.pop while content.last == '' 73 | end 74 | 75 | # Make sure we end on a trailing newline. 76 | content << '' unless content.last == '' 77 | 78 | # TODO: If the user buried it in the middle, we should probably see about 79 | # TODO: preserving a single line of space between the content above and 80 | # TODO: below... 81 | content 82 | end 83 | 84 | def rewrite_contents(existing_text, new_text, frozen) 85 | content_changed = (existing_text != new_text) 86 | 87 | if content_changed 88 | abort "annotate error. #{routes_file} needs to be updated, but annotate was run with `--frozen`." if frozen 89 | File.open(routes_file, 'wb') { |f| f.puts(new_text) } 90 | end 91 | 92 | content_changed 93 | end 94 | 95 | def annotate_routes(header, content, header_position, options = {}) 96 | magic_comments_map, content = Helpers.extract_magic_comments_from_array(content) 97 | if %w(before top).include?(options[:position_in_routes]) 98 | header = header << '' if content.first != '' 99 | magic_comments_map << '' if magic_comments_map.any? 100 | new_content = magic_comments_map + header + content 101 | else 102 | # Ensure we have adequate trailing newlines at the end of the file to 103 | # ensure a blank line separating the content from the annotation. 104 | content << '' unless content.last == '' 105 | 106 | # We're moving something from the top of the file to the bottom, so ditch 107 | # the spacer we put in the first time around. 108 | content.shift if header_position == :before && content.first == '' 109 | 110 | new_content = magic_comments_map + content + header 111 | end 112 | 113 | # Make sure we end on a trailing newline. 114 | new_content << '' unless new_content.last == '' 115 | 116 | new_content 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/annotate/annotate_routes/header_generator.rb: -------------------------------------------------------------------------------- 1 | require_relative './helpers' 2 | 3 | module AnnotateRoutes 4 | class HeaderGenerator 5 | PREFIX = '== Route Map'.freeze 6 | PREFIX_MD = '## Route Map'.freeze 7 | HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'].freeze 8 | 9 | class << self 10 | def generate(options = {}) 11 | new(options, routes_map(options)).generate 12 | end 13 | 14 | private :new 15 | 16 | private 17 | 18 | def routes_map(options) 19 | result = `rake routes`.chomp("\n").split(/\n/, -1) 20 | 21 | # In old versions of Rake, the first line of output was the cwd. Not so 22 | # much in newer ones. We ditch that line if it exists, and if not, we 23 | # keep the line around. 24 | result.shift if result.first =~ %r{^\(in \/} 25 | 26 | ignore_routes = options[:ignore_routes] 27 | regexp_for_ignoring_routes = ignore_routes ? /#{ignore_routes}/ : nil 28 | 29 | # Skip routes which match given regex 30 | # Note: it matches the complete line (route_name, path, controller/action) 31 | if regexp_for_ignoring_routes 32 | result.reject { |line| line =~ regexp_for_ignoring_routes } 33 | else 34 | result 35 | end 36 | end 37 | end 38 | 39 | def initialize(options, routes_map) 40 | @options = options 41 | @routes_map = routes_map 42 | end 43 | 44 | def generate 45 | magic_comments_map, contents_without_magic_comments = Helpers.extract_magic_comments_from_array(routes_map) 46 | 47 | out = [] 48 | 49 | magic_comments_map.each do |magic_comment| 50 | out << magic_comment 51 | end 52 | out << '' if magic_comments_map.any? 53 | 54 | out << comment(options[:wrapper_open]) if options[:wrapper_open] 55 | 56 | out << comment(markdown? ? PREFIX_MD : PREFIX) + timestamp_if_required 57 | out << comment 58 | return out if contents_without_magic_comments.size.zero? 59 | 60 | maxs = [HEADER_ROW.map(&:size)] + contents_without_magic_comments[1..-1].map { |line| line.split.map(&:size) } 61 | 62 | if markdown? 63 | max = maxs.map(&:max).compact.max 64 | 65 | out << comment(content(HEADER_ROW, maxs)) 66 | out << comment(content(['-' * max, '-' * max, '-' * max, '-' * max], maxs)) 67 | else 68 | out << comment(content(contents_without_magic_comments[0], maxs)) 69 | end 70 | 71 | out += contents_without_magic_comments[1..-1].map { |line| comment(content(markdown? ? line.split(' ') : line, maxs)) } 72 | out << comment(options[:wrapper_close]) if options[:wrapper_close] 73 | 74 | out 75 | end 76 | 77 | private 78 | 79 | attr_reader :options, :routes_map 80 | 81 | def comment(row = '') 82 | if row == '' 83 | '#' 84 | else 85 | "# #{row}" 86 | end 87 | end 88 | 89 | def content(line, maxs) 90 | return line.rstrip unless markdown? 91 | 92 | line.each_with_index.map { |elem, index| format_line_element(elem, maxs, index) }.join(' | ') 93 | end 94 | 95 | def format_line_element(elem, maxs, index) 96 | min_length = maxs.map { |arr| arr[index] }.max || 0 97 | format("%-#{min_length}.#{min_length}s", elem.tr('|', '-')) 98 | end 99 | 100 | def markdown? 101 | options[:format_markdown] 102 | end 103 | 104 | def timestamp_if_required(time = Time.now) 105 | if options[:timestamp] 106 | time_formatted = time.strftime('%Y-%m-%d %H:%M') 107 | " (Updated #{time_formatted})" 108 | else 109 | '' 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/annotate/annotate_routes/helpers.rb: -------------------------------------------------------------------------------- 1 | module AnnotateRoutes 2 | module Helpers 3 | MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/).freeze 4 | 5 | class << self 6 | # TODO: write the method doc using ruby rdoc formats 7 | # This method returns an array of 'real_content' and 'header_position'. 8 | # 'header_position' will either be :before, :after, or 9 | # a number. If the number is > 0, the 10 | # annotation was found somewhere in the 11 | # middle of the file. If the number is 12 | # zero, no annotation was found. 13 | def strip_annotations(content) 14 | real_content = [] 15 | mode = :content 16 | header_position = 0 17 | 18 | content.split(/\n/, -1).each_with_index do |line, line_number| 19 | if mode == :header && line !~ /\s*#/ 20 | mode = :content 21 | real_content << line unless line.blank? 22 | elsif mode == :content 23 | if line =~ /^\s*#\s*== Route.*$/ 24 | header_position = line_number + 1 # index start's at 0 25 | mode = :header 26 | else 27 | real_content << line 28 | end 29 | end 30 | end 31 | 32 | real_content_and_header_position(real_content, header_position) 33 | end 34 | 35 | # @param [Array<String>] content 36 | # @return [Array<String>] all found magic comments 37 | # @return [Array<String>] content without magic comments 38 | def extract_magic_comments_from_array(content_array) 39 | magic_comments = [] 40 | new_content = [] 41 | 42 | content_array.each do |row| 43 | if row =~ MAGIC_COMMENT_MATCHER 44 | magic_comments << row.strip 45 | else 46 | new_content << row 47 | end 48 | end 49 | 50 | [magic_comments, new_content] 51 | end 52 | 53 | private 54 | 55 | def real_content_and_header_position(real_content, header_position) 56 | # By default assume the annotation was found in the middle of the file 57 | 58 | # ... unless we have evidence it was at the beginning ... 59 | return real_content, :before if header_position == 1 60 | 61 | # ... or that it was at the end. 62 | return real_content, :after if header_position >= real_content.count 63 | 64 | # and the default 65 | return real_content, header_position 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/annotate/constants.rb: -------------------------------------------------------------------------------- 1 | module Annotate 2 | module Constants 3 | TRUE_RE = /^(true|t|yes|y|1)$/i.freeze 4 | 5 | ## 6 | # The set of available options to customize the behavior of Annotate. 7 | # 8 | POSITION_OPTIONS = [ 9 | :position_in_routes, :position_in_class, :position_in_test, 10 | :position_in_fixture, :position_in_factory, :position, 11 | :position_in_serializer 12 | ].freeze 13 | 14 | FLAG_OPTIONS = [ 15 | :show_indexes, :simple_indexes, :include_version, :exclude_tests, 16 | :exclude_fixtures, :exclude_factories, :ignore_model_sub_dir, 17 | :format_bare, :format_rdoc, :format_yard, :format_markdown, :sort, :force, :frozen, 18 | :trace, :timestamp, :exclude_serializers, :classified_sort, 19 | :show_foreign_keys, :show_complete_foreign_keys, 20 | :exclude_scaffolds, :exclude_controllers, :exclude_helpers, 21 | :exclude_sti_subclasses, :ignore_unknown_models, :with_comment, :with_comment_column, 22 | :show_check_constraints 23 | ].freeze 24 | 25 | OTHER_OPTIONS = [ 26 | :additional_file_patterns, :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close, 27 | :wrapper, :routes, :models, :hide_limit_column_types, :hide_default_column_types, 28 | :ignore_routes, :active_admin 29 | ].freeze 30 | 31 | PATH_OPTIONS = [ 32 | :require, :model_dir, :root_dir 33 | ].freeze 34 | 35 | ALL_ANNOTATE_OPTIONS = [ 36 | POSITION_OPTIONS, FLAG_OPTIONS, OTHER_OPTIONS, PATH_OPTIONS 37 | ].freeze 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/annotate/helpers.rb: -------------------------------------------------------------------------------- 1 | module Annotate 2 | # Class for holding helper methods. Done to make lib/annotate.rb less bloated. 3 | class Helpers 4 | class << self 5 | def skip_on_migration? 6 | ENV['ANNOTATE_SKIP_ON_DB_MIGRATE'] =~ Constants::TRUE_RE || ENV['skip_on_db_migrate'] =~ Constants::TRUE_RE 7 | end 8 | 9 | def include_routes? 10 | ENV['routes'] =~ Constants::TRUE_RE 11 | end 12 | 13 | def include_models? 14 | ENV['models'] =~ Constants::TRUE_RE 15 | end 16 | 17 | def true?(val) 18 | val.present? && Constants::TRUE_RE.match?(val) 19 | end 20 | 21 | def fallback(*args) 22 | args.detect(&:present?) 23 | end 24 | 25 | def reset_options(options) 26 | options.flatten.each { |key| ENV[key.to_s] = nil } 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/annotate/parser.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module Annotate 4 | # Class for handling command line arguments 5 | class Parser # rubocop:disable Metrics/ClassLength 6 | def self.parse(args, env = {}) 7 | new(args, env).parse 8 | end 9 | 10 | attr_reader :args, :options, :env 11 | 12 | DEFAULT_OPTIONS = { 13 | target_action: :do_annotations, 14 | exit: false 15 | }.freeze 16 | 17 | ANNOTATION_POSITIONS = %w[before top after bottom].freeze 18 | FILE_TYPE_POSITIONS = %w[position_in_class position_in_factory position_in_fixture position_in_test position_in_routes position_in_serializer].freeze 19 | EXCLUSION_LIST = %w[tests fixtures factories serializers].freeze 20 | FORMAT_TYPES = %w[bare rdoc yard markdown].freeze 21 | 22 | def initialize(args, env) 23 | @args = args 24 | @options = DEFAULT_OPTIONS.dup 25 | @env = env 26 | end 27 | 28 | def parse 29 | # To split up because right now this method parses and commits 30 | parser.parse!(args) 31 | 32 | commit 33 | 34 | options 35 | end 36 | 37 | private 38 | 39 | def parser 40 | OptionParser.new do |option_parser| 41 | add_options_to_parser(option_parser) 42 | end 43 | end 44 | 45 | def commit 46 | env.each_pair do |key, value| 47 | ENV[key] = value 48 | end 49 | end 50 | 51 | def add_options_to_parser(option_parser) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize 52 | has_set_position = {} 53 | 54 | option_parser.banner = 'Usage: annotate [options] [model_file]*' 55 | 56 | option_parser.on('--additional-file-patterns path1,path2,path3', 57 | Array, 58 | "Additional file paths or globs to annotate, separated by commas (e.g. `/foo/bar/%model_name%/*.rb,/baz/%model_name%.rb`)") do |additional_file_patterns| 59 | ENV['additional_file_patterns'] = additional_file_patterns 60 | end 61 | 62 | option_parser.on('-d', 63 | '--delete', 64 | 'Remove annotations from all model files or the routes.rb file') do 65 | @options[:target_action] = :remove_annotations 66 | end 67 | 68 | option_parser.on('-p', 69 | '--position [before|top|after|bottom]', 70 | ANNOTATION_POSITIONS, 71 | 'Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)') do |position| 72 | env['position'] = position 73 | 74 | FILE_TYPE_POSITIONS.each do |key| 75 | env[key] = position unless has_set_position[key] 76 | end 77 | end 78 | 79 | option_parser.on('--pc', 80 | '--position-in-class [before|top|after|bottom]', 81 | ANNOTATION_POSITIONS, 82 | 'Place the annotations at the top (before) or the bottom (after) of the model file') do |position_in_class| 83 | env['position_in_class'] = position_in_class 84 | has_set_position['position_in_class'] = true 85 | end 86 | 87 | option_parser.on('--pf', 88 | '--position-in-factory [before|top|after|bottom]', 89 | ANNOTATION_POSITIONS, 90 | 'Place the annotations at the top (before) or the bottom (after) of any factory files') do |position_in_factory| 91 | env['position_in_factory'] = position_in_factory 92 | has_set_position['position_in_factory'] = true 93 | end 94 | 95 | option_parser.on('--px', 96 | '--position-in-fixture [before|top|after|bottom]', 97 | ANNOTATION_POSITIONS, 98 | 'Place the annotations at the top (before) or the bottom (after) of any fixture files') do |position_in_fixture| 99 | env['position_in_fixture'] = position_in_fixture 100 | has_set_position['position_in_fixture'] = true 101 | end 102 | 103 | option_parser.on('--pt', 104 | '--position-in-test [before|top|after|bottom]', 105 | ANNOTATION_POSITIONS, 106 | 'Place the annotations at the top (before) or the bottom (after) of any test files') do |position_in_test| 107 | env['position_in_test'] = position_in_test 108 | has_set_position['position_in_test'] = true 109 | end 110 | 111 | option_parser.on('--pr', 112 | '--position-in-routes [before|top|after|bottom]', 113 | ANNOTATION_POSITIONS, 114 | 'Place the annotations at the top (before) or the bottom (after) of the routes.rb file') do |position_in_routes| 115 | env['position_in_routes'] = position_in_routes 116 | has_set_position['position_in_routes'] = true 117 | end 118 | 119 | option_parser.on('--ps', 120 | '--position-in-serializer [before|top|after|bottom]', 121 | ANNOTATION_POSITIONS, 122 | 'Place the annotations at the top (before) or the bottom (after) of the serializer files') do |position_in_serializer| 123 | env['position_in_serializer'] = position_in_serializer 124 | has_set_position['position_in_serializer'] = true 125 | end 126 | 127 | option_parser.on('--w', 128 | '--wrapper STR', 129 | 'Wrap annotation with the text passed as parameter.', 130 | 'If --w option is used, the same text will be used as opening and closing') do |wrapper| 131 | env['wrapper'] = wrapper 132 | end 133 | 134 | option_parser.on('--wo', 135 | '--wrapper-open STR', 136 | 'Annotation wrapper opening.') do |wrapper_open| 137 | env['wrapper_open'] = wrapper_open 138 | end 139 | 140 | option_parser.on('--wc', 141 | '--wrapper-close STR', 142 | 'Annotation wrapper closing') do |wrapper_close| 143 | env['wrapper_close'] = wrapper_close 144 | end 145 | 146 | option_parser.on('-r', 147 | '--routes', 148 | "Annotate routes.rb with the output of 'rake routes'") do 149 | env['routes'] = 'true' 150 | end 151 | 152 | option_parser.on('--models', 153 | "Annotate ActiveRecord models") do 154 | env['models'] = 'true' 155 | end 156 | 157 | option_parser.on('-a', 158 | '--active-admin', 159 | 'Annotate active_admin models') do 160 | env['active_admin'] = 'true' 161 | end 162 | 163 | option_parser.on('-v', 164 | '--version', 165 | 'Show the current version of this gem') do 166 | puts "annotate v#{Annotate.version}" 167 | @options[:exit] = true 168 | end 169 | 170 | option_parser.on('-m', 171 | '--show-migration', 172 | 'Include the migration version number in the annotation') do 173 | env['include_version'] = 'yes' 174 | end 175 | 176 | option_parser.on('-c', 177 | '--show-check-constraints', 178 | "List the table's check constraints in the annotation") do 179 | env['show_check_constraints'] = 'yes' 180 | end 181 | 182 | option_parser.on('-k', 183 | '--show-foreign-keys', 184 | "List the table's foreign key constraints in the annotation") do 185 | env['show_foreign_keys'] = 'yes' 186 | end 187 | 188 | option_parser.on('--ck', 189 | '--complete-foreign-keys', 190 | 'Complete foreign key names in the annotation') do 191 | env['show_foreign_keys'] = 'yes' 192 | env['show_complete_foreign_keys'] = 'yes' 193 | end 194 | 195 | option_parser.on('-i', 196 | '--show-indexes', 197 | "List the table's database indexes in the annotation") do 198 | env['show_indexes'] = 'yes' 199 | end 200 | 201 | option_parser.on('-s', 202 | '--simple-indexes', 203 | "Concat the column's related indexes in the annotation") do 204 | env['simple_indexes'] = 'yes' 205 | end 206 | 207 | option_parser.on('--model-dir dir', 208 | "Annotate model files stored in dir rather than app/models, separate multiple dirs with commas") do |dir| 209 | env['model_dir'] = dir 210 | end 211 | 212 | option_parser.on('--root-dir dir', 213 | "Annotate files stored within root dir projects, separate multiple dirs with commas") do |dir| 214 | env['root_dir'] = dir 215 | end 216 | 217 | option_parser.on('--ignore-model-subdirects', 218 | "Ignore subdirectories of the models directory") do 219 | env['ignore_model_sub_dir'] = 'yes' 220 | end 221 | 222 | option_parser.on('--sort', 223 | "Sort columns alphabetically, rather than in creation order") do 224 | env['sort'] = 'yes' 225 | end 226 | 227 | option_parser.on('--classified-sort', 228 | "Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns") do 229 | env['classified_sort'] = 'yes' 230 | end 231 | 232 | option_parser.on('-R', 233 | '--require path', 234 | "Additional file to require before loading models, may be used multiple times") do |path| 235 | env['require'] = if env['require'].present? 236 | "#{env['require']},#{path}" 237 | else 238 | path 239 | end 240 | end 241 | 242 | option_parser.on('-e', 243 | '--exclude [tests,fixtures,factories,serializers]', 244 | Array, 245 | "Do not annotate fixtures, test files, factories, and/or serializers") do |exclusions| 246 | exclusions ||= EXCLUSION_LIST 247 | exclusions.each { |exclusion| env["exclude_#{exclusion}"] = 'yes' } 248 | end 249 | 250 | option_parser.on('-f', 251 | '--format [bare|rdoc|yard|markdown]', 252 | FORMAT_TYPES, 253 | 'Render Schema Infomation as plain/RDoc/Yard/Markdown') do |format_type| 254 | env["format_#{format_type}"] = 'yes' 255 | end 256 | 257 | option_parser.on('--force', 258 | 'Force new annotations even if there are no changes.') do 259 | env['force'] = 'yes' 260 | end 261 | 262 | option_parser.on('--frozen', 263 | 'Do not allow to change annotations. Exits non-zero if there are going to be changes to files.') do 264 | env['frozen'] = 'yes' 265 | end 266 | 267 | option_parser.on('--timestamp', 268 | 'Include timestamp in (routes) annotation') do 269 | env['timestamp'] = 'true' 270 | end 271 | 272 | option_parser.on('--trace', 273 | 'If unable to annotate a file, print the full stack trace, not just the exception message.') do 274 | env['trace'] = 'yes' 275 | end 276 | 277 | option_parser.on('-I', 278 | '--ignore-columns REGEX', 279 | "don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`") do |regex| 280 | env['ignore_columns'] = regex 281 | end 282 | 283 | option_parser.on('--ignore-routes REGEX', 284 | "don't annotate routes that match a given REGEX (i.e., `annotate -I '(mobile|resque|pghero)'`") do |regex| 285 | env['ignore_routes'] = regex 286 | end 287 | 288 | option_parser.on('--hide-limit-column-types VALUES', 289 | "don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)") do |values| 290 | env['hide_limit_column_types'] = values.to_s 291 | end 292 | 293 | option_parser.on('--hide-default-column-types VALUES', 294 | "don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)") do |values| 295 | env['hide_default_column_types'] = values.to_s 296 | end 297 | 298 | option_parser.on('--ignore-unknown-models', 299 | "don't display warnings for bad model files") do 300 | env['ignore_unknown_models'] = 'true' 301 | end 302 | 303 | option_parser.on('--with-comment', 304 | "include database comments in model annotations") do 305 | env['with_comment'] = 'true' 306 | end 307 | 308 | option_parser.on('--with-comment-column', 309 | "include database comments in model annotations, as its own column, after all others") do 310 | env['with_comment_column'] = 'true' 311 | end 312 | end 313 | end 314 | end 315 | -------------------------------------------------------------------------------- /lib/annotate/tasks.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | # Make tasks visible for Rails also when used as gem. 5 | Dir[File.join(File.dirname(__FILE__), '..', 'tasks', '**/*.rake')].each { |rake| load rake } 6 | Dir[File.join(File.dirname(__FILE__), '..', '..', 'tasks', '**/*.rake')].each { |rake| load rake } 7 | -------------------------------------------------------------------------------- /lib/annotate/version.rb: -------------------------------------------------------------------------------- 1 | module Annotate 2 | def self.version 3 | '3.2.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/generators/annotate/USAGE: -------------------------------------------------------------------------------- 1 | Add a .rake file that automatically annotates models when you do a db:migrate 2 | in development mode: 3 | 4 | rails generate annotate:install 5 | -------------------------------------------------------------------------------- /lib/generators/annotate/install_generator.rb: -------------------------------------------------------------------------------- 1 | require 'annotate' 2 | 3 | module Annotate 4 | module Generators 5 | class InstallGenerator < Rails::Generators::Base 6 | desc 'Copy annotate_models rakefiles for automatic annotation' 7 | source_root File.expand_path('templates', __dir__) 8 | 9 | # copy rake tasks 10 | def copy_tasks 11 | template 'auto_annotate_models.rake', 'lib/tasks/auto_annotate_models.rake' 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/generators/annotate/templates/auto_annotate_models.rake: -------------------------------------------------------------------------------- 1 | # NOTE: only doing this in development as some production environments (Heroku) 2 | # NOTE: are sensitive to local FS writes, and besides -- it's just not proper 3 | # NOTE: to have a dev-mode tool do its thing in production. 4 | if Rails.env.development? 5 | require 'annotate' 6 | task :set_annotation_options do 7 | # You can override any of these by setting an environment variable of the 8 | # same name. 9 | Annotate.set_defaults( 10 | 'active_admin' => 'false', 11 | 'additional_file_patterns' => [], 12 | 'routes' => 'false', 13 | 'models' => 'true', 14 | 'position_in_routes' => 'before', 15 | 'position_in_class' => 'before', 16 | 'position_in_test' => 'before', 17 | 'position_in_fixture' => 'before', 18 | 'position_in_factory' => 'before', 19 | 'position_in_serializer' => 'before', 20 | 'show_check_constraints' => 'false', 21 | 'show_foreign_keys' => 'true', 22 | 'show_complete_foreign_keys' => 'false', 23 | 'show_indexes' => 'true', 24 | 'simple_indexes' => 'false', 25 | 'model_dir' => 'app/models', 26 | 'root_dir' => '', 27 | 'include_version' => 'false', 28 | 'require' => '', 29 | 'exclude_tests' => 'false', 30 | 'exclude_fixtures' => 'false', 31 | 'exclude_factories' => 'false', 32 | 'exclude_serializers' => 'false', 33 | 'exclude_scaffolds' => 'true', 34 | 'exclude_controllers' => 'true', 35 | 'exclude_helpers' => 'true', 36 | 'exclude_sti_subclasses' => 'false', 37 | 'ignore_model_sub_dir' => 'false', 38 | 'ignore_columns' => nil, 39 | 'ignore_routes' => nil, 40 | 'ignore_unknown_models' => 'false', 41 | 'hide_limit_column_types' => '<%= AnnotateModels::NO_LIMIT_COL_TYPES.join(",") %>', 42 | 'hide_default_column_types' => '<%= AnnotateModels::NO_DEFAULT_COL_TYPES.join(",") %>', 43 | 'skip_on_db_migrate' => 'false', 44 | 'format_bare' => 'true', 45 | 'format_rdoc' => 'false', 46 | 'format_yard' => 'false', 47 | 'format_markdown' => 'false', 48 | 'sort' => 'false', 49 | 'force' => 'false', 50 | 'frozen' => 'false', 51 | 'classified_sort' => 'true', 52 | 'trace' => 'false', 53 | 'wrapper_open' => nil, 54 | 'wrapper_close' => nil, 55 | 'with_comment' => 'true', 56 | 'with_comment_column' => 'false' 57 | ) 58 | end 59 | 60 | Annotate.load_tasks 61 | end 62 | -------------------------------------------------------------------------------- /lib/tasks/annotate_models.rake: -------------------------------------------------------------------------------- 1 | annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) 2 | 3 | unless ENV['is_cli'] 4 | task :set_annotation_options 5 | task annotate_models: :set_annotation_options 6 | end 7 | 8 | desc 'Add schema information (as comments) to model and fixture files' 9 | task annotate_models: :environment do 10 | require "#{annotate_lib}/annotate/annotate_models" 11 | require "#{annotate_lib}/annotate/active_record_patch" 12 | 13 | options = {is_rake: true} 14 | ENV['position'] = options[:position] = Annotate::Helpers.fallback(ENV['position'], 'before') 15 | options[:additional_file_patterns] = ENV['additional_file_patterns'] ? ENV['additional_file_patterns'].split(',') : [] 16 | options[:position_in_class] = Annotate::Helpers.fallback(ENV['position_in_class'], ENV['position']) 17 | options[:position_in_fixture] = Annotate::Helpers.fallback(ENV['position_in_fixture'], ENV['position']) 18 | options[:position_in_factory] = Annotate::Helpers.fallback(ENV['position_in_factory'], ENV['position']) 19 | options[:position_in_test] = Annotate::Helpers.fallback(ENV['position_in_test'], ENV['position']) 20 | options[:position_in_serializer] = Annotate::Helpers.fallback(ENV['position_in_serializer'], ENV['position']) 21 | options[:show_check_constraints] = Annotate::Helpers.true?(ENV['show_check_constraints']) 22 | options[:show_foreign_keys] = Annotate::Helpers.true?(ENV['show_foreign_keys']) 23 | options[:show_complete_foreign_keys] = Annotate::Helpers.true?(ENV['show_complete_foreign_keys']) 24 | options[:show_indexes] = Annotate::Helpers.true?(ENV['show_indexes']) 25 | options[:simple_indexes] = Annotate::Helpers.true?(ENV['simple_indexes']) 26 | options[:model_dir] = ENV['model_dir'] ? ENV['model_dir'].split(',') : ['app/models'] 27 | options[:root_dir] = ENV['root_dir'] 28 | options[:include_version] = Annotate::Helpers.true?(ENV['include_version']) 29 | options[:require] = ENV['require'] ? ENV['require'].split(',') : [] 30 | options[:exclude_tests] = Annotate::Helpers.true?(ENV['exclude_tests']) 31 | options[:exclude_factories] = Annotate::Helpers.true?(ENV['exclude_factories']) 32 | options[:exclude_fixtures] = Annotate::Helpers.true?(ENV['exclude_fixtures']) 33 | options[:exclude_serializers] = Annotate::Helpers.true?(ENV['exclude_serializers']) 34 | options[:exclude_scaffolds] = Annotate::Helpers.true?(ENV['exclude_scaffolds']) 35 | options[:exclude_controllers] = Annotate::Helpers.true?(ENV.fetch('exclude_controllers', 'true')) 36 | options[:exclude_helpers] = Annotate::Helpers.true?(ENV.fetch('exclude_helpers', 'true')) 37 | options[:exclude_sti_subclasses] = Annotate::Helpers.true?(ENV['exclude_sti_subclasses']) 38 | options[:ignore_model_sub_dir] = Annotate::Helpers.true?(ENV['ignore_model_sub_dir']) 39 | options[:format_bare] = Annotate::Helpers.true?(ENV['format_bare']) 40 | options[:format_rdoc] = Annotate::Helpers.true?(ENV['format_rdoc']) 41 | options[:format_yard] = Annotate::Helpers.true?(ENV['format_yard']) 42 | options[:format_markdown] = Annotate::Helpers.true?(ENV['format_markdown']) 43 | options[:sort] = Annotate::Helpers.true?(ENV['sort']) 44 | options[:force] = Annotate::Helpers.true?(ENV['force']) 45 | options[:frozen] = Annotate::Helpers.true?(ENV['frozen']) 46 | options[:classified_sort] = Annotate::Helpers.true?(ENV['classified_sort']) 47 | options[:trace] = Annotate::Helpers.true?(ENV['trace']) 48 | options[:wrapper_open] = Annotate::Helpers.fallback(ENV['wrapper_open'], ENV['wrapper']) 49 | options[:wrapper_close] = Annotate::Helpers.fallback(ENV['wrapper_close'], ENV['wrapper']) 50 | options[:ignore_columns] = ENV.fetch('ignore_columns', nil) 51 | options[:ignore_routes] = ENV.fetch('ignore_routes', nil) 52 | options[:hide_limit_column_types] = Annotate::Helpers.fallback(ENV['hide_limit_column_types'], '') 53 | options[:hide_default_column_types] = Annotate::Helpers.fallback(ENV['hide_default_column_types'], '') 54 | options[:with_comment] = Annotate::Helpers.true?(ENV['with_comment']) 55 | options[:with_comment_column] = Annotate::Helpers.true?(ENV['with_comment_column']) 56 | options[:ignore_unknown_models] = Annotate::Helpers.true?(ENV.fetch('ignore_unknown_models', 'false')) 57 | 58 | AnnotateModels.do_annotations(options) 59 | end 60 | 61 | desc 'Remove schema information from model and fixture files' 62 | task remove_annotation: :environment do 63 | require "#{annotate_lib}/annotate/annotate_models" 64 | require "#{annotate_lib}/annotate/active_record_patch" 65 | 66 | options = {is_rake: true} 67 | options[:model_dir] = ENV['model_dir'] 68 | options[:root_dir] = ENV['root_dir'] 69 | options[:require] = ENV['require'] ? ENV['require'].split(',') : [] 70 | options[:trace] = Annotate::Helpers.true?(ENV['trace']) 71 | AnnotateModels.remove_annotations(options) 72 | end 73 | -------------------------------------------------------------------------------- /lib/tasks/annotate_models_migrate.rake: -------------------------------------------------------------------------------- 1 | # These tasks are added to the project if you install annotate as a Rails plugin. 2 | # (They are not used to build annotate itself.) 3 | 4 | # Append annotations to Rake tasks for ActiveRecord, so annotate automatically gets 5 | # run after doing db:migrate. 6 | 7 | migration_tasks = %w(db:migrate db:migrate:up db:migrate:down db:migrate:reset db:migrate:redo db:rollback) 8 | if defined?(Rails::Application) && Rails.version.split('.').first.to_i >= 6 9 | require 'active_record' 10 | 11 | databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml 12 | 13 | ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name| 14 | migration_tasks.concat(%w(db:migrate db:migrate:up db:migrate:down).map { |task| "#{task}:#{spec_name}" }) 15 | end 16 | end 17 | 18 | migration_tasks.each do |task| 19 | next unless Rake::Task.task_defined?(task) 20 | 21 | Rake::Task[task].enhance do 22 | Rake::Task[Rake.application.top_level_tasks.last].enhance do 23 | annotation_options_task = if Rake::Task.task_defined?('app:set_annotation_options') 24 | 'app:set_annotation_options' 25 | else 26 | 'set_annotation_options' 27 | end 28 | Rake::Task[annotation_options_task].invoke 29 | Annotate::Migration.update_annotations 30 | end 31 | end 32 | end 33 | 34 | module Annotate 35 | class Migration 36 | @@working = false 37 | 38 | def self.update_annotations 39 | unless @@working || Annotate::Helpers.skip_on_migration? 40 | @@working = true 41 | 42 | self.update_models if Annotate::Helpers.include_models? 43 | self.update_routes if Annotate::Helpers.include_routes? 44 | end 45 | end 46 | 47 | def self.update_models 48 | if Rake::Task.task_defined?("annotate_models") 49 | Rake::Task["annotate_models"].invoke 50 | elsif Rake::Task.task_defined?("app:annotate_models") 51 | Rake::Task["app:annotate_models"].invoke 52 | end 53 | end 54 | 55 | def self.update_routes 56 | if Rake::Task.task_defined?("annotate_routes") 57 | Rake::Task["annotate_routes"].invoke 58 | elsif Rake::Task.task_defined?("app:annotate_routes") 59 | Rake::Task["app:annotate_routes"].invoke 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/tasks/annotate_routes.rake: -------------------------------------------------------------------------------- 1 | annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) 2 | 3 | unless ENV['is_cli'] 4 | task :set_annotation_options 5 | task annotate_routes: :set_annotation_options 6 | end 7 | 8 | desc "Adds the route map to routes.rb" 9 | task :annotate_routes => :environment do 10 | require "#{annotate_lib}/annotate/annotate_routes" 11 | 12 | options={} 13 | ENV['position'] = options[:position] = Annotate::Helpers.fallback(ENV['position'], 'before') 14 | options[:position_in_routes] = Annotate::Helpers.fallback(ENV['position_in_routes'], ENV['position']) 15 | options[:ignore_routes] = Annotate::Helpers.fallback(ENV['ignore_routes'], nil) 16 | options[:require] = ENV['require'] ? ENV['require'].split(',') : [] 17 | options[:frozen] = Annotate::Helpers.true?(ENV['frozen']) 18 | options[:wrapper_open] = Annotate::Helpers.fallback(ENV['wrapper_open'], ENV['wrapper']) 19 | options[:wrapper_close] = Annotate::Helpers.fallback(ENV['wrapper_close'], ENV['wrapper']) 20 | AnnotateRoutes.do_annotations(options) 21 | end 22 | 23 | desc "Removes the route map from routes.rb" 24 | task :remove_routes => :environment do 25 | annotate_lib = File.expand_path(File.dirname(File.dirname(__FILE__))) 26 | require "#{annotate_lib}/annotate/annotate_routes" 27 | 28 | options={} 29 | options[:require] = ENV['require'] ? ENV['require'].split(',') : [] 30 | AnnotateRoutes.remove_annotations(options) 31 | end 32 | -------------------------------------------------------------------------------- /potato.md: -------------------------------------------------------------------------------- 1 | Colons can be used to align columns. 2 | 3 | | Tables | Are | Cool | 4 | | ------------- |:-------------:| -----:| 5 | | col 3 is | right-aligned | $1600 | 6 | | col 2 is | centered | $12 | 7 | | zebra stripes | are neat | $1 | 8 | 9 | There must be at least 3 dashes separating each header cell. 10 | The outer pipes (|) are optional, and you don't need to make the 11 | raw Markdown line up prettily. You can also use inline Markdown. 12 | 13 | Markdown | Less | Pretty 14 | --- | --- | --- 15 | *Still* | `renders` | **nicely** 16 | 1 | 2 | 3 17 | 18 | 19 | ## Route Map 20 | 21 | Prefix | Verb | URI Pattern | Controller#Action 22 | --------- | ---------- | --------------- | -------------------- 23 | myaction1 | GET | /url1(.:format) | mycontroller1#action 24 | myaction2 | POST | /url2(.:format) | mycontroller2#action 25 | myaction3 | DELETE-GET | /url3(.:format) | mycontroller3#action \n") 26 | 27 | 28 | 29 | Table name: `users` 30 | 31 | ### Columns 32 | 33 | Name | Type | Attributes 34 | ----------------------- | ------------------ | --------------------------- 35 | **`id`** | `integer` | `not null, primary key` 36 | **`foreign_thing_id`** | `integer` | `not null` 37 | 38 | ### Foreign Keys 39 | 40 | * `fk_rails_...` (_ON DELETE => on_delete_value ON UPDATE => on_update_value_): 41 | * **`foreign_thing_id => foreign_things.id`** 42 | -------------------------------------------------------------------------------- /spec/lib/annotate/annotate_models/file_patterns_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../spec_helper' 2 | require 'annotate/annotate_models' 3 | 4 | describe AnnotateModels::FilePatterns do 5 | describe '.by_pattern' do 6 | subject { AnnotateModels::FilePatterns.generate(root_directory, pattern_type, options) } 7 | 8 | let(:root_directory) { '/root' } 9 | let(:options) { {} } 10 | 11 | context 'when pattern_type is "test"' do 12 | let(:pattern_type) { 'test' } 13 | 14 | it 'returns patterns of test files' do 15 | is_expected.to eq([ 16 | '/root/test/unit/%MODEL_NAME%_test.rb', 17 | '/root/test/models/%MODEL_NAME%_test.rb', 18 | '/root/spec/models/%MODEL_NAME%_spec.rb' 19 | ]) 20 | end 21 | end 22 | 23 | context 'when pattern_type is "fixture"' do 24 | let(:pattern_type) { 'fixture' } 25 | 26 | it 'returns patterns of fixture files' do 27 | is_expected.to eq([ 28 | '/root/test/fixtures/%TABLE_NAME%.yml', 29 | '/root/spec/fixtures/%TABLE_NAME%.yml', 30 | '/root/test/fixtures/%PLURALIZED_MODEL_NAME%.yml', 31 | '/root/spec/fixtures/%PLURALIZED_MODEL_NAME%.yml' 32 | ]) 33 | end 34 | end 35 | 36 | context 'when pattern_type is "scaffold"' do 37 | let(:pattern_type) { 'scaffold' } 38 | 39 | it 'returns patterns of scaffold files' do 40 | is_expected.to eq([ 41 | '/root/test/controllers/%PLURALIZED_MODEL_NAME%_controller_test.rb', 42 | '/root/spec/controllers/%PLURALIZED_MODEL_NAME%_controller_spec.rb', 43 | '/root/spec/requests/%PLURALIZED_MODEL_NAME%_spec.rb', 44 | '/root/spec/routing/%PLURALIZED_MODEL_NAME%_routing_spec.rb' 45 | ]) 46 | end 47 | end 48 | 49 | context 'when pattern_type is "factory"' do 50 | let(:pattern_type) { 'factory' } 51 | 52 | it 'returns patterns of factory files' do 53 | is_expected.to eq([ 54 | '/root/test/exemplars/%MODEL_NAME%_exemplar.rb', 55 | '/root/spec/exemplars/%MODEL_NAME%_exemplar.rb', 56 | '/root/test/blueprints/%MODEL_NAME%_blueprint.rb', 57 | '/root/spec/blueprints/%MODEL_NAME%_blueprint.rb', 58 | '/root/test/factories/%MODEL_NAME%_factory.rb', 59 | '/root/spec/factories/%MODEL_NAME%_factory.rb', 60 | '/root/test/factories/%TABLE_NAME%.rb', 61 | '/root/spec/factories/%TABLE_NAME%.rb', 62 | '/root/test/factories/%PLURALIZED_MODEL_NAME%.rb', 63 | '/root/spec/factories/%PLURALIZED_MODEL_NAME%.rb', 64 | '/root/test/fabricators/%MODEL_NAME%_fabricator.rb', 65 | '/root/spec/fabricators/%MODEL_NAME%_fabricator.rb' 66 | ]) 67 | end 68 | end 69 | 70 | context 'when pattern_type is "serializer"' do 71 | let(:pattern_type) { 'serializer' } 72 | 73 | it 'returns patterns of serializer files' do 74 | is_expected.to eq([ 75 | '/root/app/serializers/%MODEL_NAME%_serializer.rb', 76 | '/root/test/serializers/%MODEL_NAME%_serializer_test.rb', 77 | '/root/spec/serializers/%MODEL_NAME%_serializer_spec.rb' 78 | ]) 79 | end 80 | end 81 | 82 | context 'when pattern_type is "additional_file_patterns"' do 83 | let(:pattern_type) { 'additional_file_patterns' } 84 | 85 | context 'when additional_file_patterns is specified in the options' do 86 | let(:additional_file_patterns) do 87 | [ 88 | '%PLURALIZED_MODEL_NAME%/**/*.rb', 89 | '%PLURALIZED_MODEL_NAME%/*_form' 90 | ] 91 | end 92 | 93 | let(:options) { { additional_file_patterns: additional_file_patterns } } 94 | 95 | it 'returns additional_file_patterns in the argument "options"' do 96 | is_expected.to eq(additional_file_patterns) 97 | end 98 | end 99 | 100 | context 'when additional_file_patterns is not specified in the options' do 101 | let(:options) { {} } 102 | 103 | it 'returns an empty array' do 104 | is_expected.to eq([]) 105 | end 106 | end 107 | end 108 | 109 | context 'when pattern_type is "controller"' do 110 | let(:pattern_type) { 'controller' } 111 | 112 | it 'returns patterns of controller files' do 113 | is_expected.to eq([ 114 | '/root/app/controllers/%PLURALIZED_MODEL_NAME%_controller.rb' 115 | ]) 116 | end 117 | end 118 | 119 | context 'when pattern_type is "admin"' do 120 | let(:pattern_type) { 'admin' } 121 | 122 | it 'returns both singular and pluralized model names' do 123 | is_expected.to eq(['/root/app/admin/%MODEL_NAME%.rb', '/root/app/admin/%PLURALIZED_MODEL_NAME%.rb']) 124 | end 125 | end 126 | 127 | context 'when pattern_type is "helper"' do 128 | let(:pattern_type) { 'helper' } 129 | 130 | it 'returns patterns of helper files' do 131 | is_expected.to eq([ 132 | '/root/app/helpers/%PLURALIZED_MODEL_NAME%_helper.rb' 133 | ]) 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /spec/lib/annotate/annotate_routes_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | require 'annotate/annotate_routes' 3 | 4 | describe AnnotateRoutes do 5 | ROUTE_FILE = 'config/routes.rb'.freeze 6 | 7 | MESSAGE_ANNOTATED = "#{ROUTE_FILE} was annotated.".freeze 8 | MESSAGE_UNCHANGED = "#{ROUTE_FILE} was not changed.".freeze 9 | MESSAGE_NOT_FOUND = "#{ROUTE_FILE} could not be found.".freeze 10 | MESSAGE_REMOVED = "Annotations were removed from #{ROUTE_FILE}.".freeze 11 | 12 | MAGIC_COMMENTS = [ 13 | '# encoding: UTF-8', 14 | '# coding: UTF-8', 15 | '# -*- coding: UTF-8 -*-', 16 | '#encoding: utf-8', 17 | '# encoding: utf-8', 18 | '# -*- encoding : utf-8 -*-', 19 | "# encoding: utf-8\n# frozen_string_literal: true", 20 | "# frozen_string_literal: true\n# encoding: utf-8", 21 | '# frozen_string_literal: true', 22 | '#frozen_string_literal: false', 23 | '# -*- frozen_string_literal : true -*-' 24 | ].freeze unless const_defined?(:MAGIC_COMMENTS) 25 | 26 | let :stubs do 27 | {} 28 | end 29 | 30 | let :mock_file do 31 | double(File, stubs) 32 | end 33 | 34 | describe '.do_annotations' do 35 | context 'When "config/routes.rb" does not exist' do 36 | before :each do 37 | expect(File).to receive(:exist?).with(ROUTE_FILE).and_return(false).once 38 | end 39 | 40 | it 'does not annotates any file' do 41 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_NOT_FOUND) 42 | 43 | AnnotateRoutes.do_annotations 44 | end 45 | end 46 | 47 | context 'When "config/routes.rb" exists' do 48 | before :each do 49 | expect(File).to receive(:exist?).with(ROUTE_FILE).and_return(true).once 50 | expect(File).to receive(:read).with(ROUTE_FILE).and_return(route_file_content).once 51 | 52 | expect(AnnotateRoutes::HeaderGenerator).to receive(:`).with('rake routes').and_return(rake_routes_result).once 53 | end 54 | 55 | context 'When the result of `rake routes` is present' do 56 | context 'When the result of `rake routes` does not contain Rake version' do 57 | context 'When the file does not contain magic comment' do 58 | let :rake_routes_result do 59 | <<-EOS 60 | Prefix Verb URI Pattern Controller#Action 61 | myaction1 GET /url1(.:format) mycontroller1#action 62 | myaction2 POST /url2(.:format) mycontroller2#action 63 | myaction3 DELETE|GET /url3(.:format) mycontroller3#action 64 | EOS 65 | end 66 | 67 | let :route_file_content do 68 | '' 69 | end 70 | 71 | context 'When the file does not contain annotation yet' do 72 | context 'When no option is passed' do 73 | let :expected_result do 74 | <<~EOS 75 | 76 | # == Route Map 77 | # 78 | # Prefix Verb URI Pattern Controller#Action 79 | # myaction1 GET /url1(.:format) mycontroller1#action 80 | # myaction2 POST /url2(.:format) mycontroller2#action 81 | # myaction3 DELETE|GET /url3(.:format) mycontroller3#action 82 | EOS 83 | end 84 | 85 | it 'annotates normally' do 86 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 87 | expect(mock_file).to receive(:puts).with(expected_result).once 88 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 89 | 90 | AnnotateRoutes.do_annotations 91 | end 92 | end 93 | 94 | context 'When the option "format_markdown" is passed' do 95 | let :expected_result do 96 | <<~EOS 97 | 98 | # ## Route Map 99 | # 100 | # Prefix | Verb | URI Pattern | Controller#Action 101 | # --------- | ---------- | --------------- | -------------------- 102 | # myaction1 | GET | /url1(.:format) | mycontroller1#action 103 | # myaction2 | POST | /url2(.:format) | mycontroller2#action 104 | # myaction3 | DELETE-GET | /url3(.:format) | mycontroller3#action 105 | EOS 106 | end 107 | 108 | it 'annotates in Markdown format' do 109 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 110 | expect(mock_file).to receive(:puts).with(expected_result).once 111 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 112 | 113 | AnnotateRoutes.do_annotations(format_markdown: true) 114 | end 115 | end 116 | 117 | context 'When the options "wrapper_open" and "wrapper_close" are passed' do 118 | let :expected_result do 119 | <<~EOS 120 | 121 | # START 122 | # == Route Map 123 | # 124 | # Prefix Verb URI Pattern Controller#Action 125 | # myaction1 GET /url1(.:format) mycontroller1#action 126 | # myaction2 POST /url2(.:format) mycontroller2#action 127 | # myaction3 DELETE|GET /url3(.:format) mycontroller3#action 128 | # END 129 | EOS 130 | end 131 | 132 | it 'annotates and wraps annotation with specified words' do 133 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 134 | expect(mock_file).to receive(:puts).with(expected_result).once 135 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 136 | 137 | AnnotateRoutes.do_annotations(wrapper_open: 'START', wrapper_close: 'END') 138 | end 139 | end 140 | end 141 | end 142 | 143 | context 'When the file contains magic comments' do 144 | MAGIC_COMMENTS.each do |magic_comment| 145 | describe "magic comment: #{magic_comment.inspect}" do 146 | let :route_file_content do 147 | <<~EOS 148 | #{magic_comment} 149 | EOS 150 | end 151 | 152 | let :rake_routes_result do 153 | <<-EOS 154 | Prefix Verb URI Pattern Controller#Action 155 | myaction1 GET /url1(.:format) mycontroller1#action 156 | myaction2 POST /url2(.:format) mycontroller2#action 157 | myaction3 DELETE|GET /url3(.:format) mycontroller3#action 158 | EOS 159 | end 160 | 161 | context 'When the file does not contain annotation yet' do 162 | context 'When no option is passed' do 163 | let :expected_result do 164 | <<~EOS 165 | #{magic_comment} 166 | 167 | # == Route Map 168 | # 169 | # Prefix Verb URI Pattern Controller#Action 170 | # myaction1 GET /url1(.:format) mycontroller1#action 171 | # myaction2 POST /url2(.:format) mycontroller2#action 172 | # myaction3 DELETE|GET /url3(.:format) mycontroller3#action 173 | EOS 174 | end 175 | 176 | it 'annotates normally' do 177 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 178 | expect(mock_file).to receive(:puts).with(expected_result).once 179 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 180 | 181 | AnnotateRoutes.do_annotations 182 | end 183 | end 184 | 185 | context 'When the option "format_markdown" is passed' do 186 | let :expected_result do 187 | <<~EOS 188 | #{magic_comment} 189 | 190 | # ## Route Map 191 | # 192 | # Prefix | Verb | URI Pattern | Controller#Action 193 | # --------- | ---------- | --------------- | -------------------- 194 | # myaction1 | GET | /url1(.:format) | mycontroller1#action 195 | # myaction2 | POST | /url2(.:format) | mycontroller2#action 196 | # myaction3 | DELETE-GET | /url3(.:format) | mycontroller3#action 197 | EOS 198 | end 199 | 200 | it 'annotates in Markdown format' do 201 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 202 | expect(mock_file).to receive(:puts).with(expected_result).once 203 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 204 | 205 | AnnotateRoutes.do_annotations(format_markdown: true) 206 | end 207 | end 208 | 209 | context 'When the options "wrapper_open" and "wrapper_close" are passed' do 210 | let :expected_result do 211 | <<~EOS 212 | #{magic_comment} 213 | 214 | # START 215 | # == Route Map 216 | # 217 | # Prefix Verb URI Pattern Controller#Action 218 | # myaction1 GET /url1(.:format) mycontroller1#action 219 | # myaction2 POST /url2(.:format) mycontroller2#action 220 | # myaction3 DELETE|GET /url3(.:format) mycontroller3#action 221 | # END 222 | EOS 223 | end 224 | 225 | it 'annotates and wraps annotation with specified words' do 226 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 227 | expect(mock_file).to receive(:puts).with(expected_result).once 228 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 229 | 230 | AnnotateRoutes.do_annotations(wrapper_open: 'START', wrapper_close: 'END') 231 | end 232 | end 233 | end 234 | end 235 | end 236 | end 237 | end 238 | 239 | context 'When the result of `rake routes` contains Rake version' do 240 | context 'with older Rake versions' do 241 | let :rake_routes_result do 242 | <<~EOS.chomp 243 | (in /bad/line) 244 | good line 245 | EOS 246 | end 247 | 248 | context 'When the route file does not end with an empty line' do 249 | let :route_file_content do 250 | <<~EOS.chomp 251 | ActionController::Routing... 252 | foo 253 | EOS 254 | end 255 | 256 | let :expected_result do 257 | <<~EOS 258 | ActionController::Routing... 259 | foo 260 | 261 | # == Route Map 262 | # 263 | # good line 264 | EOS 265 | end 266 | 267 | it 'annotates with an empty line' do 268 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 269 | expect(mock_file).to receive(:puts).with(expected_result).once 270 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 271 | 272 | AnnotateRoutes.do_annotations 273 | end 274 | end 275 | 276 | context 'When the route file ends with an empty line' do 277 | let :route_file_content do 278 | <<~EOS 279 | ActionController::Routing... 280 | foo 281 | EOS 282 | end 283 | 284 | let :expected_result do 285 | <<~EOS 286 | ActionController::Routing... 287 | foo 288 | 289 | # == Route Map 290 | # 291 | # good line 292 | EOS 293 | end 294 | 295 | it 'annotates without an empty line' do 296 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 297 | expect(mock_file).to receive(:puts).with(expected_result).once 298 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 299 | 300 | AnnotateRoutes.do_annotations 301 | end 302 | end 303 | end 304 | 305 | context 'with newer Rake versions' do 306 | let :rake_routes_result do 307 | <<~EOS.chomp 308 | another good line 309 | good line 310 | EOS 311 | end 312 | 313 | context 'When the route file does not end with an empty line' do 314 | context 'When no option is passed' do 315 | let :route_file_content do 316 | <<~EOS.chomp 317 | ActionController::Routing... 318 | foo 319 | EOS 320 | end 321 | 322 | let :expected_result do 323 | <<~EOS 324 | ActionController::Routing... 325 | foo 326 | 327 | # == Route Map 328 | # 329 | # another good line 330 | # good line 331 | EOS 332 | end 333 | 334 | it 'annotates with an empty line' do 335 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 336 | expect(mock_file).to receive(:puts).with(expected_result).once 337 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 338 | 339 | AnnotateRoutes.do_annotations 340 | end 341 | end 342 | end 343 | 344 | context 'When the route file ends with an empty line' do 345 | let :route_file_content do 346 | <<~EOS 347 | ActionController::Routing... 348 | foo 349 | EOS 350 | end 351 | 352 | let :expected_result do 353 | <<~EOS 354 | ActionController::Routing... 355 | foo 356 | 357 | # == Route Map 358 | # 359 | # another good line 360 | # good line 361 | EOS 362 | end 363 | 364 | it 'annotates without an empty line' do 365 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 366 | expect(mock_file).to receive(:puts).with(expected_result).once 367 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 368 | 369 | AnnotateRoutes.do_annotations 370 | end 371 | end 372 | 373 | context 'When option "timestamp" is passed' do 374 | let :route_file_content do 375 | <<~EOS.chomp 376 | ActionController::Routing... 377 | foo 378 | EOS 379 | end 380 | 381 | let :expected_result do 382 | /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/ 383 | end 384 | 385 | it 'annotates with the timestamp and an empty line' do 386 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 387 | expect(mock_file).to receive(:puts).with(expected_result).once 388 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 389 | 390 | AnnotateRoutes.do_annotations timestamp: true 391 | end 392 | end 393 | end 394 | end 395 | end 396 | 397 | context 'When the result of `rake routes` is blank' do 398 | let :rake_routes_result do 399 | '' 400 | end 401 | 402 | context 'When the file does not contain magic comment' do 403 | context 'When the file does not contain annotation yet' do 404 | let :route_file_content do 405 | '' 406 | end 407 | 408 | context 'When no option is specified' do 409 | let :expected_result do 410 | <<~EOS 411 | 412 | # == Route Map 413 | # 414 | EOS 415 | end 416 | 417 | it 'inserts annotations' do 418 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 419 | expect(mock_file).to receive(:puts).with(expected_result).once 420 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 421 | 422 | AnnotateRoutes.do_annotations 423 | end 424 | end 425 | 426 | context 'When the option "ignore_routes" is specified' do 427 | let :expected_result do 428 | <<~EOS 429 | 430 | # == Route Map 431 | # 432 | EOS 433 | end 434 | 435 | it 'inserts annotations' do 436 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 437 | expect(mock_file).to receive(:puts).with(expected_result).once 438 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 439 | 440 | AnnotateRoutes.do_annotations(ignore_routes: 'my_route') 441 | end 442 | end 443 | 444 | context 'When the option "position_in_routes" is specified as "top"' do 445 | let :expected_result do 446 | <<~EOS 447 | # == Route Map 448 | # 449 | EOS 450 | end 451 | 452 | it 'inserts annotations' do 453 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 454 | expect(mock_file).to receive(:puts).with(expected_result).once 455 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 456 | 457 | AnnotateRoutes.do_annotations(position_in_routes: 'top') 458 | end 459 | end 460 | end 461 | 462 | context 'When the file already contains annotation' do 463 | context 'When no option is specified' do 464 | let :route_file_content do 465 | <<~EOS 466 | 467 | # == Route Map 468 | # 469 | EOS 470 | end 471 | 472 | it 'should skip annotations if file does already contain annotation' do 473 | expect(File).not_to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) 474 | expect(mock_file).not_to receive(:puts) 475 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_UNCHANGED).once 476 | 477 | AnnotateRoutes.do_annotations 478 | end 479 | end 480 | end 481 | end 482 | 483 | context 'When the file contains magic comments' do 484 | MAGIC_COMMENTS.each do |magic_comment| 485 | describe "magic comment: #{magic_comment.inspect}" do 486 | let :route_file_content do 487 | <<~EOS 488 | #{magic_comment} 489 | Something 490 | EOS 491 | end 492 | 493 | context 'When the file does not contain annotation yet' do 494 | context 'When the option "position_in_routes" is specified as "top"' do 495 | let :expected_result do 496 | <<~EOS 497 | #{magic_comment} 498 | 499 | # == Route Map 500 | # 501 | 502 | Something 503 | EOS 504 | end 505 | 506 | it 'leaves magic comment on top and adds an empty line between magic comment and annotation' do 507 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 508 | expect(mock_file).to receive(:puts).with(expected_result).once 509 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 510 | 511 | AnnotateRoutes.do_annotations(position_in_routes: 'top') 512 | end 513 | end 514 | 515 | context 'When the option "position_in_routes" is specified as "bottom"' do 516 | let :expected_result do 517 | <<~EOS 518 | #{magic_comment} 519 | Something 520 | 521 | # == Route Map 522 | # 523 | EOS 524 | end 525 | 526 | it 'leaves magic comment on top and adds an empty line between magic comment and annotation' do 527 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 528 | expect(mock_file).to receive(:puts).with(expected_result).once 529 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_ANNOTATED).once 530 | 531 | AnnotateRoutes.do_annotations(position_in_routes: 'bottom') 532 | end 533 | end 534 | end 535 | 536 | context 'When the file already contains annotation' do 537 | let :route_file_content do 538 | <<~EOS 539 | #{magic_comment} 540 | 541 | # == Route Map 542 | # 543 | EOS 544 | end 545 | 546 | it 'skips annotations' do 547 | expect(File).not_to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file) 548 | expect(mock_file).not_to receive(:puts) 549 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_UNCHANGED).once 550 | 551 | AnnotateRoutes.do_annotations 552 | end 553 | end 554 | end 555 | end 556 | end 557 | end 558 | end 559 | 560 | describe 'frozen option' do 561 | let :aborted_message do 562 | "annotate error. #{ROUTE_FILE} needs to be updated, but annotate was run with `--frozen`." 563 | end 564 | 565 | let :rake_routes_result do 566 | <<-EOS 567 | Prefix Verb URI Pattern Controller#Action 568 | myaction1 GET /url1(.:format) mycontroller1#action 569 | myaction2 POST /url2(.:format) mycontroller2#action 570 | myaction3 DELETE|GET /url3(.:format) mycontroller3#action 571 | EOS 572 | end 573 | 574 | before :each do 575 | expect(File).to receive(:exist?).with(ROUTE_FILE).and_return(true).once 576 | expect(File).to receive(:read).with(ROUTE_FILE).and_return(route_file_content).once 577 | 578 | expect(AnnotateRoutes::HeaderGenerator).to receive(:`).with('rake routes').and_return(rake_routes_result).once 579 | end 580 | 581 | context 'when annotation does not exists' do 582 | let :route_file_content do 583 | '' 584 | end 585 | 586 | it 'aborts' do 587 | expect { AnnotateRoutes.do_annotations(frozen: true) }.to raise_error SystemExit, aborted_message 588 | end 589 | end 590 | 591 | context 'when annotation exists but is not updated' do 592 | let :route_file_content do 593 | <<~EOS 594 | # == Route Map 595 | # 596 | # Prefix Verb URI Pattern Controller#Action 597 | # myaction2 POST /url2(.:format) mycontroller2#action 598 | # myaction3 DELETE|GET /url3(.:format) mycontroller3#action 599 | EOS 600 | end 601 | 602 | it 'aborts' do 603 | expect { AnnotateRoutes.do_annotations(frozen: true) }.to raise_error SystemExit, aborted_message 604 | end 605 | end 606 | 607 | context 'when annotation exists and is already updated' do 608 | let :route_file_content do 609 | <<~EOS 610 | # == Route Map 611 | # 612 | # Prefix Verb URI Pattern Controller#Action 613 | # myaction1 GET /url1(.:format) mycontroller1#action 614 | # myaction2 POST /url2(.:format) mycontroller2#action 615 | # myaction3 DELETE|GET /url3(.:format) mycontroller3#action 616 | EOS 617 | end 618 | 619 | it 'does NOT abort' do 620 | expect { AnnotateRoutes.do_annotations(frozen: true) }.not_to raise_error 621 | end 622 | end 623 | end 624 | end 625 | 626 | describe '.remove_annotations' do 627 | before :each do 628 | expect(File).to receive(:exist?).with(ROUTE_FILE).and_return(true).once 629 | expect(File).to receive(:read).with(ROUTE_FILE).and_return(route_file_content).once 630 | expect(File).to receive(:open).with(ROUTE_FILE, 'wb').and_yield(mock_file).once 631 | end 632 | 633 | context 'When trailing annotation exists' do 634 | let :route_file_content do 635 | <<~EOS 636 | 637 | 638 | 639 | ActionController::Routing... 640 | foo 641 | 642 | 643 | # == Route Map 644 | # 645 | # another good line 646 | # good line 647 | EOS 648 | end 649 | 650 | let :expected_result do 651 | <<~EOS 652 | 653 | 654 | 655 | ActionController::Routing... 656 | foo 657 | EOS 658 | end 659 | 660 | it 'removes trailing annotation and trim trailing newlines, but leave leading newlines alone' do 661 | expect(mock_file).to receive(:puts).with(expected_result).once 662 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_REMOVED).once 663 | 664 | AnnotateRoutes.remove_annotations 665 | end 666 | end 667 | 668 | context 'When prepended annotation exists' do 669 | let :route_file_content do 670 | <<~EOS 671 | # == Route Map 672 | # 673 | # another good line 674 | # good line 675 | 676 | 677 | 678 | 679 | Rails.application.routes.draw do 680 | root 'root#index' 681 | end 682 | 683 | 684 | 685 | EOS 686 | end 687 | 688 | let :expected_result do 689 | <<~EOS 690 | Rails.application.routes.draw do 691 | root 'root#index' 692 | end 693 | 694 | 695 | 696 | EOS 697 | end 698 | 699 | it 'removes prepended annotation and trim leading newlines, but leave trailing newlines alone' do 700 | expect(mock_file).to receive(:puts).with(expected_result).once 701 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_REMOVED).once 702 | 703 | AnnotateRoutes.remove_annotations 704 | end 705 | end 706 | 707 | context 'When custom comments are above route map' do 708 | let :route_file_content do 709 | <<~EOS 710 | # My comment 711 | # == Route Map 712 | # 713 | # another good line 714 | # good line 715 | Rails.application.routes.draw do 716 | root 'root#index' 717 | end 718 | EOS 719 | end 720 | 721 | let :expected_result do 722 | <<~EOS 723 | # My comment 724 | Rails.application.routes.draw do 725 | root 'root#index' 726 | end 727 | EOS 728 | end 729 | 730 | it 'does not remove custom comments above route map' do 731 | expect(mock_file).to receive(:puts).with(expected_result).once 732 | expect(AnnotateRoutes).to receive(:puts).with(MESSAGE_REMOVED).once 733 | 734 | AnnotateRoutes.remove_annotations 735 | end 736 | end 737 | end 738 | end 739 | -------------------------------------------------------------------------------- /spec/lib/annotate/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | RSpec.describe Annotate::Helpers do 4 | describe '.skip_on_migration?' do 5 | subject { described_class.skip_on_migration? } 6 | 7 | before do 8 | allow(ENV).to receive(:[]).and_return(nil) 9 | end 10 | 11 | it { is_expected.to be_falsy } 12 | 13 | context "when ENV['ANNOTATE_SKIP_ON_DB_MIGRATE'] is set" do 14 | let(:key) { 'ANNOTATE_SKIP_ON_DB_MIGRATE' } 15 | let(:env_value) { '1' } 16 | 17 | before do 18 | allow(ENV).to receive(:[]).with(key).and_return(env_value) 19 | end 20 | 21 | it { is_expected.to be_truthy } 22 | end 23 | 24 | context "when ENV['skip_on_db_migrate'] is set" do 25 | let(:key) { 'skip_on_db_migrate' } 26 | let(:env_value) { '1' } 27 | 28 | before do 29 | allow(ENV).to receive(:[]).with(key).and_return(env_value) 30 | end 31 | 32 | it { is_expected.to be_truthy } 33 | end 34 | end 35 | 36 | describe '.include_routes?' do 37 | subject { described_class.include_routes? } 38 | 39 | before do 40 | allow(ENV).to receive(:[]).and_return(nil) 41 | end 42 | 43 | it { is_expected.to be_falsy } 44 | 45 | context "when ENV['routes'] is set" do 46 | let(:key) { 'routes' } 47 | let(:env_value) { '1' } 48 | 49 | before do 50 | allow(ENV).to receive(:[]).with(key).and_return(env_value) 51 | end 52 | 53 | it { is_expected.to be_truthy } 54 | end 55 | end 56 | 57 | describe '.include_models?' do 58 | subject { described_class.include_models? } 59 | 60 | before do 61 | allow(ENV).to receive(:[]).and_return(nil) 62 | end 63 | 64 | it { is_expected.to be_falsy } 65 | 66 | context "when ENV['models'] is set" do 67 | let(:key) { 'models' } 68 | let(:env_value) { '1' } 69 | 70 | before do 71 | allow(ENV).to receive(:[]).with(key).and_return(env_value) 72 | end 73 | 74 | it { is_expected.to be_truthy } 75 | end 76 | end 77 | 78 | describe '.true?' do 79 | subject { described_class.true?(val) } 80 | 81 | let(:val) { nil } 82 | it { is_expected.to be_falsy } 83 | 84 | context 'when val is blank' do 85 | let(:val) { '' } 86 | 87 | it { is_expected.to be_falsy } 88 | end 89 | 90 | context 'when it matches the regex' do 91 | valid_truthy_values = %w[true t yes y 1] 92 | 93 | valid_truthy_values.each do |truthy_value| 94 | let(:val) { truthy_value } 95 | 96 | it "returns truthy for '#{truthy_value}'" do 97 | is_expected.to be_truthy 98 | end 99 | end 100 | end 101 | end 102 | 103 | describe '.fallback' do 104 | subject { described_class.fallback(*args) } 105 | let(:args) { [arg_1, arg_2] } 106 | 107 | let(:arg_1) { '' } # is considered blank 108 | let(:arg_2) { 'yes' } 109 | 110 | it 'returns the first non-blank argument' do 111 | is_expected.to eq(arg_2) 112 | end 113 | 114 | context 'when the first argument is non-blank' do 115 | let(:arg_1) { 'yes' } 116 | let(:arg_2) { 'no' } 117 | 118 | it { is_expected.to eq(arg_1) } 119 | end 120 | end 121 | 122 | describe '.reset_options' do 123 | subject { described_class.reset_options(options) } 124 | 125 | let(:options) { [included_option] } 126 | let(:included_option) { :some_key } 127 | let(:excluded_option) { :yet_another_key } 128 | let(:reset_value) { nil } 129 | 130 | before do 131 | allow(ENV).to receive(:[]=) 132 | end 133 | 134 | it 'resets ENV value' do 135 | expect(ENV).to receive(:[]=).with(included_option.to_s, reset_value) 136 | expect(ENV).to_not receive(:[]=).with(excluded_option.to_s, reset_value) 137 | 138 | subject 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /spec/lib/annotate/parser_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module Annotate # rubocop:disable Metrics/ModuleLength 4 | describe Parser do 5 | before(:example) do 6 | ENV.clear 7 | end 8 | 9 | context 'when given empty args' do 10 | it 'returns an options hash with defaults' do 11 | result = Parser.parse([]) 12 | expect(result).to be_a(Hash) 13 | expect(result).to include(target_action: :do_annotations) 14 | end 15 | end 16 | 17 | %w[--additional-file-patterns].each do |option| 18 | describe option do 19 | it 'sets array of paths to :additional_file_patterns' do 20 | paths = 'foo/bar,baz' 21 | allow(ENV).to receive(:[]=) 22 | Parser.parse([option, paths]) 23 | expect(ENV).to have_received(:[]=).with('additional_file_patterns', ['foo/bar', 'baz']) 24 | end 25 | end 26 | end 27 | 28 | %w[-d --delete].each do |option| 29 | describe option do 30 | it 'sets target_action to :remove_annotations' do 31 | result = Parser.parse([option]) 32 | expect(result).to include(target_action: :remove_annotations) 33 | end 34 | end 35 | end 36 | 37 | %w[-p --position].each do |option| 38 | describe option do 39 | Parser::ANNOTATION_POSITIONS.each do |position| 40 | context "when specifying #{position}" do 41 | it "#{position} position is an option" do 42 | allow(ENV).to receive(:[]=) 43 | Parser.parse([option, position]) 44 | expect(Parser::ANNOTATION_POSITIONS).to include(position) 45 | end 46 | 47 | it "sets ENV['position'] to be position" do 48 | allow(ENV).to receive(:[]=) 49 | Parser.parse([option, position]) 50 | 51 | expect(ENV).to have_received(:[]=).with('position', position) 52 | end 53 | 54 | it 'sets the value in ENV for the different file types' do 55 | allow(ENV).to receive(:[]=) 56 | Parser.parse([option, position]) 57 | 58 | Parser::FILE_TYPE_POSITIONS.each do |file_type| 59 | expect(ENV).to have_received(:[]=).with(file_type, position) 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | 67 | context 'when position_in_class is set to top' do 68 | context 'and when position is a different value' do 69 | it 'does not override' do 70 | other_commands = %w[--pc top] 71 | position_command = %w[-p bottom] 72 | options = other_commands + position_command 73 | 74 | Parser.parse(options) 75 | expect(ENV['position_in_class']).to eq('top') 76 | expect(ENV['position']).to eq('bottom') 77 | end 78 | end 79 | end 80 | 81 | %w[--pc --position-in-class].each do |option| 82 | describe option do 83 | let(:env_key) { 'position_in_class' } 84 | 85 | Parser::ANNOTATION_POSITIONS.each do |position| 86 | context "when specifying '#{position}'" do 87 | it "sets the ENV variable to '#{position}'" do 88 | allow(ENV).to receive(:[]=) 89 | Parser.parse([option, position]) 90 | expect(ENV).to have_received(:[]=).with(env_key, position) 91 | end 92 | end 93 | end 94 | end 95 | end 96 | 97 | %w[--pf --position-in-factory].each do |option| 98 | describe option do 99 | let(:env_key) { 'position_in_factory' } 100 | 101 | Parser::ANNOTATION_POSITIONS.each do |position| 102 | context "when specifying #{position}" do 103 | it "sets the ENV variable to #{position}" do 104 | allow(ENV).to receive(:[]=) 105 | Parser.parse([option, position]) 106 | expect(ENV).to have_received(:[]=).with(env_key, position) 107 | end 108 | end 109 | end 110 | end 111 | end 112 | 113 | %w[--px --position-in-fixture].each do |option| 114 | describe option do 115 | let(:env_key) { 'position_in_fixture' } 116 | 117 | Parser::ANNOTATION_POSITIONS.each do |position| 118 | context "when specifying #{position}" do 119 | it "sets the ENV variable to #{position}" do 120 | allow(ENV).to receive(:[]=) 121 | Parser.parse([option, position]) 122 | expect(ENV).to have_received(:[]=).with(env_key, position) 123 | end 124 | end 125 | end 126 | end 127 | end 128 | 129 | %w[--pt --position-in-test].each do |option| 130 | describe option do 131 | let(:env_key) { 'position_in_test' } 132 | 133 | Parser::ANNOTATION_POSITIONS.each do |position| 134 | context "when specifying #{position}" do 135 | it "sets the ENV variable to #{position}" do 136 | allow(ENV).to receive(:[]=) 137 | Parser.parse([option, position]) 138 | expect(ENV).to have_received(:[]=).with(env_key, position) 139 | end 140 | end 141 | end 142 | end 143 | end 144 | 145 | %w[--pr --position-in-routes].each do |option| 146 | describe option do 147 | let(:env_key) { 'position_in_routes' } 148 | 149 | Parser::ANNOTATION_POSITIONS.each do |position| 150 | context "when specifying #{position}" do 151 | it "sets the ENV variable to #{position}" do 152 | allow(ENV).to receive(:[]=) 153 | Parser.parse([option, position]) 154 | expect(ENV).to have_received(:[]=).with(env_key, position) 155 | end 156 | end 157 | end 158 | end 159 | end 160 | 161 | %w[--ps --position-in-serializer].each do |option| 162 | describe option do 163 | let(:env_key) { 'position_in_serializer' } 164 | 165 | Parser::ANNOTATION_POSITIONS.each do |position| 166 | context "when specifying #{position}" do 167 | it "sets the ENV variable to #{position}" do 168 | allow(ENV).to receive(:[]=) 169 | Parser.parse([option, position]) 170 | expect(ENV).to have_received(:[]=).with(env_key, position) 171 | end 172 | end 173 | end 174 | end 175 | end 176 | 177 | %w[--w --wrapper].each do |option| 178 | describe option do 179 | let(:env_key) { 'wrapper' } 180 | let(:set_value) { 'STR' } 181 | it 'sets the ENV variable' do 182 | expect(ENV).to receive(:[]=).with(env_key, set_value) 183 | Parser.parse([option, set_value]) 184 | end 185 | end 186 | end 187 | 188 | %w[--wo --wrapper-open].each do |option| 189 | describe option do 190 | let(:env_key) { 'wrapper_open' } 191 | let(:set_value) { 'STR' } 192 | it 'sets the ENV variable' do 193 | expect(ENV).to receive(:[]=).with(env_key, set_value) 194 | Parser.parse([option, set_value]) 195 | end 196 | end 197 | end 198 | 199 | %w[--wc --wrapper-close].each do |option| 200 | describe option do 201 | let(:env_key) { 'wrapper_close' } 202 | let(:set_value) { 'STR' } 203 | it 'sets the ENV variable' do 204 | expect(ENV).to receive(:[]=).with(env_key, set_value) 205 | Parser.parse([option, set_value]) 206 | end 207 | end 208 | end 209 | 210 | %w[-r --routes].each do |option| 211 | describe option do 212 | let(:env_key) { 'routes' } 213 | let(:set_value) { 'true' } 214 | it 'sets the ENV variable' do 215 | expect(ENV).to receive(:[]=).with(env_key, set_value) 216 | Parser.parse([option]) 217 | end 218 | end 219 | end 220 | 221 | %w[--models].each do |option| 222 | describe option do 223 | let(:env_key) { 'models' } 224 | let(:set_value) { 'true' } 225 | it 'sets the ENV variable' do 226 | expect(ENV).to receive(:[]=).with(env_key, set_value) 227 | Parser.parse([option]) 228 | end 229 | end 230 | end 231 | 232 | %w[-a --active-admin].each do |option| 233 | describe option do 234 | let(:env_key) { 'active_admin' } 235 | let(:set_value) { 'true' } 236 | it 'sets the ENV variable' do 237 | expect(ENV).to receive(:[]=).with(env_key, set_value) 238 | Parser.parse([option]) 239 | end 240 | end 241 | end 242 | 243 | %w[-v --version].each do |option| 244 | describe option do 245 | it 'sets the ENV variable' do 246 | expect { Parser.parse([option]) }.to output("annotate v#{Annotate.version}\n").to_stdout 247 | expect(Parser.parse([option])).to include(exit: true) 248 | end 249 | end 250 | end 251 | 252 | %w[-m --show-migration].each do |option| 253 | describe option do 254 | let(:env_key) { 'include_version' } 255 | let(:set_value) { 'yes' } 256 | it 'sets the ENV variable' do 257 | expect(ENV).to receive(:[]=).with(env_key, set_value) 258 | Parser.parse([option]) 259 | end 260 | end 261 | end 262 | 263 | %w[-c --show-check-constraints].each do |option| 264 | describe option do 265 | let(:env_key) { 'show_check_constraints' } 266 | let(:set_value) { 'yes' } 267 | it 'sets the ENV variable' do 268 | expect(ENV).to receive(:[]=).with(env_key, set_value) 269 | Parser.parse([option]) 270 | end 271 | end 272 | end 273 | 274 | %w[-k --show-foreign-keys].each do |option| 275 | describe option do 276 | let(:env_key) { 'show_foreign_keys' } 277 | let(:set_value) { 'yes' } 278 | it 'sets the ENV variable' do 279 | expect(ENV).to receive(:[]=).with(env_key, set_value) 280 | Parser.parse([option]) 281 | end 282 | end 283 | end 284 | 285 | %w[--ck --complete-foreign-keys].each do |option| 286 | describe option do 287 | it 'sets the ENV variable' do 288 | allow(ENV).to receive(:[]=) 289 | Parser.parse([option]) 290 | 291 | expect(ENV).to have_received(:[]=).with('show_foreign_keys', 'yes') 292 | expect(ENV).to have_received(:[]=).with('show_complete_foreign_keys', 'yes') 293 | end 294 | end 295 | end 296 | 297 | %w[-i --show-indexes].each do |option| 298 | describe option do 299 | let(:env_key) { 'show_indexes' } 300 | let(:set_value) { 'yes' } 301 | it 'sets the ENV variable' do 302 | expect(ENV).to receive(:[]=).with(env_key, set_value) 303 | Parser.parse([option]) 304 | end 305 | end 306 | end 307 | 308 | %w[-s --simple-indexes].each do |option| 309 | describe option do 310 | let(:env_key) { 'simple_indexes' } 311 | let(:set_value) { 'yes' } 312 | it 'sets the ENV variable' do 313 | expect(ENV).to receive(:[]=).with(env_key, set_value) 314 | Parser.parse([option]) 315 | end 316 | end 317 | end 318 | 319 | describe '--model-dir' do 320 | let(:option) { '--model-dir' } 321 | let(:env_key) { 'model_dir' } 322 | let(:set_value) { 'some_dir/' } 323 | it 'sets the ENV variable' do 324 | expect(ENV).to receive(:[]=).with(env_key, set_value) 325 | Parser.parse([option, set_value]) 326 | end 327 | end 328 | 329 | describe '--root-dir' do 330 | let(:option) { '--root-dir' } 331 | let(:env_key) { 'root_dir' } 332 | let(:set_value) { 'some_dir/' } 333 | it 'sets the ENV variable' do 334 | expect(ENV).to receive(:[]=).with(env_key, set_value) 335 | Parser.parse([option, set_value]) 336 | end 337 | end 338 | 339 | describe '--ignore-model-subdirects' do 340 | let(:option) { '--ignore-model-subdirects' } 341 | let(:env_key) { 'ignore_model_sub_dir' } 342 | let(:set_value) { 'yes' } 343 | it 'sets the ENV variable' do 344 | expect(ENV).to receive(:[]=).with(env_key, set_value) 345 | Parser.parse([option]) 346 | end 347 | end 348 | 349 | describe '--sort' do 350 | let(:option) { '--sort' } 351 | let(:env_key) { 'sort' } 352 | let(:set_value) { 'yes' } 353 | it 'sets the ENV variable' do 354 | expect(ENV).to receive(:[]=).with(env_key, set_value) 355 | Parser.parse([option]) 356 | end 357 | end 358 | 359 | describe '--classified-sort' do 360 | let(:option) { '--classified-sort' } 361 | let(:env_key) { 'classified_sort' } 362 | let(:set_value) { 'yes' } 363 | it 'sets the ENV variable' do 364 | expect(ENV).to receive(:[]=).with(env_key, set_value) 365 | Parser.parse([option]) 366 | end 367 | end 368 | 369 | %w[-R --require].each do |option| 370 | describe option do 371 | let(:env_key) { 'require' } 372 | let(:set_value) { 'another_dir' } 373 | it 'sets the ENV variable' do 374 | expect(ENV).to receive(:[]=).with(env_key, set_value) 375 | Parser.parse([option, set_value]) 376 | end 377 | 378 | context "when ENV['require'] is already set" do 379 | let(:preset_require_value) { 'some_dir/' } 380 | it "appends the path to ENV['require']" do 381 | env = { 'require' => preset_require_value } 382 | expect(ENV).to receive(:[]=).with(env_key, "#{preset_require_value},#{set_value}") 383 | Parser.parse([option, set_value], env) 384 | end 385 | end 386 | end 387 | end 388 | 389 | describe 'Parser::EXCLUSION_LIST' do 390 | it "has 'tests'" do 391 | expect(Parser::EXCLUSION_LIST).to include('tests') 392 | end 393 | 394 | it "has 'fixtures'" do 395 | expect(Parser::EXCLUSION_LIST).to include('fixtures') 396 | end 397 | 398 | it "has 'factories'" do 399 | expect(Parser::EXCLUSION_LIST).to include('factories') 400 | end 401 | 402 | it "has 'serializers'" do 403 | expect(Parser::EXCLUSION_LIST).to include('serializers') 404 | end 405 | end 406 | 407 | %w[-e --exclude].each do |option| 408 | describe option do 409 | let(:set_value) { 'yes' } 410 | 411 | it "sets the exclusion ENV variables for 'tests', 'fixtures', 'factories', and 'serializers'" do 412 | allow(ENV).to receive(:[]=) 413 | Parser.parse([option]) 414 | 415 | expect(ENV).to have_received(:[]=).with('exclude_tests', set_value) 416 | expect(ENV).to have_received(:[]=).with('exclude_fixtures', set_value) 417 | expect(ENV).to have_received(:[]=).with('exclude_factories', set_value) 418 | expect(ENV).to have_received(:[]=).with('exclude_serializers', set_value) 419 | end 420 | 421 | context 'when a type is passed in' do 422 | let(:exclusions) { "tests" } 423 | 424 | it "sets the exclusion ENV variable for 'tests' only" do 425 | expect(ENV).to receive(:[]=).with('exclude_tests', set_value) 426 | Parser.parse([option, exclusions]) 427 | end 428 | end 429 | 430 | context 'when two types are passed in' do 431 | let(:exclusions) { "tests,fixtures" } 432 | 433 | it "sets the exclusion ENV variable for 'tests' and 'fixtures'" do 434 | allow(ENV).to receive(:[]=) 435 | Parser.parse([option, exclusions]) 436 | expect(ENV).to have_received(:[]=).with('exclude_tests', set_value) 437 | expect(ENV).to have_received(:[]=).with('exclude_fixtures', set_value) 438 | end 439 | end 440 | end 441 | end 442 | 443 | %w[-f --format].each do |option| 444 | describe option do 445 | Parser::FORMAT_TYPES.each do |format_type| 446 | context "when passing in format type '#{format_type}'" do 447 | let(:env_key) { "format_#{format_type}" } 448 | let(:set_value) { 'yes' } 449 | 450 | it 'sets the ENV variable' do 451 | expect(ENV).to receive(:[]=).with(env_key, set_value) 452 | Parser.parse([option, format_type]) 453 | end 454 | end 455 | end 456 | end 457 | end 458 | 459 | describe '--force' do 460 | let(:option) { '--force' } 461 | let(:env_key) { 'force' } 462 | let(:set_value) { 'yes' } 463 | it 'sets the ENV variable' do 464 | expect(ENV).to receive(:[]=).with(env_key, set_value) 465 | Parser.parse([option]) 466 | end 467 | end 468 | 469 | describe '--frozen' do 470 | let(:option) { '--frozen' } 471 | let(:env_key) { 'frozen' } 472 | let(:set_value) { 'yes' } 473 | it 'sets the ENV variable' do 474 | expect(ENV).to receive(:[]=).with(env_key, set_value) 475 | Parser.parse([option]) 476 | end 477 | end 478 | 479 | describe '--timestamp' do 480 | let(:option) { '--timestamp' } 481 | let(:env_key) { 'timestamp' } 482 | let(:set_value) { 'true' } 483 | it 'sets the ENV variable' do 484 | expect(ENV).to receive(:[]=).with(env_key, set_value) 485 | Parser.parse([option]) 486 | end 487 | end 488 | 489 | describe '--trace' do 490 | let(:option) { '--trace' } 491 | let(:env_key) { 'trace' } 492 | let(:set_value) { 'yes' } 493 | it 'sets the ENV variable' do 494 | expect(ENV).to receive(:[]=).with(env_key, set_value) 495 | Parser.parse([option]) 496 | end 497 | end 498 | 499 | %w[-I --ignore-columns].each do |option| 500 | describe option do 501 | let(:env_key) { 'ignore_columns' } 502 | let(:regex) { '^(id|updated_at|created_at)' } 503 | 504 | it 'sets the ENV variable' do 505 | expect(ENV).to receive(:[]=).with(env_key, regex) 506 | Parser.parse([option, regex]) 507 | end 508 | end 509 | end 510 | 511 | describe '--ignore-routes' do 512 | let(:option) { '--ignore-routes' } 513 | let(:env_key) { 'ignore_routes' } 514 | let(:regex) { '(mobile|resque|pghero)' } 515 | 516 | it 'sets the ENV variable' do 517 | expect(ENV).to receive(:[]=).with(env_key, regex) 518 | Parser.parse([option, regex]) 519 | end 520 | end 521 | 522 | describe '--hide-limit-column-types' do 523 | let(:option) { '--hide-limit-column-types' } 524 | let(:env_key) { 'hide_limit_column_types' } 525 | let(:values) { 'integer,boolean,text' } 526 | 527 | it 'sets the ENV variable' do 528 | expect(ENV).to receive(:[]=).with(env_key, values) 529 | Parser.parse([option, values]) 530 | end 531 | end 532 | 533 | describe '--hide-default-column-types' do 534 | let(:option) { '--hide-default-column-types' } 535 | let(:env_key) { 'hide_default_column_types' } 536 | let(:values) { 'json,jsonb,hstore' } 537 | 538 | it 'sets the ENV variable' do 539 | expect(ENV).to receive(:[]=).with(env_key, values) 540 | Parser.parse([option, values]) 541 | end 542 | end 543 | 544 | describe '--ignore-unknown-models' do 545 | let(:option) { '--ignore-unknown-models' } 546 | let(:env_key) { 'ignore_unknown_models' } 547 | let(:set_value) { 'true' } 548 | it 'sets the ENV variable' do 549 | expect(ENV).to receive(:[]=).with(env_key, set_value) 550 | Parser.parse([option]) 551 | end 552 | end 553 | 554 | describe '--with-comment' do 555 | let(:option) { '--with-comment' } 556 | let(:env_key) { 'with_comment' } 557 | let(:set_value) { 'true' } 558 | it 'sets the ENV variable' do 559 | expect(ENV).to receive(:[]=).with(env_key, set_value) 560 | Parser.parse([option]) 561 | end 562 | end 563 | 564 | describe '--with-comment-column' do 565 | let(:option) { '--with-comment-column' } 566 | let(:env_key) { 'with_comment_column' } 567 | let(:set_value) { 'true' } 568 | it 'sets the ENV variable' do 569 | expect(ENV).to receive(:[]=).with(env_key, set_value) 570 | Parser.parse([option]) 571 | end 572 | end 573 | end 574 | end 575 | -------------------------------------------------------------------------------- /spec/lib/annotate_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | describe Annotate do 4 | describe '.version' do 5 | it 'has version' do 6 | expect(Annotate.version).to be_instance_of(String) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/lib/tasks/annotate_models_migrate_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | describe 'ActiveRecord migration rake task hooks' do 4 | before do 5 | Rake.application = Rake::Application.new 6 | 7 | # Stub migration tasks 8 | %w(db:migrate db:migrate:up db:migrate:down db:migrate:reset db:rollback).each do |task| 9 | Rake::Task.define_task(task) 10 | end 11 | Rake::Task.define_task('db:migrate:redo') do 12 | Rake::Task['db:rollback'].invoke 13 | Rake::Task['db:migrate'].invoke 14 | end 15 | 16 | Rake::Task.define_task('set_annotation_options') 17 | Rake.load_rakefile('tasks/annotate_models_migrate.rake') 18 | 19 | Rake.application.instance_variable_set(:@top_level_tasks, [subject]) 20 | end 21 | 22 | describe 'db:migrate' do 23 | it 'should update annotations' do 24 | expect(Annotate::Migration).to receive(:update_annotations) 25 | Rake.application.top_level 26 | end 27 | end 28 | 29 | describe 'db:migrate:up' do 30 | it 'should update annotations' do 31 | expect(Annotate::Migration).to receive(:update_annotations) 32 | Rake.application.top_level 33 | end 34 | end 35 | 36 | describe 'db:migrate:down' do 37 | it 'should update annotations' do 38 | expect(Annotate::Migration).to receive(:update_annotations) 39 | Rake.application.top_level 40 | end 41 | end 42 | 43 | describe 'db:migrate:reset' do 44 | it 'should update annotations' do 45 | expect(Annotate::Migration).to receive(:update_annotations) 46 | Rake.application.top_level 47 | end 48 | end 49 | 50 | describe 'db:rollback' do 51 | it 'should update annotations' do 52 | expect(Annotate::Migration).to receive(:update_annotations) 53 | Rake.application.top_level 54 | end 55 | end 56 | 57 | describe 'db:migrate:redo' do 58 | it 'should update annotations after all migration tasks' do 59 | allow(Annotate::Migration).to receive(:update_annotations) 60 | 61 | # Confirm that update_annotations isn't called when the original redo task finishes 62 | Rake::Task[subject].enhance do 63 | expect(Annotate::Migration).not_to have_received(:update_annotations) 64 | end 65 | 66 | Rake.application.top_level 67 | 68 | # Hooked 3 times by db:rollback, db:migrate, and db:migrate:redo tasks 69 | expect(Annotate::Migration).to have_received(:update_annotations).exactly(3).times 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/lib/tasks/annotate_models_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | describe 'Annotate annotate_models rake task and Annotate.set_defaults' do # rubocop:disable RSpec/DescribeClass 4 | before do 5 | Rake.application = Rake::Application.new 6 | Rake::Task.define_task('environment') 7 | Rake.load_rakefile('tasks/annotate_models.rake') 8 | end 9 | 10 | after do 11 | Annotate.instance_variable_set('@has_set_defaults', false) 12 | end 13 | 14 | let(:annotate_models_argument) do 15 | argument = nil 16 | allow(AnnotateModels).to receive(:do_annotations) { |arg| argument = arg } 17 | Rake::Task['annotate_models'].invoke 18 | argument 19 | end 20 | 21 | describe 'with_comment_column' do 22 | subject { annotate_models_argument[:with_comment_column] } 23 | 24 | after { ENV.delete('with_comment_column') } 25 | 26 | context 'when Annotate.set_defaults is not called (defaults)' do 27 | it { is_expected.to be_falsey } 28 | end 29 | 30 | context 'when Annotate.set_defaults sets it to "true"' do 31 | before { Annotate.set_defaults('with_comment_column' => 'true') } 32 | 33 | it { is_expected.to be_truthy } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] 2 | require 'coveralls' 3 | require 'codeclimate-test-reporter' 4 | require 'simplecov' 5 | 6 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( 7 | [ 8 | Coveralls::SimpleCov::Formatter, 9 | SimpleCov::Formatter::HTMLFormatter, 10 | CodeClimate::TestReporter::Formatter 11 | ] 12 | ) 13 | 14 | SimpleCov.start 15 | end 16 | 17 | require 'rubygems' 18 | require 'bundler' 19 | Bundler.setup 20 | 21 | require 'rake' 22 | require 'rspec' 23 | 24 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../lib')) 25 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 26 | 27 | require 'active_support' 28 | require 'active_support/core_ext/object/blank' 29 | require 'active_support/core_ext/class/subclasses' 30 | require 'active_support/core_ext/string/inflections' 31 | require 'annotate' 32 | require 'annotate/parser' 33 | require 'annotate/helpers' 34 | require 'annotate/constants' 35 | require 'byebug' 36 | 37 | RSpec.configure do |config| 38 | config.order = 'random' 39 | config.filter_run_when_matching :focus 40 | end 41 | --------------------------------------------------------------------------------