Module: ClassLoader

Defined in:
lib/class_loader/class_loader.rb

Defined Under Namespace

Classes: Watcher

Constant Summary collapse

SKIP =

Sometimes other libraries try to load some const to check if it exist, and we don’t want ClassLoader automatically load it.

%w(Test RSpec)

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.after_callbacksObject (readonly)

Returns the value of attribute after_callbacks.



12
13
14
# File 'lib/class_loader/class_loader.rb', line 12

def after_callbacks
  @after_callbacks
end

.loaded_classesObject (readonly)

Returns the value of attribute loaded_classes.



12
13
14
# File 'lib/class_loader/class_loader.rb', line 12

def loaded_classes
  @loaded_classes
end

.monitorObject (readonly)

Returns the value of attribute monitor.



12
13
14
# File 'lib/class_loader/class_loader.rb', line 12

def monitor
  @monitor
end

Class Method Details

.after(class_name, &block) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
# File 'lib/class_loader/class_loader.rb', line 121

def after class_name, &block
  (@after_callbacks[class_name] ||= []) << block

  # Firing it immediatelly if class has been already loaded.
  # Checking if constant exist without loading it.
  names = class_name.gsub(/^::/, '').split('::')
  const_defined = names.inject Object do |current, const|
    current = current && current.const_defined?(const) && current.const_get(const)
  end
  block.call if const_defined
end

.filter_backtrace(backtrace) ⇒ Object



133
134
135
# File 'lib/class_loader/class_loader.rb', line 133

def filter_backtrace backtrace
  backtrace.reject{|path| path.include?("/lib/class_loader") or path.include?('monitor.rb')}
end

.load(namespace, const) ⇒ Object

Hierarchically searching for class file, according to modules hierarchy.

For example, let’s suppose that ‘C` class defined in ’/lib/a/c.rb’ file, and we referencing it in the ‘A::B` namespace, like this - `A::B::C`, the following files will be checked:

  • ‘/lib/a/b/c.rb’ - there’s nothing, moving up in hierarchy.

  • ‘/lib/a/c.rb’ - got and load it.



21
22
23
24
25
26
27
28
29
30
31
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/class_loader/class_loader.rb', line 21

def load namespace, const
  monitor.synchronize do
    original_namespace = namespace
    namespace = nil if namespace == Object or namespace == Module
    target_namespace = namespace

    # Need this hack to work with anonymous classes.
    namespace = eval "#{name_hack(namespace)}" if namespace

    # Hierarchically searching for class name.
    hierarchy = {}
    begin
      class_name = namespace ? "#{namespace.name}::#{const}" : const.to_s
      class_file_name = get_file_name class_name
      binding = namespace || Object
      hierarchy[class_file_name] = binding

      # Skip some constants.
      return nil if SKIP.any?{|c| class_name == c or class_name.include?("#{c}::")}

      # Trying to load class file, if its exist.
      loaded = begin
        require class_file_name
        true
      rescue LoadError => e
        # Not the best way - hardcoding error messages, but it's the fastest way
        # to check existence of file & load it.
        unless e.message =~ /no such file.*#{Regexp.escape(class_file_name)}/
          raise e.class, e.message, filter_backtrace(e.backtrace)
        end
        false
      end

      if loaded
        # Checking that class hasn't been loaded previously, sometimes it may be caused by
        # weird class definition code.
        if loaded_classes.include? class_name
          msg = "something wrong with '#{const}' referenced from '#{original_namespace}' scope!"
          raise NameError, msg, filter_backtrace(caller)
        end

        # Checking that class defined in correct namespace, not the another one.
        unless binding.const_defined? const, false
          msg = "class name #{class_name} doesn't correspond to file name '#{class_file_name}'!"
          raise NameError, msg, filter_backtrace(caller)
        end

        # Getting the class itself.
        klass = binding.const_get const, false

        # Firing after callbacks.
        if callbacks = after_callbacks[klass.name] then callbacks.each{|c| c.call klass} end

        loaded_classes[class_name] = klass

        return klass
      end

      # Moving to higher namespace.
      global_also_tried = namespace == nil
      namespace = Module.namespace_for namespace.name if namespace
    end until global_also_tried

    # If file not found trying to find directory and evaluate it as a module.
    hierarchy.each do |path, binding|
      $LOAD_PATH.each do |base|
        next unless File.directory? File.join(base, path)
        amodule = Module.new
        binding.const_set const, amodule
        return amodule
      end
    end

    return nil
  end
end

.preload(*paths) ⇒ Object

Eagerly load all classes in paths.



99
100
101
102
103
104
105
106
107
108
# File 'lib/class_loader/class_loader.rb', line 99

def preload *paths
  monitor.synchronize do
    paths.each do |path|
      Dir.glob("#{path}/**/*.rb").each do |class_path|
        class_file_name = class_path.sub("#{path}/", '').sub(/\.rb$/, '')
        require class_file_name
      end
    end
  end
end

.watch(*paths) ⇒ Object

Watch and reload files.



111
112
113
114
# File 'lib/class_loader/class_loader.rb', line 111

def watch *paths
  paths.each{|path| watcher.paths << path unless watcher.paths.include? path}
  watcher.start
end

.watcherObject



116
117
118
119
# File 'lib/class_loader/class_loader.rb', line 116

def watcher
  require 'class_loader/watcher'
  @watcher ||= ClassLoader::Watcher.new monitor
end