annotaterb 4.0.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE.txt +55 -0
- data/README.md +91 -0
- data/VERSION +1 -0
- data/exe/annotaterb +21 -0
- data/lib/annotate_rb/active_record_patch.rb +9 -0
- data/lib/annotate_rb/commands/annotate_models.rb +22 -0
- data/lib/annotate_rb/commands/annotate_routes.rb +19 -0
- data/lib/annotate_rb/commands/print_help.rb +16 -0
- data/lib/annotate_rb/commands/print_version.rb +12 -0
- data/lib/annotate_rb/commands.rb +10 -0
- data/lib/annotate_rb/config_finder.rb +21 -0
- data/lib/annotate_rb/config_loader.rb +63 -0
- data/lib/annotate_rb/core.rb +23 -0
- data/lib/annotate_rb/eager_loader.rb +23 -0
- data/lib/annotate_rb/env.rb +30 -0
- data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +19 -0
- data/lib/annotate_rb/model_annotator/annotator.rb +74 -0
- data/lib/annotate_rb/model_annotator/bad_model_file_error.rb +11 -0
- data/lib/annotate_rb/model_annotator/constants.rb +22 -0
- data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +25 -0
- data/lib/annotate_rb/model_annotator/file_annotator.rb +79 -0
- data/lib/annotate_rb/model_annotator/file_name_resolver.rb +16 -0
- data/lib/annotate_rb/model_annotator/file_patterns.rb +129 -0
- data/lib/annotate_rb/model_annotator/helper.rb +54 -0
- data/lib/annotate_rb/model_annotator/model_class_getter.rb +63 -0
- data/lib/annotate_rb/model_annotator/model_file_annotator.rb +118 -0
- data/lib/annotate_rb/model_annotator/model_files_getter.rb +62 -0
- data/lib/annotate_rb/model_annotator/pattern_getter.rb +27 -0
- data/lib/annotate_rb/model_annotator/schema_info.rb +480 -0
- data/lib/annotate_rb/model_annotator.rb +20 -0
- data/lib/annotate_rb/options.rb +204 -0
- data/lib/annotate_rb/parser.rb +385 -0
- data/lib/annotate_rb/rake_bootstrapper.rb +34 -0
- data/lib/annotate_rb/route_annotator/annotation_processor.rb +56 -0
- data/lib/annotate_rb/route_annotator/annotator.rb +40 -0
- data/lib/annotate_rb/route_annotator/base_processor.rb +104 -0
- data/lib/annotate_rb/route_annotator/header_generator.rb +113 -0
- data/lib/annotate_rb/route_annotator/helper.rb +104 -0
- data/lib/annotate_rb/route_annotator/removal_processor.rb +40 -0
- data/lib/annotate_rb/route_annotator.rb +12 -0
- data/lib/annotate_rb/runner.rb +34 -0
- data/lib/annotate_rb/tasks/annotate_models_migrate.rake +30 -0
- data/lib/annotate_rb.rb +30 -0
- data/lib/generators/annotate_rb/USAGE +4 -0
- data/lib/generators/annotate_rb/install_generator.rb +15 -0
- data/lib/generators/annotate_rb/templates/auto_annotate_models.rake +7 -0
- metadata +96 -0
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnnotateRb
         | 
| 4 | 
            +
              module ModelAnnotator
         | 
| 5 | 
            +
                class FileNameResolver
         | 
| 6 | 
            +
                  class << self
         | 
| 7 | 
            +
                    def call(filename_template, model_name, table_name)
         | 
| 8 | 
            +
                      filename_template
         | 
| 9 | 
            +
                        .gsub('%MODEL_NAME%', model_name)
         | 
| 10 | 
            +
                        .gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize)
         | 
| 11 | 
            +
                        .gsub('%TABLE_NAME%', table_name || model_name.pluralize)
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,129 @@ | |
| 1 | 
            +
            module AnnotateRb
         | 
| 2 | 
            +
              module ModelAnnotator
         | 
| 3 | 
            +
                # This module provides module method to get file paths.
         | 
| 4 | 
            +
                module FilePatterns
         | 
| 5 | 
            +
                  # Controller files
         | 
| 6 | 
            +
                  CONTROLLER_DIR       = File.join('app', 'controllers')
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  # Active admin registry files
         | 
| 9 | 
            +
                  ACTIVEADMIN_DIR      = File.join('app', 'admin')
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # Helper files
         | 
| 12 | 
            +
                  HELPER_DIR           = File.join('app', 'helpers')
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # File.join for windows reverse bar compat?
         | 
| 15 | 
            +
                  # I dont use windows, can`t test
         | 
| 16 | 
            +
                  UNIT_TEST_DIR        = File.join('test', 'unit')
         | 
| 17 | 
            +
                  MODEL_TEST_DIR       = File.join('test', 'models') # since rails 4.0
         | 
| 18 | 
            +
                  SPEC_MODEL_DIR       = File.join('spec', 'models')
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  FIXTURE_TEST_DIR     = File.join('test', 'fixtures')
         | 
| 21 | 
            +
                  FIXTURE_SPEC_DIR     = File.join('spec', 'fixtures')
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  # Other test files
         | 
| 24 | 
            +
                  CONTROLLER_TEST_DIR  = File.join('test', 'controllers')
         | 
| 25 | 
            +
                  CONTROLLER_SPEC_DIR  = File.join('spec', 'controllers')
         | 
| 26 | 
            +
                  REQUEST_SPEC_DIR     = File.join('spec', 'requests')
         | 
| 27 | 
            +
                  ROUTING_SPEC_DIR     = File.join('spec', 'routing')
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  # Object Daddy http://github.com/flogic/object_daddy/tree/master
         | 
| 30 | 
            +
                  EXEMPLARS_TEST_DIR   = File.join('test', 'exemplars')
         | 
| 31 | 
            +
                  EXEMPLARS_SPEC_DIR   = File.join('spec', 'exemplars')
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # Machinist http://github.com/notahat/machinist
         | 
| 34 | 
            +
                  BLUEPRINTS_TEST_DIR  = File.join('test', 'blueprints')
         | 
| 35 | 
            +
                  BLUEPRINTS_SPEC_DIR  = File.join('spec', 'blueprints')
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  # Factory Bot https://github.com/thoughtbot/factory_bot
         | 
| 38 | 
            +
                  FACTORY_BOT_TEST_DIR = File.join('test', 'factories')
         | 
| 39 | 
            +
                  FACTORY_BOT_SPEC_DIR = File.join('spec', 'factories')
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # Fabrication https://github.com/paulelliott/fabrication.git
         | 
| 42 | 
            +
                  FABRICATORS_TEST_DIR = File.join('test', 'fabricators')
         | 
| 43 | 
            +
                  FABRICATORS_SPEC_DIR = File.join('spec', 'fabricators')
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  # Serializers https://github.com/rails-api/active_model_serializers
         | 
| 46 | 
            +
                  SERIALIZERS_DIR      = File.join('app',  'serializers')
         | 
| 47 | 
            +
                  SERIALIZERS_TEST_DIR = File.join('test', 'serializers')
         | 
| 48 | 
            +
                  SERIALIZERS_SPEC_DIR = File.join('spec', 'serializers')
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  class << self
         | 
| 51 | 
            +
                    def generate(root_directory, pattern_type, options)
         | 
| 52 | 
            +
                      case pattern_type
         | 
| 53 | 
            +
                      when 'test'       then test_files(root_directory)
         | 
| 54 | 
            +
                      when 'fixture'    then fixture_files(root_directory)
         | 
| 55 | 
            +
                      when 'scaffold'   then scaffold_files(root_directory)
         | 
| 56 | 
            +
                      when 'factory'    then factory_files(root_directory)
         | 
| 57 | 
            +
                      when 'serializer' then serialize_files(root_directory)
         | 
| 58 | 
            +
                      when 'additional_file_patterns'
         | 
| 59 | 
            +
                        [options[:additional_file_patterns] || []].flatten
         | 
| 60 | 
            +
                      when 'controller'
         | 
| 61 | 
            +
                        [File.join(root_directory, CONTROLLER_DIR, '%PLURALIZED_MODEL_NAME%_controller.rb')]
         | 
| 62 | 
            +
                      when 'admin'
         | 
| 63 | 
            +
                        [
         | 
| 64 | 
            +
                          File.join(root_directory, ACTIVEADMIN_DIR, '%MODEL_NAME%.rb'),
         | 
| 65 | 
            +
                          File.join(root_directory, ACTIVEADMIN_DIR, '%PLURALIZED_MODEL_NAME%.rb')
         | 
| 66 | 
            +
                        ]
         | 
| 67 | 
            +
                      when 'helper'
         | 
| 68 | 
            +
                        [File.join(root_directory, HELPER_DIR, '%PLURALIZED_MODEL_NAME%_helper.rb')]
         | 
| 69 | 
            +
                      else
         | 
| 70 | 
            +
                        []
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    private
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    def test_files(root_directory)
         | 
| 77 | 
            +
                      [
         | 
| 78 | 
            +
                        File.join(root_directory, UNIT_TEST_DIR,  '%MODEL_NAME%_test.rb'),
         | 
| 79 | 
            +
                        File.join(root_directory, MODEL_TEST_DIR, '%MODEL_NAME%_test.rb'),
         | 
| 80 | 
            +
                        File.join(root_directory, SPEC_MODEL_DIR, '%MODEL_NAME%_spec.rb')
         | 
| 81 | 
            +
                      ]
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def fixture_files(root_directory)
         | 
| 85 | 
            +
                      [
         | 
| 86 | 
            +
                        File.join(root_directory, FIXTURE_TEST_DIR, '%TABLE_NAME%.yml'),
         | 
| 87 | 
            +
                        File.join(root_directory, FIXTURE_SPEC_DIR, '%TABLE_NAME%.yml'),
         | 
| 88 | 
            +
                        File.join(root_directory, FIXTURE_TEST_DIR, '%PLURALIZED_MODEL_NAME%.yml'),
         | 
| 89 | 
            +
                        File.join(root_directory, FIXTURE_SPEC_DIR, '%PLURALIZED_MODEL_NAME%.yml')
         | 
| 90 | 
            +
                      ]
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    def scaffold_files(root_directory)
         | 
| 94 | 
            +
                      [
         | 
| 95 | 
            +
                        File.join(root_directory, CONTROLLER_TEST_DIR, '%PLURALIZED_MODEL_NAME%_controller_test.rb'),
         | 
| 96 | 
            +
                        File.join(root_directory, CONTROLLER_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_controller_spec.rb'),
         | 
| 97 | 
            +
                        File.join(root_directory, REQUEST_SPEC_DIR,    '%PLURALIZED_MODEL_NAME%_spec.rb'),
         | 
| 98 | 
            +
                        File.join(root_directory, ROUTING_SPEC_DIR,    '%PLURALIZED_MODEL_NAME%_routing_spec.rb')
         | 
| 99 | 
            +
                      ]
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    def factory_files(root_directory)
         | 
| 103 | 
            +
                      [
         | 
| 104 | 
            +
                        File.join(root_directory, EXEMPLARS_TEST_DIR,   '%MODEL_NAME%_exemplar.rb'),
         | 
| 105 | 
            +
                        File.join(root_directory, EXEMPLARS_SPEC_DIR,   '%MODEL_NAME%_exemplar.rb'),
         | 
| 106 | 
            +
                        File.join(root_directory, BLUEPRINTS_TEST_DIR,  '%MODEL_NAME%_blueprint.rb'),
         | 
| 107 | 
            +
                        File.join(root_directory, BLUEPRINTS_SPEC_DIR,  '%MODEL_NAME%_blueprint.rb'),
         | 
| 108 | 
            +
                        File.join(root_directory, FACTORY_BOT_TEST_DIR, '%MODEL_NAME%_factory.rb'),    # (old style)
         | 
| 109 | 
            +
                        File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%MODEL_NAME%_factory.rb'),    # (old style)
         | 
| 110 | 
            +
                        File.join(root_directory, FACTORY_BOT_TEST_DIR, '%TABLE_NAME%.rb'),            # (new style)
         | 
| 111 | 
            +
                        File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%TABLE_NAME%.rb'),            # (new style)
         | 
| 112 | 
            +
                        File.join(root_directory, FACTORY_BOT_TEST_DIR, '%PLURALIZED_MODEL_NAME%.rb'), # (new style)
         | 
| 113 | 
            +
                        File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%PLURALIZED_MODEL_NAME%.rb'), # (new style)
         | 
| 114 | 
            +
                        File.join(root_directory, FABRICATORS_TEST_DIR, '%MODEL_NAME%_fabricator.rb'),
         | 
| 115 | 
            +
                        File.join(root_directory, FABRICATORS_SPEC_DIR, '%MODEL_NAME%_fabricator.rb')
         | 
| 116 | 
            +
                      ]
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    def serialize_files(root_directory)
         | 
| 120 | 
            +
                      [
         | 
| 121 | 
            +
                        File.join(root_directory, SERIALIZERS_DIR,      '%MODEL_NAME%_serializer.rb'),
         | 
| 122 | 
            +
                        File.join(root_directory, SERIALIZERS_TEST_DIR, '%MODEL_NAME%_serializer_test.rb'),
         | 
| 123 | 
            +
                        File.join(root_directory, SERIALIZERS_SPEC_DIR, '%MODEL_NAME%_serializer_spec.rb')
         | 
| 124 | 
            +
                      ]
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnnotateRb
         | 
| 4 | 
            +
              module ModelAnnotator
         | 
| 5 | 
            +
                module Helper
         | 
| 6 | 
            +
                  MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper).freeze
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  class << self
         | 
| 9 | 
            +
                    def matched_types(options)
         | 
| 10 | 
            +
                      types = MATCHED_TYPES.dup
         | 
| 11 | 
            +
                      types << 'admin' if options[:active_admin] =~ Constants::TRUE_RE && !types.include?('admin')
         | 
| 12 | 
            +
                      types << 'additional_file_patterns' if options[:additional_file_patterns].present?
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      types
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def magic_comments_as_string(content)
         | 
| 18 | 
            +
                      magic_comments = content.scan(Annotator::MAGIC_COMMENT_MATCHER).flatten.compact
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      if magic_comments.any?
         | 
| 21 | 
            +
                        magic_comments.join
         | 
| 22 | 
            +
                      else
         | 
| 23 | 
            +
                        ''
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def skip_on_migration?
         | 
| 28 | 
            +
                      Env.read('ANNOTATE_SKIP_ON_DB_MIGRATE') =~ Constants::TRUE_RE || Env.read('skip_on_db_migrate') =~ Constants::TRUE_RE
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def include_routes?
         | 
| 32 | 
            +
                      Env.read('routes') =~ Constants::TRUE_RE
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def include_models?
         | 
| 36 | 
            +
                      Env.read('models') =~ Constants::TRUE_RE
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def true?(val)
         | 
| 40 | 
            +
                      val.present? && Constants::TRUE_RE.match?(val)
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # TODO: Find another implementation that doesn't depend on ActiveSupport
         | 
| 44 | 
            +
                    def fallback(*args)
         | 
| 45 | 
            +
                      args.compact.detect(&:present?)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def reset_options(options)
         | 
| 49 | 
            +
                      options.flatten.each { |key| Env.write(key, nil) }
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnnotateRb
         | 
| 4 | 
            +
              module ModelAnnotator
         | 
| 5 | 
            +
                class ModelClassGetter
         | 
| 6 | 
            +
                  class << self
         | 
| 7 | 
            +
                    # Retrieve the classes belonging to the model names we're asked to process
         | 
| 8 | 
            +
                    # Check for namespaced models in subdirectories as well as models
         | 
| 9 | 
            +
                    # in subdirectories without namespacing.
         | 
| 10 | 
            +
                    def call(file, options)
         | 
| 11 | 
            +
                      model_path = file.gsub(/\.rb$/, '')
         | 
| 12 | 
            +
                      options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      begin
         | 
| 15 | 
            +
                        get_loaded_model(model_path, file) || raise(BadModelFileError.new)
         | 
| 16 | 
            +
                      rescue LoadError
         | 
| 17 | 
            +
                        # this is for non-rails projects, which don't get Rails auto-require magic
         | 
| 18 | 
            +
                        file_path = File.expand_path(file)
         | 
| 19 | 
            +
                        if File.file?(file_path) && Kernel.require(file_path)
         | 
| 20 | 
            +
                          retry
         | 
| 21 | 
            +
                        elsif model_path =~ /\//
         | 
| 22 | 
            +
                          model_path = model_path.split('/')[1..-1].join('/').to_s
         | 
| 23 | 
            +
                          retry
         | 
| 24 | 
            +
                        else
         | 
| 25 | 
            +
                          raise
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    private
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    # Retrieve loaded model class
         | 
| 33 | 
            +
                    def get_loaded_model(model_path, file)
         | 
| 34 | 
            +
                      loaded_model_class = get_loaded_model_by_path(model_path)
         | 
| 35 | 
            +
                      return loaded_model_class if loaded_model_class
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      # We cannot get loaded model when `model_path` is loaded by Rails
         | 
| 38 | 
            +
                      # auto_load/eager_load paths. Try all possible model paths one by one.
         | 
| 39 | 
            +
                      absolute_file = File.expand_path(file)
         | 
| 40 | 
            +
                      model_paths =
         | 
| 41 | 
            +
                        $LOAD_PATH.select { |path| absolute_file.include?(path) }
         | 
| 42 | 
            +
                                  .map { |path| absolute_file.sub(path, '').sub(/\.rb$/, '').sub(/^\//, '') }
         | 
| 43 | 
            +
                      model_paths
         | 
| 44 | 
            +
                        .map { |path| get_loaded_model_by_path(path) }
         | 
| 45 | 
            +
                        .find { |loaded_model| !loaded_model.nil? }
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # Retrieve loaded model class by path to the file where it's supposed to be defined.
         | 
| 49 | 
            +
                    def get_loaded_model_by_path(model_path)
         | 
| 50 | 
            +
                      ::ActiveSupport::Inflector.constantize(::ActiveSupport::Inflector.camelize(model_path))
         | 
| 51 | 
            +
                    rescue StandardError, LoadError
         | 
| 52 | 
            +
                      # Revert to the old way but it is not really robust
         | 
| 53 | 
            +
                      ObjectSpace.each_object(::Class)
         | 
| 54 | 
            +
                                 .select do |c|
         | 
| 55 | 
            +
                        Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
         | 
| 56 | 
            +
                          c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
         | 
| 57 | 
            +
                          c.ancestors.include?(::ActiveRecord::Base)
         | 
| 58 | 
            +
                      end.detect { |c| ::ActiveSupport::Inflector.underscore(c.to_s) == model_path }
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,118 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnnotateRb
         | 
| 4 | 
            +
              module ModelAnnotator
         | 
| 5 | 
            +
                # Not sure yet what the difference is between this and FileAnnotator
         | 
| 6 | 
            +
                class ModelFileAnnotator
         | 
| 7 | 
            +
                  class << self
         | 
| 8 | 
            +
                    def call(annotated, file, header, options)
         | 
| 9 | 
            +
                      begin
         | 
| 10 | 
            +
                        return false if /#{Constants::SKIP_ANNOTATION_PREFIX}.*/ =~ (File.exist?(file) ? File.read(file) : '')
         | 
| 11 | 
            +
                        klass = ModelClassGetter.call(file, options)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                        klass_is_a_class = klass.is_a?(Class)
         | 
| 14 | 
            +
                        klass_inherits_active_record_base = klass < ActiveRecord::Base
         | 
| 15 | 
            +
                        klass_is_not_abstract = !klass.abstract_class?
         | 
| 16 | 
            +
                        klass_table_exists = klass.table_exists?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        not_sure_this_conditional = (!options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name))
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                        annotate_conditions = [
         | 
| 21 | 
            +
                          klass_is_a_class,
         | 
| 22 | 
            +
                          klass_inherits_active_record_base,
         | 
| 23 | 
            +
                          not_sure_this_conditional,
         | 
| 24 | 
            +
                          klass_is_not_abstract,
         | 
| 25 | 
            +
                          klass_table_exists
         | 
| 26 | 
            +
                        ]
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        do_annotate = annotate_conditions.all?
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                        if do_annotate
         | 
| 31 | 
            +
                          files_annotated = annotate(klass, file, header, options)
         | 
| 32 | 
            +
                          annotated.concat(files_annotated)
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      rescue BadModelFileError => e
         | 
| 36 | 
            +
                        unless options[:ignore_unknown_models]
         | 
| 37 | 
            +
                          $stderr.puts "Unable to annotate #{file}: #{e.message}"
         | 
| 38 | 
            +
                          $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
         | 
| 39 | 
            +
                        end
         | 
| 40 | 
            +
                      rescue StandardError => e
         | 
| 41 | 
            +
                        $stderr.puts "Unable to annotate #{file}: #{e.message}"
         | 
| 42 | 
            +
                        $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    private
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # Given the name of an ActiveRecord class, create a schema
         | 
| 49 | 
            +
                    # info block (basically a comment containing information
         | 
| 50 | 
            +
                    # on the columns and their types) and put it at the front
         | 
| 51 | 
            +
                    # of the model and fixture source files.
         | 
| 52 | 
            +
                    #
         | 
| 53 | 
            +
                    # === Options (opts)
         | 
| 54 | 
            +
                    #  :position_in_class<Symbol>:: where to place the annotated section in model file
         | 
| 55 | 
            +
                    #  :position_in_test<Symbol>:: where to place the annotated section in test/spec file(s)
         | 
| 56 | 
            +
                    #  :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
         | 
| 57 | 
            +
                    #  :position_in_factory<Symbol>:: where to place the annotated section in factory file
         | 
| 58 | 
            +
                    #  :position_in_serializer<Symbol>:: where to place the annotated section in serializer file
         | 
| 59 | 
            +
                    #  :exclude_tests<Symbol>:: whether to skip modification of test/spec files
         | 
| 60 | 
            +
                    #  :exclude_fixtures<Symbol>:: whether to skip modification of fixture files
         | 
| 61 | 
            +
                    #  :exclude_factories<Symbol>:: whether to skip modification of factory files
         | 
| 62 | 
            +
                    #  :exclude_serializers<Symbol>:: whether to skip modification of serializer files
         | 
| 63 | 
            +
                    #  :exclude_scaffolds<Symbol>:: whether to skip modification of scaffold files
         | 
| 64 | 
            +
                    #  :exclude_controllers<Symbol>:: whether to skip modification of controller files
         | 
| 65 | 
            +
                    #  :exclude_helpers<Symbol>:: whether to skip modification of helper files
         | 
| 66 | 
            +
                    #  :exclude_sti_subclasses<Symbol>:: whether to skip modification of files for STI subclasses
         | 
| 67 | 
            +
                    #
         | 
| 68 | 
            +
                    # == Returns:
         | 
| 69 | 
            +
                    # an array of file names that were annotated.
         | 
| 70 | 
            +
                    #
         | 
| 71 | 
            +
                    def annotate(klass, file, header, options = {})
         | 
| 72 | 
            +
                      begin
         | 
| 73 | 
            +
                        klass.reset_column_information
         | 
| 74 | 
            +
                        info = SchemaInfo.generate(klass, header, options)
         | 
| 75 | 
            +
                        model_name = klass.name.underscore
         | 
| 76 | 
            +
                        table_name = klass.table_name
         | 
| 77 | 
            +
                        model_file_name = File.join(file)
         | 
| 78 | 
            +
                        annotated = []
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                        if FileAnnotator.call(model_file_name, info, :position_in_class, options)
         | 
| 81 | 
            +
                          annotated << model_file_name
         | 
| 82 | 
            +
                        end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                        Helper.matched_types(options).each do |key|
         | 
| 85 | 
            +
                          exclusion_key = "exclude_#{key.pluralize}".to_sym
         | 
| 86 | 
            +
                          position_key = "position_in_#{key}".to_sym
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                          # Same options for active_admin models
         | 
| 89 | 
            +
                          if key == 'admin'
         | 
| 90 | 
            +
                            exclusion_key = 'exclude_class'.to_sym
         | 
| 91 | 
            +
                            position_key = 'position_in_class'.to_sym
         | 
| 92 | 
            +
                          end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                          next if options[exclusion_key]
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                          patterns = PatternGetter.call(options, key)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                          patterns
         | 
| 99 | 
            +
                            .map { |f| FileNameResolver.call(f, model_name, table_name) }
         | 
| 100 | 
            +
                            .map { |f| Dir.glob(f) }
         | 
| 101 | 
            +
                            .flatten
         | 
| 102 | 
            +
                            .each do |f|
         | 
| 103 | 
            +
                            if FileAnnotator.call(f, info, position_key, options)
         | 
| 104 | 
            +
                              annotated << f
         | 
| 105 | 
            +
                            end
         | 
| 106 | 
            +
                          end
         | 
| 107 | 
            +
                        end
         | 
| 108 | 
            +
                      rescue StandardError => e
         | 
| 109 | 
            +
                        $stderr.puts "Unable to annotate #{file}: #{e.message}"
         | 
| 110 | 
            +
                        $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                      annotated
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnnotateRb
         | 
| 4 | 
            +
              module ModelAnnotator
         | 
| 5 | 
            +
                class ModelFilesGetter
         | 
| 6 | 
            +
                  class << self
         | 
| 7 | 
            +
                    # Return a list of the model files to annotate.
         | 
| 8 | 
            +
                    # If we have command line arguments, they're assumed to the path
         | 
| 9 | 
            +
                    # of model files from root dir. Otherwise we take all the model files
         | 
| 10 | 
            +
                    # in the model_dir directory.
         | 
| 11 | 
            +
                    def call(options)
         | 
| 12 | 
            +
                      model_files = []
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      model_files = list_model_files_from_argument(options) if !options[:is_rake]
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      return model_files if !model_files.empty?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      options[:model_dir].each do |dir|
         | 
| 19 | 
            +
                        Dir.chdir(dir) do
         | 
| 20 | 
            +
                          list = if options[:ignore_model_sub_dir]
         | 
| 21 | 
            +
                                   Dir["*.rb"].map { |f| [dir, f] }
         | 
| 22 | 
            +
                                 else
         | 
| 23 | 
            +
                                   Dir["**/*.rb"].reject { |f| f["concerns/"] }.map { |f| [dir, f] }
         | 
| 24 | 
            +
                                 end
         | 
| 25 | 
            +
                          model_files.concat(list)
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      model_files
         | 
| 30 | 
            +
                    rescue SystemCallError
         | 
| 31 | 
            +
                      $stderr.puts "No models found in directory '#{options[:model_dir].join("', '")}'."
         | 
| 32 | 
            +
                      $stderr.puts "Either specify models on the command line, or use the --model-dir option."
         | 
| 33 | 
            +
                      $stderr.puts "Call 'annotate --help' for more info."
         | 
| 34 | 
            +
                      # exit 1 # TODO: Return exit code back to caller. Right now it messes up RSpec being able to run
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    private
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def list_model_files_from_argument(options)
         | 
| 40 | 
            +
                      return [] if ARGV.empty?
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      specified_files = ARGV.map { |file| File.expand_path(file) }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      model_files = options[:model_dir].flat_map do |dir|
         | 
| 45 | 
            +
                        absolute_dir_path = File.expand_path(dir)
         | 
| 46 | 
            +
                        specified_files
         | 
| 47 | 
            +
                          .find_all { |file| file.start_with?(absolute_dir_path) }
         | 
| 48 | 
            +
                          .map { |file| [dir, file.sub("#{absolute_dir_path}/", '')] }
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      if model_files.size != specified_files.size
         | 
| 52 | 
            +
                        $stderr.puts "The specified file could not be found in directory '#{options[:model_dir].join("', '")}'."
         | 
| 53 | 
            +
                        $stderr.puts "Call 'annotate --help' for more info."
         | 
| 54 | 
            +
                        # exit 1 # TODO: Return exit code back to caller. Right now it messes up RSpec being able to run
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      model_files
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnnotateRb
         | 
| 4 | 
            +
              module ModelAnnotator
         | 
| 5 | 
            +
                class PatternGetter
         | 
| 6 | 
            +
                  class << self
         | 
| 7 | 
            +
                    def call(options, pattern_types = [])
         | 
| 8 | 
            +
                      current_patterns = []
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      options[:root_dir].each do |root_directory|
         | 
| 11 | 
            +
                        Array(pattern_types).each do |pattern_type|
         | 
| 12 | 
            +
                          patterns = FilePatterns.generate(root_directory, pattern_type, options)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                          current_patterns += if pattern_type.to_sym == :additional_file_patterns
         | 
| 15 | 
            +
                                                patterns
         | 
| 16 | 
            +
                                              else
         | 
| 17 | 
            +
                                                patterns.map { |p| p.sub(/^[\/]*/, '') }
         | 
| 18 | 
            +
                                              end
         | 
| 19 | 
            +
                        end
         | 
| 20 | 
            +
                      end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      current_patterns
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         |