# An instance of Plugin is created for each plugin loaded by Rails, and
# stored in the Rails.plugins PluginList
# (see Engines::RailsExtensions::RailsInitializer for more details).
#
# Once the engines plugin is loaded, other plugins can take advantage of
# their own instances by accessing either Engines.current, or the preferred mechanism
#
# Rails.plugins[:plugin_name]
#
# Useful properties of this object include Plugin#version, which plugin developers
# can set in their init.rb scripts:
#
# Rails.plugins[:my_plugin].version = "1.4.2"
#
# Plugin developers can also access the contents of their about.yml files
# via Plugin#about, which returns a Hash if the about.yml file exists for
# this plugin. Note that if about.yml contains a "version" key, it will
# automatically be loaded into the version attribute described above.
#
# If this plugin contains paths in directories other than app/controllers,
# app/helpers, app/models and components, authors can
# declare this by adding extra paths to #code_paths:
#
# Rails.plugin[:my_plugin].code_paths << "app/sweepers" << "vendor/my_lib"
#
# Other properties of the Plugin instance can also be set.
class Plugin
# The name of this plugin
attr_accessor :name
# The directory in which this plugin is located
attr_accessor :root
# The version of this plugin
attr_accessor :version
# The about.yml information as a Hash, if it exists
attr_accessor :about
# Plugins can add code paths to this attribute in init.rb if they
# need plugin directories to be added to the load path, i.e.
#
# plugin.code_paths << 'app/other_classes'
#
# Defaults to ["app/controllers", "app/helpers", "app/models", "components"]
# (see #default_code_paths). NOTE: if you want to set this, you must
# ensure that the engines plugin is loaded before any plugins which
# reference this since it's not available before the engines plugin has worked
# its magic.
attr_accessor :code_paths
# Plugins can add paths to this attribute in init.rb if they need
# controllers loaded from additional locations. See also #default_controller_paths, and
# the caveat surrounding the #code_paths accessor.
attr_accessor :controller_paths
# The directory in this plugin to mirror into the shared directory
# under +public+. See Engines.initialize_base_public_directory
# for more information.
#
# Defaults to "assets" (see default_public_directory).
attr_accessor :public_directory
protected
# The default set of code paths which will be added to $LOAD_PATH
# and Dependencies.load_paths
def default_code_paths
# lib will actually be removed from the load paths when we call
# uniq! in #inject_into_load_paths, but it's important to keep it
# around (for the documentation tasks, for instance).
%w(app/controllers app/helpers app/models components lib)
end
# The default set of code paths which will be added to the routing system
def default_controller_paths
%w(app/controllers components)
end
# Attempts to detect the directory to use for public files.
# If +assets+ exists in the plugin, this will be used. If +assets+ is missing
# but +public+ is found, +public+ will be used.
def default_public_directory
%w(assets public).select { |dir| File.directory?(File.join(root, dir)) }.first || "assets"
end
public
# Creates a new Plugin instance, and loads any other data from about.yml
def initialize(name, path)
@name = name
@root = path
@code_paths = default_code_paths
@controller_paths = default_controller_paths
@public_directory = default_public_directory
load_about_information
end
# Load the information from about.yml. This Hash is then accessible
# from #about.
#
# If about.yml includes a "version", this will be assigned
# automatically into #version.
def load_about_information
about_path = File.join(self.root, 'about.yml')
if File.exist?(about_path)
@about = YAML.load(File.open(about_path).read)
@about.stringify_keys!
@version = @about["version"]
end
end
# Load the plugin. Since Rails takes care of evaluating init.rb and
# adding +lib+ to the $LOAD_PATH, we don't need to do that here (see
# Engines::RailsExtensions::RailsInitializer.load_plugins_with_engine_additions).
#
# Here we add controller/helper code to the appropriate load paths (see
# #inject_into_load_path) and mirror the plugin assets into the shared public
# directory (#mirror_public_assets).
def load
logger.debug "Plugin '#{name}': starting load."
inject_into_load_path
mirror_public_assets
logger.debug "Plugin '#{name}': loaded."
end
# Adds all directories in the +app+ and +lib+ directories within the engine
# to the three relevant load paths mechanism that Rails might use:
#
# * $LOAD_PATH
# * Dependencies.load_paths
# * ActionController::Routing.controller_paths
#
def inject_into_load_path
load_path_index = $LOAD_PATH.index(Engines.rails_final_load_path)
dependency_index = ::Dependencies.load_paths.index(Engines.rails_final_dependency_load_path)
# Add relevant paths under the engine root to the load path
code_paths.map { |p| File.join(root, p) }.each do |path|
if File.directory?(path)
# Add to the load paths
$LOAD_PATH.insert(load_path_index + 1, path)
# Add to the dependency system, for autoloading.
::Dependencies.load_paths.insert(dependency_index + 1, path)
end
end
# Add controllers to the Routing system specifically. We actually add our paths
# to the configuration too, since routing is started AFTER plugins are. Plugins
# which are loaded by engines specifically (i.e. because of the '*' in
# +config.plugins+) will need their paths added directly to the routing system,
# since at that point it has already been configured.
controller_paths.map { |p| File.join(root, p) }.each do |path|
if File.directory?(path)
ActionController::Routing.controller_paths << path
Rails.configuration.controller_paths << path
end
end
$LOAD_PATH.uniq!
::Dependencies.load_paths.uniq!
ActionController::Routing.controller_paths.uniq!
Rails.configuration.controller_paths.uniq!
end
# Replicates the subdirectories under the plugins's +assets+ (or +public+) directory into
# the corresponding public directory. See also Plugin#public_directory for more.
def mirror_public_assets
begin
source = File.join(root, self.public_directory)
# if there is no public directory, just return after this file
return if !File.exist?(source)
logger.debug "Attempting to copy plugin plugin asset files from '#{source}' to '#{Engines.public_directory}'"
Engines.mirror_files_from(source, File.join(Engines.public_directory, name))
rescue Exception => e
logger.warn "WARNING: Couldn't create the public file structure for plugin '#{name}'; Error follows:"
logger.warn e
end
end
# The path to this plugin's public files
def public_asset_directory
"#{File.basename(Engines.public_directory)}/#{name}"
end
# The directory containing this plugin's migrations (plugin/db/migrate)
def migration_directory
File.join(self.root, 'db', 'migrate')
end
# Returns the version number of the latest migration for this plugin. Returns
# nil if this plugin has no migrations.
def latest_migration
migrations = Dir[migration_directory+"/*.rb"]
return nil if migrations.empty?
migrations.map { |p| File.basename(p) }.sort.last.match(/0*(\d+)\_/)[1].to_i
end
# Migrate this plugin to the given version. See Engines::PluginMigrator for more
# information.
def migrate(version = nil)
Engines::PluginMigrator.migrate_plugin(self, version)
end
end