aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails/plugin/loader.rb
blob: 0d16cbd7c355df84843f2e5f359eeb241499a514 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
require "rails/plugin"

module Rails
  class Plugin
    class Loader
      attr_reader :initializer

      # Creates a new Plugin::Loader instance, associated with the given
      # Rails::Initializer. This default implementation automatically locates
      # all plugins, and adds all plugin load paths, when it is created. The plugins
      # are then fully loaded (init.rb is evaluated) when load_plugins is called.
      #
      # It is the loader's responsibility to ensure that only the plugins specified
      # in the configuration are actually loaded, and that the order defined
      # is respected.
      def initialize(initializer)
        @initializer = initializer
      end

      # Returns the plugins to be loaded, in the order they should be loaded.
      def plugins
        @plugins ||= all_plugins.select { |plugin| should_load?(plugin) }.sort { |p1, p2| order_plugins(p1, p2) }
      end

      # Returns the plugins that are in engine-form (have an app/ directory)
      def engines
        @engines ||= plugins.select {|plugin| plugin.engine? }
      end

      # Returns all the plugins that could be found by the current locators.
      def all_plugins
        @all_plugins ||= locate_plugins
        @all_plugins
      end

      def load_plugins
        plugins.each do |plugin|
          plugin.load(initializer)
          register_plugin_as_loaded(plugin)
        end

        configure_engines

        ensure_all_registered_plugins_are_loaded!
      end

      # Adds the load paths for every plugin into the $LOAD_PATH. Plugin load paths are
      # added *after* the application's <tt>lib</tt> directory, to ensure that an application
      # can always override code within a plugin.
      #
      # Plugin load paths are also added to Dependencies.load_paths, and Dependencies.load_once_paths.
      def add_plugin_load_paths
        plugins.each do |plugin|
          plugin.load_paths.each do |path|
            $LOAD_PATH.insert(application_lib_index + 1, path)

            ActiveSupport::Dependencies.load_paths << path

            unless configuration.reload_plugins?
              ActiveSupport::Dependencies.load_once_paths << path
            end
          end
        end

        $LOAD_PATH.uniq!
      end

      def engine_metal_paths
        engines.collect {|engine| engine.metal_path }
      end

      protected
        def configure_engines
          if engines.any?
            add_engine_routing_configurations
            add_engine_locales
            add_engine_controller_paths
            add_engine_view_paths
          end
        end

        def add_engine_routing_configurations
          engines.select {|engine| engine.routed? }.map {|engine| engine.routing_file }.each do |routing_file|
            ActionController::Routing::Routes.add_configuration_file(routing_file)
          end
        end

        def add_engine_locales
          localized_engines = engines.select { |engine| engine.localized? }

          # reverse it such that the last engine can overwrite translations from the first, like with routes
          locale_files = localized_engines.collect { |engine| engine.locale_files }.reverse.flatten
          I18n.load_path += locale_files - I18n.load_path
        end

        def add_engine_controller_paths
          ActionController::Routing.controller_paths += engines.collect {|engine| engine.controller_path }
        end

        def add_engine_view_paths
          # reverse it such that the last engine can overwrite view paths from the first, like with routes
          paths = ActionView::PathSet.new(engines.collect {|engine| engine.view_path }.reverse)
          ActionController::Base.view_paths.concat(paths)
          ActionMailer::Base.view_paths.concat(paths) if configuration.frameworks.include?(:action_mailer)
        end

        # The locate_plugins method uses each class in config.plugin_locators to
        # find the set of all plugins available to this Rails application.
        def locate_plugins
          configuration.plugin_locators.map do |locator|
            locator.new(initializer).plugins
          end.flatten
          # TODO: sorting based on config.plugins
        end

        def register_plugin_as_loaded(plugin)
          initializer.config.loaded_plugins << plugin
        end

        def configuration
          initializer.configuration
        end

        def should_load?(plugin)
          # uses Plugin#name and Plugin#valid?
          enabled?(plugin) && plugin.valid?
        end

        def order_plugins(plugin_a, plugin_b)
          if !explicit_plugin_loading_order?
            plugin_a <=> plugin_b
          else
            if !explicitly_enabled?(plugin_a) && !explicitly_enabled?(plugin_b)
              plugin_a <=> plugin_b
            else
              effective_order_of(plugin_a) <=> effective_order_of(plugin_b)
            end
          end
        end

        def effective_order_of(plugin)
          if explicitly_enabled?(plugin)
            registered_plugin_names.index(plugin.name)
          else
            registered_plugin_names.index('all')
          end
        end

        def application_lib_index
          $LOAD_PATH.index(File.join(RAILS_ROOT, 'lib')) || 0
        end

        def enabled?(plugin)
          !explicit_plugin_loading_order? || registered?(plugin)
        end

        def explicit_plugin_loading_order?
          !registered_plugin_names.nil?
        end

        def registered?(plugin)
          explicit_plugin_loading_order? && registered_plugins_names_plugin?(plugin)
        end

        def explicitly_enabled?(plugin)
          !explicit_plugin_loading_order? || explicitly_registered?(plugin)
        end

        def explicitly_registered?(plugin)
          explicit_plugin_loading_order? && registered_plugin_names.include?(plugin.name)
        end

        def registered_plugins_names_plugin?(plugin)
          registered_plugin_names.include?(plugin.name) || registered_plugin_names.include?('all')
        end

        # The plugins that have been explicitly listed with config.plugins. If this list is nil
        # then it means the client does not care which plugins or in what order they are loaded,
        # so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is
        # non empty, we load the named plugins in the order specified.
        def registered_plugin_names
          configuration.plugins ? configuration.plugins.map {|plugin| plugin.to_s } : nil
        end

        def loaded?(plugin_name)
          initializer.config.loaded_plugins.detect { |plugin| plugin.name == plugin_name.to_s }
        end

        def ensure_all_registered_plugins_are_loaded!
          if explicit_plugin_loading_order?
            if configuration.plugins.detect {|plugin| plugin != :all && !loaded?(plugin) }
              missing_plugins = configuration.plugins - (plugins.map{|p| p.name.to_sym} + [:all])
              raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence(:locale => :en)}"
            end
          end
        end

    end
  end
end