File: lib/radiant/initializer.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Radiant#5
has properties
module method: config #21
module method: configuration #29
module method: root #37
  class: Configuration#45
inherits from
  Configuration ( Rails )
has properties
attribute: extension_paths [RW] #52
attribute: ignored_extensions [RW] #52
method: initialize #54
method: default_extension_paths #66
method: enabled_extensions #83
method: expanded_extension_list #95
method: expand_and_check / 1 #100
method: extensions #117
method: extensions= / 1 #129
method: ignore_extensions / 1 #140
method: available_extensions #153
method: vendored_extensions #161
method: gem_extensions #177
method: extension / 1 #188
method: gem / 2 #194
method: admin #202
method: default_autoload_paths #217
method: default_plugin_paths #240
method: default_view_path #246
method: default_controller_paths #252
  class: Initializer#257
inherits from
  Initializer ( Rails )
has properties
class method: run / 2 #264
method: deployed_as_app? #273
method: set_autoload_paths #280
method: initialize_metal #290
method: initialize_i18n #303
method: add_plugin_load_paths #312
method: load_gems #321
method: load_plugins #327
method: load_application_initializers #340
method: load_radiant_initializers #348
method: after_initialize #362
method: initialize_views #372
method: initialize_default_admin_tabs #380
method: initialize_framework_views #388
method: initialize_routing #401
method: admin #409
method: extension_loader #415

Class Hierarchy

Object ( Builtin-Module )
Configuration ( Rails )
  Configuration ( Radiant ) #45
Initializer ( Rails )
  Initializer ( Radiant ) #257

Code

   1  require 'initializer'
   2  require 'radiant/admin_ui'
   3  require 'radiant/extension_loader'
   4 
   5  module Radiant
   6    autoload :Cache, 'radiant/cache'
   7    
   8    class << self
   9      # Returns the Radiant::Config eigenclass object, so it can be used wherever you would use Radiant::Config.
  10      #
  11      #   Radiant.config['site.title']
  12      #   Radiant.config['site.url'] = 'example.com'
  13      #
  14      # but it will also yield itself to a block:
  15      #
  16      #   Radiant.config do |config|
  17      #     config.define 'something', default => 'something'
  18      #     config['other.thing'] = 'nothing'
  19      #   end
  20      #    
  21      def config  # method must be defined before any initializers run
  22        yield Radiant::Config if block_given?
  23        Radiant::Config
  24      end
  25      
  26      # Returns the configuration object with which this application was initialized.
  27      # For now it's exactly the same as calling Rails.configuration except that it will also yield itself to a block.
  28      #    
  29      def configuration
  30        yield Rails.configuration if block_given?
  31        Rails.configuration
  32      end
  33      
  34      # Returns the root directory of this radiant installation (which is usually the gem directory).
  35      # This is not the same as Rails.root, which is the instance directory and tends to contain only site-delivery material.
  36      #
  37      def root
  38        Pathname.new(RADIANT_ROOT) if defined?(RADIANT_ROOT)
  39      end
  40    end
  41    
  42    # NB. Radiant::Configuration (aka Radiant.configuration) is our extension-aware subclass of Rails::Configuration 
  43    #     Radiant::Config (aka Radiant.config) is the application-configuration model class.
  44 
  45    class Configuration < Rails::Configuration
  46      
  47      # The Radiant::Configuration class extends Rails::Configuration with three purposes:
  48      # * to reset some rails defaults so that files are found in RADIANT_ROOT instead of RAILS_ROOT
  49      # * to notice that some gems and plugins are in fact radiant extensions 
  50      # * to notice that some radiant extensions add load paths (for plugins, controllers, metal, etc)
  51      
  52      attr_accessor :extension_paths, :ignored_extensions
  53 
  54      def initialize #:nodoc:
  55        self.extension_paths = default_extension_paths
  56        self.ignored_extensions = []
  57        super
  58      end
  59 
  60      # Sets the locations in which we look for vendored extensions. Normally:
  61      #   Rails.root/vendor/extensions
  62      #   Radiant.root/vendor/extensions        
  63      # There are no vendor/* directories in +RADIANT_ROOT+ any more but the possibility remains for compatibility reasons.
  64      # In test mode we also add a fixtures path for testing the extension loader.
  65      #
  66      def default_extension_paths
  67        env = ENV["RAILS_ENV"] || RAILS_ENV
  68        paths = [Rails.root + 'vendor/extensions']
  69        paths.unshift(Radiant.root + "vendor/extensions") unless Rails.root == Radiant.root
  70        paths.unshift(Radiant.root + "test/fixtures/extensions") if env == "test"
  71        paths
  72      end
  73      
  74      # The list of extensions, expanded and in load order, that results from combining all the extension
  75      # configuration directives. These are the extensions that will actually be loaded or migrated, 
  76      # and for most purposes this is the list you want to refer to.
  77      # 
  78      #   Radiant.configuration.enabled_extensions  # => [:name, :name, :name, :name]
  79      #
  80      # Note that an extension enabled is not the same as an extension activated or even loaded: it just means
  81      # that the application is configured to load that extension.
  82      #
  83      def enabled_extensions
  84        @enabled_extensions ||= expanded_extension_list - ignored_extensions
  85      end
  86 
  87      # The expanded and ordered list of extensions, including any that may later be ignored. This can be configured
  88      # (it is here that the :all entry is expanded to mean 'everything else'), or will default to an alphabetical list
  89      # of every extension found among gems and vendor/extensions directories.
  90      #
  91      #   Radiant.configuration.expanded_extension_list  # => [:name, :name, :name, :name]
  92      #
  93      # If an extension in the configurted list is not found, a LoadError will be thrown from here.
  94      #
  95      def expanded_extension_list
  96        # NB. it should remain possible to say config.extensions = []
  97        @extension_list ||= extensions ? expand_and_check(extensions) : available_extensions
  98      end
  99      
 100      def expand_and_check(extension_list) #:nodoc
 101        missing_extensions = extension_list - [:all] - available_extensions
 102        raise LoadError, "These configured extensions have not been found: #{missing_extensions.to_sentence}" if missing_extensions.any?
 103        if m = extension_list.index(:all)
 104          extension_list[m] = available_extensions - extension_list
 105        end
 106        extension_list.flatten
 107      end
 108      
 109      # Returns the checked and expanded list of extensions-to-enable. This may be derived from a list passed to
 110      # +config.extensions=+ or it may have defaulted to all available extensions.
 111      
 112      # Without such a call, we default to the alphabetical list of all well-formed vendor and gem extensions 
 113      # returned by +available_extensions+.
 114      # 
 115      #   Radiant.configuration.extensions  # => [:name, :all, :name]
 116      #
 117      def extensions
 118        @requested_extensions ||= available_extensions
 119      end
 120      
 121      # Sets the list of extensions that will be loaded and the order in which to load them.
 122      # It can include an :all marker to mean 'everything else' and is typically set in environment.rb:
 123      #   config.extensions = [:layouts, :taggable, :all]
 124      #   config.extensions = [:dashboard, :blog, :all]
 125      #   config.extensions = [:dashboard, :blog, :all, :comments]
 126      #
 127      # A LoadError is raised if any of the specified extensions can't be found.
 128      #
 129      def extensions=(extensions)
 130        @requested_extensions = extensions
 131      end
 132      
 133      # This is a configurable list of extension that should not be loaded.
 134      #   config.ignore_extensions = [:experimental, :broken]
 135      # You can also retrieve the list with +ignored_extensions+:
 136      #   Radiant.configuration.ignored_extensions  # => [:experimental, :broken]
 137      # These exclusions are applied regardless of dependencies and extension locations. A configuration that bundles
 138      # required extensions then ignores them will not boot and is likely to fail with errors about unitialized constants.
 139      #
 140      def ignore_extensions(array)
 141        self.ignored_extensions |= array
 142      end
 143      
 144      # Returns an alphabetical list of every extension found among all the load paths and bundled gems. Any plugin or 
 145      # gem whose path ends in the form +radiant-something-extension+ is considered to be an extension.
 146      #
 147      #   Radiant.configuration.available_extensions  # => [:name, :name, :name, :name]
 148      #
 149      # This method is always called during initialization, either as a default or to check that specified extensions are
 150      # available. One of its side effects is to populate the ExtensionLoader's list of extension root locations, later 
 151      # used when activating those extensions that have been enabled.
 152      #
 153      def available_extensions
 154        @available_extensions ||= (vendored_extensions + gem_extensions).uniq.sort.map(&:to_sym)
 155      end
 156      
 157      # Searches the defined extension_paths for subdirectories and returns a list of names as symbols.
 158      #
 159      #   Radiant.configuration.vendored_extensions  # => [:name, :name]
 160      #
 161      def vendored_extensions
 162        extension_paths.each_with_object([]) do |load_path, found|
 163          Dir["#{load_path}/*"].each do |path|
 164            if File.directory?(path)
 165              ep = ExtensionLoader.record_path(path)
 166              found << ep.name
 167            end
 168          end
 169        end
 170      end
 171      
 172      # Scans the bundled gems for any whose name match the +radiant-something-extension+ format
 173      # and returns a list of their names as symbols.
 174      #
 175      #   Radiant.configuration.gem_extensions  # => [:name, :name]
 176      #
 177      def gem_extensions
 178        Gem.loaded_specs.each_with_object([]) do |(gemname, gemspec), found|
 179          if gemname =~ /radiant-.*-extension$/
 180            ep = ExtensionLoader.record_path(gemspec.full_gem_path, gemname)
 181            found << ep.name
 182          end
 183        end
 184      end
 185          
 186      # Old extension-dependency mechanism now deprecated
 187      #
 188      def extension(ext)
 189        ::ActiveSupport::Deprecation.warn("Extension dependencies have been deprecated and are no longer supported in radiant 1.0. Extensions with dependencies should be packaged as gems and use the .gemspec to declare them.", caller)
 190      end
 191 
 192      # Old gem-invogation method now deprecated
 193      #
 194      def gem(name, options = {})
 195        ::ActiveSupport::Deprecation.warn("Please declare gem dependencies in your Gemfile (or for an extension, in the .gemspec file).", caller)
 196        super
 197      end
 198 
 199      # Returns the AdminUI singleton, giving get-and-set access to the tabs and partial-sets it defines.
 200      # More commonly accessed in the initializer via its call to +configuration.admin+.
 201      #
 202      def admin
 203        AdminUI.instance
 204      end
 205      
 206      %w{controller model view metal plugin load eager_load}.each do |type|
 207        define_method("add_#{type}_paths".to_sym) do |paths|
 208          self.send("#{type}_paths".to_sym).concat(paths)
 209        end
 210      end
 211 
 212    private
 213 
 214      # Overrides the Rails::Initializer default so that autoload paths for models, controllers etc point to 
 215      # directories in RADIANT_ROOT rather than in RAILS_ROOT.
 216      #
 217      def default_autoload_paths
 218        paths = ["#{RADIANT_ROOT}/test/mocks/#{environment}"]
 219 
 220        # Add the app's controller directory
 221        paths.concat(Dir["#{RADIANT_ROOT}/app/controllers/"])
 222 
 223        # Followed by the standard includes.
 224        paths.concat %w(
 225          app
 226          app/metal
 227          app/models
 228          app/controllers
 229          app/helpers
 230          config
 231          lib
 232          vendor
 233        ).map { |dir| "#{RADIANT_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) }
 234 
 235        paths.concat builtin_directories
 236      end
 237 
 238      # Overrides the Rails::Initializer default to add plugin paths in RADIANT_ROOT as well as RAILS_ROOT.
 239      #
 240      def default_plugin_paths
 241        super + ["#{RADIANT_ROOT}/lib/plugins", "#{RADIANT_ROOT}/vendor/plugins"]
 242      end
 243      
 244      # Overrides the Rails::Initializer default to look for views in RADIANT_ROOT rather than RAILS_ROOT.
 245      #
 246      def default_view_path
 247        File.join(RADIANT_ROOT, 'app', 'views')
 248      end
 249 
 250      # Overrides the Rails::Initializer default to look for controllers in RADIANT_ROOT rather than RAILS_ROOT.
 251      #
 252      def default_controller_paths
 253        [File.join(RADIANT_ROOT, 'app', 'controllers')]
 254      end
 255    end
 256 
 257    class Initializer < Rails::Initializer
 258    
 259      # Rails::Initializer is essentially a list of startup steps and we extend it here by:
 260      # * overriding or extending some of those steps so that they use radiant and extension paths
 261      #   as well as (or instead of) the rails defaults.
 262      # * appending some extra steps to set up the admin UI and activate extensions
 263      
 264      def self.run(command = :process, configuration = Configuration.new) #:nodoc
 265        Rails.configuration = configuration
 266        super
 267      end
 268 
 269      # Returns true in the very unusual case where radiant has been deployed as a rails app itself, rather than 
 270      # loaded as a gem or from vendor/. This is only likely in situations where radiant is customised so heavily
 271      # that extensions are not sufficient.
 272      #
 273      def deployed_as_app?
 274        RADIANT_ROOT == RAILS_ROOT
 275      end
 276      
 277      # Extends the Rails::Initializer default to add extension paths to the autoload list.
 278      # Note that +default_autoload_paths+ is also overridden to point to RADIANT_ROOT.
 279      # 
 280      def set_autoload_paths
 281        extension_loader.paths(:load).reverse_each do |path|
 282          configuration.autoload_paths.unshift path
 283          $LOAD_PATH.unshift path
 284        end
 285        super
 286      end
 287      
 288      # Overrides the Rails initializer to load metal from RADIANT_ROOT and from radiant extensions.
 289      #
 290      def initialize_metal
 291        Rails::Rack::Metal.requested_metals = configuration.metals
 292        Rails::Rack::Metal.metal_paths = ["#{RADIANT_ROOT}/app/metal"] # reset Rails default to RADIANT_ROOT
 293        Rails::Rack::Metal.metal_paths += plugin_loader.engine_metal_paths
 294        Rails::Rack::Metal.metal_paths += extension_loader.paths(:metal)
 295      
 296        configuration.middleware.insert_before(
 297          :"ActionController::ParamsParser",
 298          Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?)
 299      end
 300      
 301      # Extends the Rails initializer to add locale paths from RADIANT_ROOT and from radiant extensions.
 302      #
 303      def initialize_i18n
 304        radiant_locale_paths = Dir[File.join(RADIANT_ROOT, 'config', 'locales', '*.{rb,yml}')]
 305        configuration.i18n.load_path = radiant_locale_paths + extension_loader.paths(:locale)
 306        super
 307      end
 308 
 309      # Extends the Rails initializer to add plugin paths in extensions
 310      # and makes extension load paths reloadable (eg in development mode)
 311      #
 312      def add_plugin_load_paths
 313        configuration.add_plugin_paths(extension_loader.paths(:plugin))
 314        super
 315        ActiveSupport::Dependencies.autoload_once_paths -= extension_loader.paths(:load)
 316      end
 317 
 318      # Overrides the standard gem-loader to use Bundler instead of config.gem. This is the method normally monkey-patched
 319      # into Rails::Initializer from boot.rb if you follow the instructions at http://gembundler.com/rails23.html
 320      #
 321      def load_gems
 322        @bundler_loaded ||= Bundler.require :default, Rails.env
 323      end
 324 
 325      # Extends the Rails initializer also to load radiant extensions (which have been excluded from the list of plugins).
 326      #
 327      def load_plugins
 328        super
 329        extension_loader.load_extensions
 330      end
 331      
 332      # Extends the Rails initializer to run initializers from radiant and from extensions. The load order will be:
 333      # 1. RADIANT_ROOT/config/intializers/*.rb
 334      # 2. RAILS_ROOT/config/intializers/*.rb
 335      # 3. config/initializers/*.rb found in extensions, in extension load order.
 336      #
 337      # In the now rare case where radiant is deployed as an ordinary rails application, step 1 is skipped 
 338      # because it is equivalent to step 2.
 339      #
 340      def load_application_initializers
 341        load_radiant_initializers unless deployed_as_app?
 342        super
 343        extension_loader.load_extension_initalizers
 344      end
 345 
 346      # Loads initializers found in RADIANT_ROOT/config/initializers.
 347      #
 348      def load_radiant_initializers
 349        Dir["#{RADIANT_ROOT}/config/initializers/**/*.rb"].sort.each do |initializer|
 350          load(initializer)
 351        end
 352      end
 353 
 354      # Extends the Rails initializer with some extra steps at the end of initialization:
 355      # * hook up radiant view paths in controllers and notifiers
 356      # * initialize the navigation tabs in the admin interface
 357      # * initialize the extendable partial sets that make up the admin interface
 358      # * call +activate+ on all radiant extensions
 359      # * add extension controller paths
 360      # * mark extension app paths for eager loading
 361      #
 362      def after_initialize
 363        super
 364        extension_loader.activate_extensions  # also calls initialize_views
 365        configuration.add_controller_paths(extension_loader.paths(:controller))
 366        configuration.add_eager_load_paths(extension_loader.paths(:eager_load))
 367      end
 368      
 369      # Initializes all the admin interface elements and views. Separate here so that it can be called
 370      # to reset the interface before extension (re)activation.
 371      #
 372      def initialize_views
 373        initialize_default_admin_tabs
 374        initialize_framework_views
 375        admin.load_default_regions
 376      end
 377      
 378      # Initializes the core admin tabs. Separate so that it can be invoked by itself in tests.
 379      #
 380      def initialize_default_admin_tabs
 381        admin.initialize_nav
 382      end
 383      
 384      # This adds extension view paths to the standard Rails::Initializer method. 
 385      # In environments that don't cache templates it reloads the path set on each request, 
 386      # so that new extension paths are noticed without a restart.
 387      #
 388      def initialize_framework_views
 389        view_paths = extension_loader.paths(:view).push(configuration.view_path)
 390        if ActionController::Base.view_paths.blank? || !ActionView::Base.cache_template_loading?
 391          ActionController::Base.view_paths = ActionView::Base.process_view_paths(view_paths)
 392        end
 393        if configuration.frameworks.include?(:action_mailer) && ActionMailer::Base.view_paths.blank? || !ActionView::Base.cache_template_loading?
 394          ActionMailer::Base.view_paths = ActionView::Base.process_view_paths(view_paths) if configuration.frameworks.include?(:action_mailer)
 395        end
 396      end 
 397 
 398      # Extends the Rails initializer to make sure that extension controller paths are available when routes 
 399      # are initialized.
 400      #
 401      def initialize_routing
 402        configuration.add_controller_paths(extension_loader.paths(:controller))
 403        configuration.add_eager_load_paths(extension_loader.paths(:eager_load))
 404        super
 405      end
 406 
 407      # Returns the Radiant::AdminUI singleton so that the initializer can set up the admin interface.
 408      #
 409      def admin
 410        configuration.admin
 411      end
 412 
 413      # Returns the ExtensionLoader singleton that will eventually load extensions.
 414      #
 415      def extension_loader
 416        ExtensionLoader.instance {|l| l.initializer = self }
 417      end
 418 
 419    end
 420  end