Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotate gem does not support Rails autoloader collapsed directories #969

Open
bazay opened this issue Oct 4, 2022 · 4 comments
Open

Annotate gem does not support Rails autoloader collapsed directories #969

bazay opened this issue Oct 4, 2022 · 4 comments

Comments

@bazay
Copy link

bazay commented Oct 4, 2022

Annotate gem does not support Rails autoloader collapsed directories i.e.

pry(AnnotateModels)> Rails.autoloaders.main
=> #<Zeitwerk::Loader:0x00007f6305898b58
 @autoloaded_dirs=[],
 @autoloads={},
 @collapse_dirs=
  #<Set: {"/home/developer/my_app/app/components/my_namespace/models"}>,
 @collapse_glob_patterns=#<Set: {/home/developer/my_app/app/components/*/models", "/home/developer/my_app/app/components/my_namespace/jobs"}>,
 @eager_load_exclusions=#<Set: {}>,
 @eager_loaded=true,
 @ignored_glob_patterns=
  #<Set: {"/home/developer/my_app/lib/rails",
   "/home/developer/my_app/lib/patches",
   "//home/developer/my_app/lib/rails/generators/job",
...
pry(AnnotateModels)> Rails.autoloaders.main.collapsed_dirs
=> #<Set: {"/home/developer/my_app/app/components/my_namespace/models"}>

In config/application.rb:

Rails.autoloaders.main.collapse("app/components/*/models")

I've obfuscated the model names and namespace, but the end result is still the same.

Commands

$ bundle exec annotate
Unable to annotate app/components/my_namespace/models/my_model.rb: file doesn't contain a valid model class
Unable to annotate app/components/my_namespace/models/my_second_model.rb: file doesn't contain a valid model class
Unable to annotate app/components/my_namespace/models/my_third_model.rb: file doesn't contain a valid model class
$

Version

  • annotate (3.2.0 96831c1)
  • rails (7.0.3.1)
  • ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
bazay added a commit to bazay/annotate_models that referenced this issue Oct 4, 2022
bazay added a commit to bazay/annotate_models that referenced this issue Oct 4, 2022
Signed-off-by: Baron Bloomer <baronbloomer@gmail.com>
@guigs
Copy link

guigs commented Oct 11, 2022

Here's a monkey patch we are using that find classes based on zeitwerk loader.
Our use case is for loading classes within namespaces. I'm not sure if it works with collapsed dirs, but maybe you can modify it to work.

Put in an initializer:

# frozen_string_literal: true

if Rails.env.development?
  require 'annotate'

  module AnnotateModelsWithZeitwerk
    def get_model_class(file)
      loader = Rails.autoloaders.main
      root_dirs = loader.dirs(namespaces: true) # or `root_dirs = loader.root_dirs` with zeitwerk < 2.6.1
      expanded_file = File.expand_path(file)
      root_dir, namespace = root_dirs.find do |dir, _|
        expanded_file.start_with?(dir)
      end
      _, filepath_relative_to_root_dir = expanded_file.split(root_dir)
      filepath_relative_to_root_dir = filepath_relative_to_root_dir[1..].sub(/\.rb$/, '')
      camelize = loader.inflector.camelize(filepath_relative_to_root_dir, nil)
      namespace.const_get(camelize)
    end
  end

  module AnnotateModels
    class << self
      prepend AnnotateModelsWithZeitwerk
    end
  end
end

@joshuaclayton
Copy link

I also ran into this issue earlier and had to adjust the example above slightly.

We're using collapsed directories as such:

# within config/application.rb
Rails.autoloaders.main.collapse("#{Rails.root}/app/domains/domain_name/models")
Rails.autoloaders.main.collapse("#{Rails.root}/app/domains/domain_name/controllers")
Rails.autoloaders.main.collapse("#{Rails.root}/app/domains/domain_name/lib")

Based on this, I adjusted the sample above to address const loading issues:

module AnnotateModelsWithZeitwerk
  def get_model_class(file)
    loader = Rails.autoloaders.main
    root_dirs = loader.dirs(namespaces: true) # or `root_dirs = loader.root_dirs` with zeitwerk < 2.6.1
    expanded_file = File.expand_path(file)
    root_dir, namespace = root_dirs.find do |dir, _|
      expanded_file.start_with?(dir)
    end
    _, filepath_relative_to_root_dir = expanded_file.split(root_dir)

    filepath_relative_to_root_dir = filepath_relative_to_root_dir[1..].sub(/\.rb$/, "")

    # changed

    # once we have the filepath_relative_to_root_dir, we need to see if it
    # falls within one of our Zeitwerk "collapsed" paths.
    if loader.collapse.any? { |path| path.include?(root_dir) && file.include?(path.split(root_dir)[1]) }
      # if the file is within a collapsed path, we then need to, for each
      # collapsed path, remove the root dir
      collapsed = loader.collapse.map { |path| path.split(root_dir)[1].sub(/^\//, "") }.to_set

      collapsed.each do |collapse|
        # next, we split the collapsed directory, e.g. `domain_name/models`, by
        # slash, and discard the domain_name
        _, *collapsed_namespace = collapse.split("/")

        # if there are any collapsed namespaces, e.g. `models`, we then remove
        # that from `filepath_relative_to_root_dir`.
        #
        # This would result in:
        #
        # previous filepath_relative_to_root_dir: domain_name/models/model_name
        # new filepath_relative_to_root_dir: domain_name/model_name
        if collapsed_namespace.any?
          filepath_relative_to_root_dir.sub!("/#{collapsed_namespace.last}", "")
        end
      end
    end

    # end changed

    camelize = loader.inflector.camelize(filepath_relative_to_root_dir, nil)
    namespace.const_get(camelize)
  end
end

@ilvez
Copy link

ilvez commented Feb 7, 2024

I found that fork of this gem has solved those issues: https://github.com/drwl/annotaterb

I understand fork was done since this gem is unmaintained quite a long time now (2 years without release): https://www.reddit.com/r/rails/comments/13keyfm/i_spent_the_past_3_months_working_on_a_fork_of/

Nearly drop in replacement, but configuration file needs to be created instead of configuring in rake task.

@drwl
Copy link
Collaborator

drwl commented Feb 13, 2024

@joshuaclayton @bazay, I took a stab at adding zeitwerk support in my fork, if you want to know more: drwl/annotaterb#82

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants