Module: Zeitwerk::Loader::EagerLoad

Included in:
Zeitwerk::Loader
Defined in:
lib/zeitwerk/loader/eager_load.rb

Instance Method Summary collapse

Instance Method Details

#eager_load(force: false) ⇒ Object

Eager loads all files in the root directories, recursively. Files do not need to be in ‘$LOAD_PATH`, absolute file names are used. Ignored and shadowed files are not eager loaded. You can opt-out specifically in specific files and directories with do_not_eager_load, and that can be overridden passing `force: true`.

: (?force: boolish) -> void



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/zeitwerk/loader/eager_load.rb', line 9

def eager_load(force: false)
  mutex.synchronize do
    break if @eager_loaded
    raise Zeitwerk::SetupRequired unless @setup

    log { "eager load start" }

    actual_roots.each do |root_dir, root_namespace|
      actual_eager_load_dir(root_dir, root_namespace, force: force)
    end

    autoloaded_dirs.each do |autoloaded_dir|
      Zeitwerk::Registry.autoloads.unregister(autoloaded_dir)
    end
    autoloaded_dirs.clear

    @eager_loaded = true

    log { "eager load end" }
  end
end

#eager_load_dir(path) ⇒ Object

: (String | Pathname) -> void



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/zeitwerk/loader/eager_load.rb', line 32

def eager_load_dir(path)
  raise Zeitwerk::SetupRequired unless @setup

  abspath = File.expand_path(path)

  raise Zeitwerk::Error.new("#{abspath} is not a directory") unless @fs.dir?(abspath)

  paths = []

  root_namespace = nil
  @fs.walk_up(abspath) do |dir|
    return if ignored_path?(dir)
    return if eager_load_exclusions.member?(dir)

    break if root_namespace = roots[dir]

    basename = File.basename(dir)
    return if @fs.hidden?(basename)

    paths << [basename, dir] unless collapse?(dir)
  end

  raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace

  return if @eager_loaded

  namespace = root_namespace
  paths.reverse_each do |basename, dir|
    cname = cname_for(basename, dir)
    # Can happen if there are no Ruby files. This is not an error condition,
    # the directory is actually managed. Could have Ruby files later.
    return unless namespace.const_defined?(cname, false)
    namespace = namespace.const_get(cname, false)
  end

  # A shortcircuiting test depends on the invocation of this method. Please
  # keep them in sync if refactored.
  actual_eager_load_dir(abspath, namespace)
end

#eager_load_namespace(mod) ⇒ Object

: (Module) -> void



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/zeitwerk/loader/eager_load.rb', line 73

def eager_load_namespace(mod)
  raise Zeitwerk::SetupRequired unless @setup

  unless mod.is_a?(Module)
    raise Zeitwerk::Error, "#{mod.inspect} is not a class or module object"
  end

  return if @eager_loaded

  mod_name = real_mod_name(mod)
  return unless mod_name

  actual_roots.each do |root_dir, root_namespace|
    if Object.equal?(mod)
      # A shortcircuiting test depends on the invocation of this method.
      # Please keep them in sync if refactored.
      actual_eager_load_dir(root_dir, root_namespace)
    elsif root_namespace.equal?(Object)
      eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
    else
      root_namespace_name = real_mod_name(root_namespace)
      if root_namespace_name.start_with?(mod_name + "::")
        actual_eager_load_dir(root_dir, root_namespace)
      elsif mod_name == root_namespace_name
        actual_eager_load_dir(root_dir, root_namespace)
      elsif mod_name.start_with?(root_namespace_name + "::")
        eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
      else
        # Unrelated constant hierarchies, do nothing.
      end
    end
  end
end

#load_file(path) ⇒ Object

Loads the given Ruby file.

Raises if the argument is ignored, shadowed, or not managed by the receiver.

The method is implemented as constantize for files, in a sense, to be able to descend orderly and make sure the file is loadable.

: (String | Pathname) -> void

Raises:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/zeitwerk/loader/eager_load.rb', line 115

def load_file(path)
  abspath = File.expand_path(path)

  raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
  raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if !@fs.rb_extension?(abspath)
  raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)

  file_basename = File.basename(abspath, ".rb")
  raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(file_basename)

  root_namespace = nil
  paths = []

  @fs.walk_up(File.dirname(abspath)) do |dir|
    raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir)

    break if root_namespace = roots[dir]

    basename = File.basename(dir)
    raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(basename)

    paths << [basename, dir] unless collapse?(dir)
  end

  raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace

  base_cname = cname_for(file_basename, abspath)

  namespace = root_namespace
  paths.reverse_each do |basename, dir|
    cname = cname_for(basename, dir)
    namespace = namespace.const_get(cname, false)
  end

  raise Zeitwerk::Error.new("#{abspath} is shadowed") if shadowed_file?(abspath)

  namespace.const_get(base_cname, false)
end