Adds a branch for unlimited project hierarchy.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/work@2148 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
require 'active_support'
|
||||
require File.join(File.dirname(__FILE__), 'engines/plugin')
|
||||
require File.join(File.dirname(__FILE__), 'engines/plugin/list')
|
||||
require File.join(File.dirname(__FILE__), 'engines/plugin/loader')
|
||||
require File.join(File.dirname(__FILE__), 'engines/plugin/locator')
|
||||
require File.join(File.dirname(__FILE__), 'engines/assets')
|
||||
require File.join(File.dirname(__FILE__), 'engines/rails_extensions/rails')
|
||||
|
||||
# == Parameters
|
||||
#
|
||||
# The Engines module has a number of public configuration parameters:
|
||||
#
|
||||
# [+public_directory+] The directory into which plugin assets should be
|
||||
# mirrored. Defaults to <tt>RAILS_ROOT/public/plugin_assets</tt>.
|
||||
# [+schema_info_table+] The table to use when storing plugin migration
|
||||
# version information. Defaults to +plugin_schema_info+.
|
||||
#
|
||||
# Additionally, there are a few flags which control the behaviour of
|
||||
# some of the features the engines plugin adds to Rails:
|
||||
#
|
||||
# [+disable_application_view_loading+] A boolean flag determining whether
|
||||
# or not views should be loaded from
|
||||
# the main <tt>app/views</tt> directory.
|
||||
# Defaults to false; probably only
|
||||
# useful when testing your plugin.
|
||||
# [+disable_application_code_loading+] A boolean flag determining whether
|
||||
# or not to load controllers/helpers
|
||||
# from the main +app+ directory,
|
||||
# if corresponding code exists within
|
||||
# a plugin. Defaults to false; again,
|
||||
# probably only useful when testing
|
||||
# your plugin.
|
||||
# [+disable_code_mixing+] A boolean flag indicating whether all plugin
|
||||
# copies of a particular controller/helper should
|
||||
# be loaded and allowed to override each other,
|
||||
# or if the first matching file should be loaded
|
||||
# instead. Defaults to false.
|
||||
#
|
||||
module Engines
|
||||
# The set of all loaded plugins
|
||||
mattr_accessor :plugins
|
||||
self.plugins = Engines::Plugin::List.new
|
||||
|
||||
# List of extensions to load, can be changed in init.rb before calling Engines.init
|
||||
mattr_accessor :rails_extensions
|
||||
self.rails_extensions = %w(action_mailer asset_helpers routing migrations dependencies)
|
||||
|
||||
# The name of the public directory to mirror public engine assets into.
|
||||
# Defaults to <tt>RAILS_ROOT/public/plugin_assets</tt>.
|
||||
mattr_accessor :public_directory
|
||||
self.public_directory = File.join(RAILS_ROOT, 'public', 'plugin_assets')
|
||||
|
||||
# The table in which to store plugin schema information. Defaults to
|
||||
# "plugin_schema_info".
|
||||
mattr_accessor :schema_info_table
|
||||
self.schema_info_table = "plugin_schema_info"
|
||||
|
||||
#--
|
||||
# These attributes control the behaviour of the engines extensions
|
||||
#++
|
||||
|
||||
# Set this to true if views should *only* be loaded from plugins
|
||||
mattr_accessor :disable_application_view_loading
|
||||
self.disable_application_view_loading = false
|
||||
|
||||
# Set this to true if controller/helper code shouldn't be loaded
|
||||
# from the application
|
||||
mattr_accessor :disable_application_code_loading
|
||||
self.disable_application_code_loading = false
|
||||
|
||||
# Set this ti true if code should not be mixed (i.e. it will be loaded
|
||||
# from the first valid path on $LOAD_PATH)
|
||||
mattr_accessor :disable_code_mixing
|
||||
self.disable_code_mixing = false
|
||||
|
||||
# This is used to determine which files are candidates for the "code
|
||||
# mixing" feature that the engines plugin provides, where classes from
|
||||
# plugins can be loaded, and then code from the application loaded
|
||||
# on top of that code to override certain methods.
|
||||
mattr_accessor :code_mixing_file_types
|
||||
self.code_mixing_file_types = %w(controller helper)
|
||||
|
||||
class << self
|
||||
def init
|
||||
load_extensions
|
||||
Engines::Assets.initialize_base_public_directory
|
||||
end
|
||||
|
||||
def logger
|
||||
RAILS_DEFAULT_LOGGER
|
||||
end
|
||||
|
||||
def load_extensions
|
||||
rails_extensions.each { |name| require "engines/rails_extensions/#{name}" }
|
||||
# load the testing extensions, if we are in the test environment.
|
||||
require "engines/testing" if RAILS_ENV == "test"
|
||||
end
|
||||
|
||||
def select_existing_paths(paths)
|
||||
paths.select { |path| File.directory?(path) }
|
||||
end
|
||||
|
||||
# The engines plugin will, by default, mix code from controllers and helpers,
|
||||
# allowing application code to override specific methods in the corresponding
|
||||
# controller or helper classes and modules. However, if other file types should
|
||||
# also be mixed like this, they can be added by calling this method. For example,
|
||||
# if you want to include "things" within your plugin and override them from
|
||||
# your applications, you should use the following layout:
|
||||
#
|
||||
# app/
|
||||
# +-- things/
|
||||
# | +-- one_thing.rb
|
||||
# | +-- another_thing.rb
|
||||
# ...
|
||||
# vendor/
|
||||
# +-- plugins/
|
||||
# +-- my_plugin/
|
||||
# +-- app/
|
||||
# +-- things/
|
||||
# +-- one_thing.rb
|
||||
# +-- another_thing.rb
|
||||
#
|
||||
# The important point here is that your "things" are named <whatever>_thing.rb,
|
||||
# and that they are placed within plugin/app/things (the pluralized form of 'thing').
|
||||
#
|
||||
# It's important to note that you'll also want to ensure that the "things" are
|
||||
# on your load path in your plugin's init.rb:
|
||||
#
|
||||
# Rails.plugins[:my_plugin].code_paths << "app/things"
|
||||
#
|
||||
def mix_code_from(*types)
|
||||
self.code_mixing_file_types += types.map { |x| x.to_s.singularize }
|
||||
end
|
||||
|
||||
# A general purpose method to mirror a directory (+source+) into a destination
|
||||
# directory, including all files and subdirectories. Files will not be mirrored
|
||||
# if they are identical already (checked via FileUtils#identical?).
|
||||
def mirror_files_from(source, destination)
|
||||
return unless File.directory?(source)
|
||||
|
||||
# TODO: use Rake::FileList#pathmap?
|
||||
source_files = Dir[source + "/**/*"]
|
||||
source_dirs = source_files.select { |d| File.directory?(d) }
|
||||
source_files -= source_dirs
|
||||
|
||||
source_dirs.each do |dir|
|
||||
# strip down these paths so we have simple, relative paths we can
|
||||
# add to the destination
|
||||
target_dir = File.join(destination, dir.gsub(source, ''))
|
||||
begin
|
||||
FileUtils.mkdir_p(target_dir)
|
||||
rescue Exception => e
|
||||
raise "Could not create directory #{target_dir}: \n" + e
|
||||
end
|
||||
end
|
||||
|
||||
source_files.each do |file|
|
||||
begin
|
||||
target = File.join(destination, file.gsub(source, ''))
|
||||
unless File.exist?(target) && FileUtils.identical?(file, target)
|
||||
FileUtils.cp(file, target)
|
||||
end
|
||||
rescue Exception => e
|
||||
raise "Could not copy #{file} to #{target}: \n" + e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
module Engines
|
||||
module Assets
|
||||
class << self
|
||||
@@readme = %{Files in this directory are automatically generated from your plugins.
|
||||
They are copied from the 'assets' directories of each plugin into this directory
|
||||
each time Rails starts (script/server, script/console... and so on).
|
||||
Any edits you make will NOT persist across the next server restart; instead you
|
||||
should edit the files within the <plugin_name>/assets/ directory itself.}
|
||||
|
||||
# Ensure that the plugin asset subdirectory of RAILS_ROOT/public exists, and
|
||||
# that we've added a little warning message to instruct developers not to mess with
|
||||
# the files inside, since they're automatically generated.
|
||||
def initialize_base_public_directory
|
||||
dir = Engines.public_directory
|
||||
unless File.exist?(dir)
|
||||
Engines.logger.debug "Creating public engine files directory '#{dir}'"
|
||||
FileUtils.mkdir_p(dir)
|
||||
end
|
||||
readme = File.join(dir, "README")
|
||||
File.open(readme, 'w') { |f| f.puts @@readme } unless File.exist?(readme)
|
||||
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_files_for(plugin)
|
||||
return if plugin.public_directory.nil?
|
||||
begin
|
||||
Engines.logger.debug "Attempting to copy plugin assets from '#{plugin.public_directory}' to '#{Engines.public_directory}'"
|
||||
Engines.mirror_files_from(plugin.public_directory, File.join(Engines.public_directory, plugin.name))
|
||||
rescue Exception => e
|
||||
Engines.logger.warn "WARNING: Couldn't create the public file structure for plugin '#{plugin.name}'; Error follows:"
|
||||
Engines.logger.warn e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,126 @@
|
||||
# An instance of Plugin is created for each plugin loaded by Rails, and
|
||||
# stored in the <tt>Engines.plugins</tt> PluginList
|
||||
# (see Engines::RailsExtensions::RailsInitializer for more details).
|
||||
#
|
||||
# Engines.plugins[:plugin_name]
|
||||
#
|
||||
# If this plugin contains paths in directories other than <tt>app/controllers</tt>,
|
||||
# <tt>app/helpers</tt>, <tt>app/models</tt> and <tt>components</tt>, 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.
|
||||
module Engines
|
||||
class Plugin < Rails::Plugin
|
||||
# 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"]
|
||||
attr_accessor :code_paths
|
||||
|
||||
# Plugins can add paths to this attribute in init.rb if they need
|
||||
# controllers loaded from additional locations.
|
||||
attr_accessor :controller_paths
|
||||
|
||||
# The directory in this plugin to mirror into the shared directory
|
||||
# under +public+.
|
||||
#
|
||||
# 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
|
||||
Engines.select_existing_paths(%w(assets public).map { |p| File.join(directory, p) }).first
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def initialize(directory)
|
||||
super directory
|
||||
@code_paths = default_code_paths
|
||||
@controller_paths = default_controller_paths
|
||||
@public_directory = default_public_directory
|
||||
end
|
||||
|
||||
# Returns a list of paths this plugin wishes to make available in $LOAD_PATH
|
||||
#
|
||||
# Overwrites the correspondend method in the superclass
|
||||
def load_paths
|
||||
report_nonexistant_or_empty_plugin! unless valid?
|
||||
select_existing_paths :code_paths
|
||||
end
|
||||
|
||||
# Extends the superclass' load method to additionally mirror public assets
|
||||
def load(initializer)
|
||||
return if loaded?
|
||||
super initializer
|
||||
add_plugin_view_paths
|
||||
Assets.mirror_files_for(self)
|
||||
end
|
||||
|
||||
# for code_paths and controller_paths select those paths that actually
|
||||
# exist in the plugin's directory
|
||||
def select_existing_paths(name)
|
||||
Engines.select_existing_paths(self.send(name).map { |p| File.join(directory, p) })
|
||||
end
|
||||
|
||||
def add_plugin_view_paths
|
||||
view_path = File.join(directory, 'app', 'views')
|
||||
if File.exist?(view_path)
|
||||
ActionController::Base.prepend_view_path(view_path) # push it just underneath the app
|
||||
ActionView::TemplateFinder.process_view_paths(view_path)
|
||||
end
|
||||
end
|
||||
|
||||
# The path to this plugin's public files
|
||||
def public_asset_directory
|
||||
"#{File.basename(Engines.public_directory)}/#{name}"
|
||||
end
|
||||
|
||||
# The path to this plugin's routes file
|
||||
def routes_path
|
||||
File.join(directory, "routes.rb")
|
||||
end
|
||||
|
||||
# The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
|
||||
def migration_directory
|
||||
File.join(self.directory, '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::Plugin::Migrator for more
|
||||
# information.
|
||||
def migrate(version = nil)
|
||||
Engines::Plugin::Migrator.migrate_plugin(self, version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# The PluginList class is an array, enhanced to allow access to loaded plugins
|
||||
# by name, and iteration over loaded plugins in order of priority. This array is used
|
||||
# by Engines::RailsExtensions::RailsInitializer to create the Engines.plugins array.
|
||||
#
|
||||
# Each loaded plugin has a corresponding Plugin instance within this array, and
|
||||
# the order the plugins were loaded is reflected in the entries in this array.
|
||||
#
|
||||
# For more information, see the Rails module.
|
||||
module Engines
|
||||
class Plugin
|
||||
class List < Array
|
||||
# Finds plugins with the set with the given name (accepts Strings or Symbols), or
|
||||
# index. So, Engines.plugins[0] returns the first-loaded Plugin, and Engines.plugins[:engines]
|
||||
# returns the Plugin instance for the engines plugin itself.
|
||||
def [](name_or_index)
|
||||
if name_or_index.is_a?(Fixnum)
|
||||
super
|
||||
else
|
||||
self.find { |plugin| plugin.name.to_s == name_or_index.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
# Go through each plugin, highest priority first (last loaded first). Effectively,
|
||||
# this is like <tt>Engines.plugins.reverse</tt>
|
||||
def by_precedence
|
||||
reverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module Engines
|
||||
class Plugin
|
||||
class Loader < Rails::Plugin::Loader
|
||||
protected
|
||||
def register_plugin_as_loaded(plugin)
|
||||
super plugin
|
||||
Engines.plugins << plugin
|
||||
register_to_routing(plugin)
|
||||
end
|
||||
|
||||
# Registers the plugin's controller_paths for the routing system.
|
||||
def register_to_routing(plugin)
|
||||
initializer.configuration.controller_paths += plugin.select_existing_paths(:controller_paths)
|
||||
initializer.configuration.controller_paths.uniq!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
module Engines
|
||||
class Plugin
|
||||
class FileSystemLocator < Rails::Plugin::FileSystemLocator
|
||||
def create_plugin(path)
|
||||
plugin = Engines::Plugin.new(path)
|
||||
plugin.valid? ? plugin : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# The Plugin::Migrator class contains the logic to run migrations from
|
||||
# within plugin directories. The directory in which a plugin's migrations
|
||||
# should be is determined by the Plugin#migration_directory method.
|
||||
#
|
||||
# To migrate a plugin, you can simple call the migrate method (Plugin#migrate)
|
||||
# with the version number that plugin should be at. The plugin's migrations
|
||||
# will then be used to migrate up (or down) to the given version.
|
||||
#
|
||||
# For more information, see Engines::RailsExtensions::Migrations
|
||||
class Engines::Plugin::Migrator < ActiveRecord::Migrator
|
||||
|
||||
# We need to be able to set the 'current' engine being migrated.
|
||||
cattr_accessor :current_plugin
|
||||
|
||||
# Runs the migrations from a plugin, up (or down) to the version given
|
||||
def self.migrate_plugin(plugin, version)
|
||||
self.current_plugin = plugin
|
||||
# There seems to be a bug in Rails' own migrations, where migrating
|
||||
# to the existing version causes all migrations to be run where that
|
||||
# migration number doesn't exist (i.e. zero). We could fix this by
|
||||
# removing the line if the version hits zero...?
|
||||
return if current_version(plugin) == version
|
||||
migrate(plugin.migration_directory, version)
|
||||
end
|
||||
|
||||
# Returns the name of the table used to store schema information about
|
||||
# installed plugins.
|
||||
#
|
||||
# See Engines.schema_info_table for more details.
|
||||
def self.schema_info_table_name
|
||||
proper_table_name Engines.schema_info_table
|
||||
end
|
||||
|
||||
# Returns the current version of the given plugin
|
||||
def self.current_version(plugin=current_plugin)
|
||||
result = ActiveRecord::Base.connection.select_one(<<-ESQL
|
||||
SELECT version FROM #{schema_info_table_name}
|
||||
WHERE plugin_name = '#{plugin.name}'
|
||||
ESQL
|
||||
)
|
||||
if result
|
||||
result["version"].to_i
|
||||
else
|
||||
# There probably isn't an entry for this engine in the migration info table.
|
||||
# We need to create that entry, and set the version to 0
|
||||
ActiveRecord::Base.connection.execute(<<-ESQL
|
||||
INSERT INTO #{schema_info_table_name} (version, plugin_name)
|
||||
VALUES (0,'#{plugin.name}')
|
||||
ESQL
|
||||
)
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def migrated(plugin=current_plugin)
|
||||
current = ActiveRecord::Base.connection.select_value(<<-ESQL
|
||||
SELECT version FROM #{self.class.schema_info_table_name}
|
||||
WHERE plugin_name = '#{plugin.name}'
|
||||
ESQL
|
||||
).to_i
|
||||
current ? (1..current).to_a : []
|
||||
end
|
||||
|
||||
# Sets the version of the plugin in Engines::Plugin::Migrator.current_plugin to
|
||||
# the given version.
|
||||
def record_version_state_after_migrating(version)
|
||||
ActiveRecord::Base.connection.update(<<-ESQL
|
||||
UPDATE #{self.class.schema_info_table_name}
|
||||
SET version = #{down? ? version.to_i - 1 : version.to_i}
|
||||
WHERE plugin_name = '#{self.current_plugin.name}'
|
||||
ESQL
|
||||
)
|
||||
end
|
||||
end
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
# The way ActionMailer is coded in terms of finding templates is very restrictive, to the point
|
||||
# where all templates for rendering must exist under the single base path. This is difficult to
|
||||
# work around without re-coding significant parts of the action mailer code.
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# The MailTemplates module overrides two (private) methods from ActionMailer to enable mail
|
||||
# templates within plugins:
|
||||
#
|
||||
# [+template_path+] which now produces the contents of #template_paths
|
||||
# [+initialize_template_class+] which now find the first matching template and creates
|
||||
# an ActionVew::Base instance with the correct view_paths
|
||||
#
|
||||
# Ideally ActionMailer would use the same template-location logic as ActionView, and the same
|
||||
# view paths as ActionController::Base.view_paths, but it currently does not.
|
||||
module Engines::RailsExtensions::ActionMailer
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
# TODO commented this out because it seems to break ActionMailer
|
||||
# how can this be fixed?
|
||||
|
||||
alias_method_chain :template_path, :engine_additions
|
||||
alias_method_chain :initialize_template_class, :engine_additions
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#--
|
||||
# ActionMailer::Base#create uses two mechanisms to determine the proper template file(s)
|
||||
# to load. Firstly, it searches within the template_root for files that much the explicit
|
||||
# (or implicit) part encodings (like signup.text.plain.erb for the signup action).
|
||||
# This is how implicit multipart emails are built, by the way.
|
||||
#
|
||||
# Secondly, it then creates an ActionMailer::Base instance with it's view_paths parameter
|
||||
# set to the template_root, so that ActionMailer will then take over rendering the
|
||||
# templates.
|
||||
#
|
||||
# Ideally, ActionMailer would pass the same set of view paths as it gets in a normal
|
||||
# request (i.e. ActionController::Base.view_paths), so that all possible view paths
|
||||
# were searched. However, this seems to introduce some problems with helper modules.
|
||||
#
|
||||
# So instead, and because we have to fool these two independent parts of ActionMailer,
|
||||
# we fudge with the mechanisms it uses to find the templates (via template_paths, and
|
||||
# template_path_with_engine_additions), and then intercept the creation of the ActionView
|
||||
# instance so we can set the view_paths (in initialize_template_class_with_engine_additions).
|
||||
#++
|
||||
|
||||
# Returns all possible template paths for the current mailer, including those
|
||||
# within the loaded plugins.
|
||||
def template_paths
|
||||
paths = Engines.plugins.by_precedence.map { |p| "#{p.directory}/app/views/#{mailer_name}" }
|
||||
paths.unshift(template_path_without_engine_additions) unless Engines.disable_application_view_loading
|
||||
paths
|
||||
end
|
||||
|
||||
# Return something that Dir[] can glob against. This method is called in
|
||||
# ActionMailer::Base#create! and used as part of an argument to Dir. We can
|
||||
# take advantage of this by using some of the features of Dir.glob to search
|
||||
# multiple paths for matching files.
|
||||
def template_path_with_engine_additions
|
||||
"{#{template_paths.join(",")}}"
|
||||
end
|
||||
|
||||
# Return an instance of ActionView::Base with the view paths set to all paths
|
||||
# in ActionController::Base.view_paths (i.e. including all plugin view paths)
|
||||
def initialize_template_class_with_engine_additions(assigns)
|
||||
# I'd like to just return this, but I get problems finding methods in helper
|
||||
# modules if the method implemention from the regular class is not called
|
||||
#
|
||||
# ActionView::Base.new(ActionController::Base.view_paths.dup, assigns, self)
|
||||
renderer = initialize_template_class_without_engine_additions(assigns)
|
||||
renderer.finder.view_paths = ActionController::Base.view_paths.dup
|
||||
renderer
|
||||
end
|
||||
end
|
||||
|
||||
# We don't need to do this if ActionMailer hasn't been loaded.
|
||||
if Object.const_defined?(:ActionMailer)
|
||||
module ::ActionMailer #:nodoc:
|
||||
class Base #:nodoc:
|
||||
include Engines::RailsExtensions::ActionMailer
|
||||
end
|
||||
end
|
||||
end
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# The engines plugin makes it trivial to share public assets using plugins.
|
||||
# To do this, include an <tt>assets</tt> directory within your plugin, and put
|
||||
# your javascripts, stylesheets and images in subdirectories of that folder:
|
||||
#
|
||||
# my_plugin
|
||||
# |- init.rb
|
||||
# |- lib/
|
||||
# |- assets/
|
||||
# |- javascripts/
|
||||
# | |- my_functions.js
|
||||
# |
|
||||
# |- stylesheets/
|
||||
# | |- my_styles.css
|
||||
# |
|
||||
# |- images/
|
||||
# |- my_face.jpg
|
||||
#
|
||||
# Files within the <tt>asset</tt> structure are automatically mirrored into
|
||||
# a publicly-accessible folder each time your application starts (see
|
||||
# Engines::Assets#mirror_assets).
|
||||
#
|
||||
#
|
||||
# == Using plugin assets in views
|
||||
#
|
||||
# It's also simple to use Rails' helpers in your views to use plugin assets.
|
||||
# The default helper methods have been enhanced by the engines plugin to accept
|
||||
# a <tt>:plugin</tt> option, indicating the plugin containing the desired asset.
|
||||
#
|
||||
# For example, it's easy to use plugin assets in your layouts:
|
||||
#
|
||||
# <%= stylesheet_link_tag "my_styles", :plugin => "my_plugin", :media => "screen" %>
|
||||
# <%= javascript_include_tag "my_functions", :plugin => "my_plugin" %>
|
||||
#
|
||||
# ... and similarly in views and partials, it's easy to use plugin images:
|
||||
#
|
||||
# <%= image_tag "my_face", :plugin => "my_plugin" %>
|
||||
# <!-- or -->
|
||||
# <%= image_path "my_face", :plugin => "my_plugin" %>
|
||||
#
|
||||
# Where the default helpers allow the specification of more than one file (i.e. the
|
||||
# javascript and stylesheet helpers), you can do similarly for multiple assets from
|
||||
# within a single plugin.
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# This module enhances four of the methods from ActionView::Helpers::AssetTagHelper:
|
||||
#
|
||||
# * stylesheet_link_tag
|
||||
# * javascript_include_tag
|
||||
# * image_path
|
||||
# * image_tag
|
||||
#
|
||||
# Each one of these methods now accepts the key/value pair <tt>:plugin => "plugin_name"</tt>,
|
||||
# which can be used to specify the originating plugin for any assets.
|
||||
#
|
||||
module Engines::RailsExtensions::AssetHelpers
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
[:stylesheet_link_tag, :javascript_include_tag, :image_path, :image_tag].each do |m|
|
||||
alias_method_chain m, :engine_additions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Adds plugin functionality to Rails' default stylesheet_link_tag method.
|
||||
def stylesheet_link_tag_with_engine_additions(*sources)
|
||||
stylesheet_link_tag_without_engine_additions(*Engines::RailsExtensions::AssetHelpers.pluginify_sources("stylesheets", *sources))
|
||||
end
|
||||
|
||||
# Adds plugin functionality to Rails' default javascript_include_tag method.
|
||||
def javascript_include_tag_with_engine_additions(*sources)
|
||||
javascript_include_tag_without_engine_additions(*Engines::RailsExtensions::AssetHelpers.pluginify_sources("javascripts", *sources))
|
||||
end
|
||||
|
||||
#--
|
||||
# Our modified image_path now takes a 'plugin' option, though it doesn't require it
|
||||
#++
|
||||
|
||||
# Adds plugin functionality to Rails' default image_path method.
|
||||
def image_path_with_engine_additions(source, options={})
|
||||
options.stringify_keys!
|
||||
source = Engines::RailsExtensions::AssetHelpers.plugin_asset_path(options["plugin"], "images", source) if options["plugin"]
|
||||
image_path_without_engine_additions(source)
|
||||
end
|
||||
|
||||
# Adds plugin functionality to Rails' default image_tag method.
|
||||
def image_tag_with_engine_additions(source, options={})
|
||||
options.stringify_keys!
|
||||
if options["plugin"]
|
||||
source = Engines::RailsExtensions::AssetHelpers.plugin_asset_path(options["plugin"], "images", source)
|
||||
options.delete("plugin")
|
||||
end
|
||||
image_tag_without_engine_additions(source, options)
|
||||
end
|
||||
|
||||
#--
|
||||
# The following are methods on this module directly because of the weird-freaky way
|
||||
# Rails creates the helper instance that views actually get
|
||||
#++
|
||||
|
||||
# Convert sources to the paths for the given plugin, if any plugin option is given
|
||||
def self.pluginify_sources(type, *sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
sources.map! { |s| plugin_asset_path(options["plugin"], type, s) } if options["plugin"]
|
||||
options.delete("plugin") # we don't want it appearing in the HTML
|
||||
sources << options # re-add options
|
||||
end
|
||||
|
||||
# Returns the publicly-addressable relative URI for the given asset, type and plugin
|
||||
def self.plugin_asset_path(plugin_name, type, asset)
|
||||
raise "No plugin called '#{plugin_name}' - please use the full name of a loaded plugin." if Engines.plugins[plugin_name].nil?
|
||||
"/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}/#{asset}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module ::ActionView::Helpers::AssetTagHelper #:nodoc:
|
||||
include Engines::RailsExtensions::AssetHelpers
|
||||
end
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
# One of the magic features that that engines plugin provides is the ability to
|
||||
# override selected methods in controllers and helpers from your application.
|
||||
# This is achieved by trapping requests to load those files, and then mixing in
|
||||
# code from plugins (in the order the plugins were loaded) before finally loading
|
||||
# any versions from the main +app+ directory.
|
||||
#
|
||||
# The behaviour of this extension is output to the log file for help when
|
||||
# debugging.
|
||||
#
|
||||
# == Example
|
||||
#
|
||||
# A plugin contains the following controller in <tt>plugin/app/controllers/my_controller.rb</tt>:
|
||||
#
|
||||
# class MyController < ApplicationController
|
||||
# def index
|
||||
# @name = "HAL 9000"
|
||||
# end
|
||||
# def list
|
||||
# @robots = Robot.find(:all)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# In one application that uses this plugin, we decide that the name used in the
|
||||
# index action should be "Robbie", not "HAL 9000". To override this single method,
|
||||
# we create the corresponding controller in our application
|
||||
# (<tt>RAILS_ROOT/app/controllers/my_controller.rb</tt>), and redefine the method:
|
||||
#
|
||||
# class MyController < ApplicationController
|
||||
# def index
|
||||
# @name = "Robbie"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The list method remains as it was defined in the plugin controller.
|
||||
#
|
||||
# The same basic principle applies to helpers, and also views and partials (although
|
||||
# view overriding is performed in Engines::RailsExtensions::Templates; see that
|
||||
# module for more information).
|
||||
#
|
||||
# === What about models?
|
||||
#
|
||||
# Unfortunately, it's not possible to provide this kind of magic for models.
|
||||
# The only reason why it's possible for controllers and helpers is because
|
||||
# they can be recognised by their filenames ("whatever_controller", "jazz_helper"),
|
||||
# whereas models appear the same as any other typical Ruby library ("node",
|
||||
# "user", "image", etc.).
|
||||
#
|
||||
# If mixing were allowed in models, it would mean code mixing for *every*
|
||||
# file that was loaded via +require_or_load+, and this could result in
|
||||
# problems where, for example, a Node model might start to include
|
||||
# functionality from another file called "node" somewhere else in the
|
||||
# <tt>$LOAD_PATH</tt>.
|
||||
#
|
||||
# One way to overcome this is to provide model functionality as a module in
|
||||
# a plugin, which developers can then include into their own model
|
||||
# implementations.
|
||||
#
|
||||
# Another option is to provide an abstract model (see the ActiveRecord::Base
|
||||
# documentation) and have developers subclass this model in their own
|
||||
# application if they must.
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# The Engines::RailsExtensions::Dependencies module includes a method to
|
||||
# override Dependencies.require_or_load, which is called to load code needed
|
||||
# by Rails as it encounters constants that aren't defined.
|
||||
#
|
||||
# This method is enhanced with the code-mixing features described above.
|
||||
#
|
||||
module Engines::RailsExtensions::Dependencies
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval { alias_method_chain :require_or_load, :engine_additions }
|
||||
end
|
||||
|
||||
# Attempt to load the given file from any plugins, as well as the application.
|
||||
# This performs the 'code mixing' magic, allowing application controllers and
|
||||
# helpers to override single methods from those in plugins.
|
||||
# If the file can be found in any plugins, it will be loaded first from those
|
||||
# locations. Finally, the application version is loaded, using Ruby's behaviour
|
||||
# to replace existing methods with their new definitions.
|
||||
#
|
||||
# If <tt>Engines.disable_code_mixing == true</tt>, the first controller/helper on the
|
||||
# <tt>$LOAD_PATH</tt> will be used (plugins' +app+ directories are always lower on the
|
||||
# <tt>$LOAD_PATH</tt> than the main +app+ directory).
|
||||
#
|
||||
# If <tt>Engines.disable_application_code_loading == true</tt>, controllers will
|
||||
# not be loaded from the main +app+ directory *if* they are present in any
|
||||
# plugins.
|
||||
#
|
||||
# Returns true if the file could be loaded (from anywhere); false otherwise -
|
||||
# mirroring the behaviour of +require_or_load+ from Rails (which mirrors
|
||||
# that of Ruby's own +require+, I believe).
|
||||
def require_or_load_with_engine_additions(file_name, const_path=nil)
|
||||
return require_or_load_without_engine_additions(file_name, const_path) if Engines.disable_code_mixing
|
||||
|
||||
file_loaded = false
|
||||
|
||||
# try and load the plugin code first
|
||||
# can't use model, as there's nothing in the name to indicate that the file is a 'model' file
|
||||
# rather than a library or anything else.
|
||||
Engines.code_mixing_file_types.each do |file_type|
|
||||
# if we recognise this type
|
||||
# (this regexp splits out the module/filename from any instances of app/#{type}, so that
|
||||
# modules are still respected.)
|
||||
if file_name =~ /^(.*app\/#{file_type}s\/)?(.*_#{file_type})(\.rb)?$/
|
||||
base_name = $2
|
||||
# ... go through the plugins from first started to last, so that
|
||||
# code with a high precedence (started later) will override lower precedence
|
||||
# implementations
|
||||
Engines.plugins.each do |plugin|
|
||||
plugin_file_name = File.expand_path(File.join(plugin.directory, 'app', "#{file_type}s", base_name))
|
||||
Engines.logger.debug("checking plugin '#{plugin.name}' for '#{base_name}'")
|
||||
if File.file?("#{plugin_file_name}.rb")
|
||||
Engines.logger.debug("==> loading from plugin '#{plugin.name}'")
|
||||
file_loaded = true if require_or_load_without_engine_additions(plugin_file_name, const_path)
|
||||
end
|
||||
end
|
||||
|
||||
# finally, load any application-specific controller classes using the 'proper'
|
||||
# rails load mechanism, EXCEPT when we're testing engines and could load this file
|
||||
# from an engine
|
||||
if Engines.disable_application_code_loading
|
||||
Engines.logger.debug("loading from application disabled.")
|
||||
else
|
||||
# Ensure we are only loading from the /app directory at this point
|
||||
app_file_name = File.join(RAILS_ROOT, 'app', "#{file_type}s", "#{base_name}")
|
||||
if File.file?("#{app_file_name}.rb")
|
||||
Engines.logger.debug("loading from application: #{base_name}")
|
||||
file_loaded = true if require_or_load_without_engine_additions(app_file_name, const_path)
|
||||
else
|
||||
Engines.logger.debug("(file not found in application)")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# if we managed to load a file, return true. If not, default to the original method.
|
||||
# Note that this relies on the RHS of a boolean || not to be evaluated if the LHS is true.
|
||||
file_loaded || require_or_load_without_engine_additions(file_name, const_path)
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport::Dependencies.send :include, Engines::RailsExtensions::Dependencies
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
# Contains the enhancements to Rails' migrations system to support the
|
||||
# Engines::Plugin::Migrator. See Engines::RailsExtensions::Migrations for more
|
||||
# information.
|
||||
|
||||
require "engines/plugin/migrator"
|
||||
|
||||
# = Plugins and Migrations: Background
|
||||
#
|
||||
# Rails uses migrations to describe changes to the databases as your application
|
||||
# evolves. Each change to your application - adding and removing models, most
|
||||
# commonly - might require tweaks to your schema in the form of new tables, or new
|
||||
# columns on existing tables, or possibly the removal of tables or columns. Migrations
|
||||
# can even include arbitrary code to *transform* data as the underlying schema
|
||||
# changes.
|
||||
#
|
||||
# The point is that at any particular stage in your application's development,
|
||||
# migrations serve to transform the database into a state where it is compatible
|
||||
# and appropriate at that time.
|
||||
#
|
||||
# == What about plugins?
|
||||
#
|
||||
# If you want to share models using plugins, chances are that you might also
|
||||
# want to include the corresponding migrations to create tables for those models.
|
||||
# With the engines plugin installed, plugins can carry migration data easily:
|
||||
#
|
||||
# vendor/
|
||||
# |
|
||||
# plugins/
|
||||
# |
|
||||
# my_plugin/
|
||||
# |- init.rb
|
||||
# |- lib/
|
||||
# |- db/
|
||||
# |-migrate/
|
||||
# |- 001_do_something.rb
|
||||
# |- 002_and_something_else.rb
|
||||
# |- ...
|
||||
#
|
||||
# When you install a plugin which contains migrations, you are undertaking a
|
||||
# further step in the development of your application, the same as the addition
|
||||
# of any other code. With this in mind, you may want to 'roll back' the
|
||||
# installation of this plugin at some point, and the database should be able
|
||||
# to migrate back to the point without this plugin in it too.
|
||||
#
|
||||
# == An example
|
||||
#
|
||||
# For example, our current application is at version 14 (according to the
|
||||
# +schema_info+ table), when we decide that we want to add a tagging plugin. The
|
||||
# tagging plugin chosen includes migrations to create the tables it requires
|
||||
# (say, _tags_ and _taggings_, for instance), along with the models and helpers
|
||||
# one might expect.
|
||||
#
|
||||
# After installing this plugin, these tables should be created in our database.
|
||||
# Rather than running the migrations directly from the plugin, they should be
|
||||
# integrated into our main migration stream in order to accurately reflect the
|
||||
# state of our application's database *at this moment in time*.
|
||||
#
|
||||
# $ script/generate plugin_migration
|
||||
# exists db/migrate
|
||||
# create db/migrate/015_migrate_tagging_plugin_to_version_3.rb
|
||||
#
|
||||
# This migration will take our application to version 15, and contains the following,
|
||||
# typical migration code:
|
||||
#
|
||||
# class MigrateTaggingPluginToVersion3 < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# Engines.plugins[:tagging].migrate(3)
|
||||
# end
|
||||
# def self.down
|
||||
# Engines.plugins[:tagging].migrate(0)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# When we migrate our application up, using <tt>rake db:migrate</tt> as normal,
|
||||
# the plugin will be migrated up to its latest version (3 in this example). If we
|
||||
# ever decide to migrate the application back to the state it was in at version 14,
|
||||
# the plugin migrations will be taken back down to version 0 (which, typically,
|
||||
# would remove all tables the plugin migrations define).
|
||||
#
|
||||
# == Upgrading plugins
|
||||
#
|
||||
# It might happen that later in an application's life, we update to a new version of
|
||||
# the tagging plugin which requires some changes to our database. The tagging plugin
|
||||
# provides these changes in the form of its own migrations.
|
||||
#
|
||||
# In this case, we just need to re-run the plugin_migration generator to create a
|
||||
# new migration from the current revision to the newest one:
|
||||
#
|
||||
# $ script/generate plugin_migration
|
||||
# exists db/migrate
|
||||
# create db/migrate/023_migrate_tagging_plugin_to_version_5.rb
|
||||
#
|
||||
# The contents of this migration are:
|
||||
#
|
||||
# class MigrateTaggingPluginToVersion3 < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# Engines.plugins[:tagging].migrate(5)
|
||||
# end
|
||||
# def self.down
|
||||
# Engines.plugins[:tagging].migrate(3)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Notice that if we were to migrate down to revision 22 or lower, the tagging plugin
|
||||
# will be migrated back down to version 3 - the version we were previously at.
|
||||
#
|
||||
#
|
||||
# = Creating migrations in plugins
|
||||
#
|
||||
# In order to use the plugin migration functionality that engines provides, a plugin
|
||||
# only needs to provide regular migrations in a <tt>db/migrate</tt> folder within it.
|
||||
#
|
||||
# = Explicitly migrating plugins
|
||||
#
|
||||
# It's possible to migrate plugins within your own migrations, or any other code.
|
||||
# Simply get the Plugin instance, and its Plugin#migrate method with the version
|
||||
# you wish to end up at:
|
||||
#
|
||||
# Engines.plugins[:whatever].migrate(version)
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# The Engines::RailsExtensions::Migrations module defines extensions for Rails'
|
||||
# migration systems. Specifically:
|
||||
#
|
||||
# * Adding a hook to initialize_schema_migrations_table to create the plugin schema
|
||||
# info table.
|
||||
#
|
||||
module Engines::RailsExtensions::Migrations
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval { alias_method_chain :initialize_schema_migrations_table, :engine_additions }
|
||||
end
|
||||
|
||||
# Create the schema tables, and ensure that the plugin schema table
|
||||
# is also initialized. The plugin schema info table is defined by
|
||||
# Engines::Plugin::Migrator.schema_info_table_name.
|
||||
def initialize_schema_migrations_table_with_engine_additions
|
||||
initialize_schema_migrations_table_without_engine_additions
|
||||
|
||||
# create the plugin schema stuff.
|
||||
begin
|
||||
execute <<-ESQL
|
||||
CREATE TABLE #{Engines::Plugin::Migrator.schema_info_table_name}
|
||||
(plugin_name #{type_to_sql(:string)}, version #{type_to_sql(:integer)})
|
||||
ESQL
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# Schema has been initialized
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ::ActiveRecord #:nodoc:
|
||||
module ConnectionAdapters #:nodoc:
|
||||
module SchemaStatements #:nodoc:
|
||||
include Engines::RailsExtensions::Migrations
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set ActiveRecord to ignore the plugin schema table by default
|
||||
::ActiveRecord::SchemaDumper.ignore_tables << Engines.schema_info_table
|
||||
@@ -0,0 +1,11 @@
|
||||
# This is only here to allow for backwards compability with Engines that
|
||||
# have been implemented based on Engines for Rails 1.2. It is preferred that
|
||||
# the plugin list be accessed via Engines.plugins.
|
||||
|
||||
module Rails
|
||||
# Returns the Engines::Plugin::List from Engines.plugins. It is preferable to
|
||||
# access Engines.plugins directly.
|
||||
def self.plugins
|
||||
Engines.plugins
|
||||
end
|
||||
end
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# Effective use of Rails' routes can help create a tidy and elegant set of URLs,
|
||||
# and is a significant part of creating an external API for your web application.
|
||||
#
|
||||
# When developing plugins which contain controllers, it seems obvious that including
|
||||
# the corresponding routes would be extremely useful. This is particularly true
|
||||
# when exposing RESTful resources using the new REST-ian features of Rails.
|
||||
#
|
||||
# == Including routes in your plugin
|
||||
#
|
||||
# The engines plugin makes it possible to include a set of routes within your plugin
|
||||
# very simply, as it turns out. In your plugin, you simply include a <tt>routes.rb</tt>
|
||||
# file like the one below at the root of your plugin:
|
||||
#
|
||||
# connect "/login", :controller => "my_plugin/account", :action => "login"
|
||||
#
|
||||
# # add a named route
|
||||
# logout "/logout", :controller => "my_plugin/account", :action => "logout"
|
||||
#
|
||||
# # some restful stuff
|
||||
# resources :things do |t|
|
||||
# t.resources :other_things
|
||||
# end
|
||||
#
|
||||
# Everywhere in a normal <tt>RAILS_ROOT/config/routes.rb</tt> file
|
||||
# where you might have <tt>map.connect</tt>, you just use <tt>connect</tt> in your
|
||||
# plugin's <tt>routes.rb</tt>.
|
||||
#
|
||||
# === Hooking it up in your application
|
||||
#
|
||||
# While it would be possible to have each plugin's routes automagically included into
|
||||
# the application's route set, to do so would actually be a stunningly bad idea. Route
|
||||
# priority is the key issue here. You, the application developer, needs to be in complete
|
||||
# control when it comes to specifying the priority of routes in your application, since
|
||||
# the ordering of your routes directly affects how Rails will interpret incoming requests.
|
||||
#
|
||||
# To add plugin routes into your application's <tt>routes.rb</tt> file, you need to explicitly
|
||||
# map them in using the Engines::RailsExtensions::Routing#from_plugin method:
|
||||
#
|
||||
# ApplicationController::Routing::Routes.draw do |map|
|
||||
#
|
||||
# map.connect "/app_stuff", :controller => "application_thing" # etc...
|
||||
#
|
||||
# # This line includes the routes from the given plugin at this point, giving you
|
||||
# # control over the priority of your application routes
|
||||
# map.from_plugin :your_plugin
|
||||
#
|
||||
# map.connect ":controller/:action/:id"
|
||||
# end
|
||||
#
|
||||
# By including routes in plugins which have controllers, you can now share in a simple way
|
||||
# a compact and elegant URL scheme which corresponds to those controllers.
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# The Engines::RailsExtensions::Routing module defines extensions to Rails'
|
||||
# routing (ActionController::Routing) mechanism such that routes can be loaded
|
||||
# from a given plugin.
|
||||
#
|
||||
# The key method is Engines::RailsExtensions::Routing#from_plugin, which can be called
|
||||
# within your application's <tt>config/routes.rb</tt> file to load plugin routes at that point.
|
||||
#
|
||||
module Engines::RailsExtensions::Routing
|
||||
# Loads the set of routes from within a plugin and evaluates them at this
|
||||
# point within an application's main <tt>routes.rb</tt> file.
|
||||
#
|
||||
# Plugin routes are loaded from <tt><plugin_root>/routes.rb</tt>.
|
||||
def from_plugin(name)
|
||||
map = self # to make 'map' available within the plugin route file
|
||||
routes_path = Engines.plugins[name].routes_path
|
||||
Engines.logger.debug "loading routes from #{routes_path}"
|
||||
eval(IO.read(routes_path), binding, routes_path) if File.file?(routes_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module ::ActionController #:nodoc:
|
||||
module Routing #:nodoc:
|
||||
class RouteSet #:nodoc:
|
||||
class Mapper #:nodoc:
|
||||
include Engines::RailsExtensions::Routing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
# Contains the enhancements to assist in testing plugins. See Engines::Testing
|
||||
# for more details.
|
||||
|
||||
require 'test/unit'
|
||||
|
||||
require 'tmpdir'
|
||||
require 'fileutils'
|
||||
|
||||
# In most cases, Rails' own plugin testing mechanisms are sufficient. However, there
|
||||
# are cases where plugins can be given a helping hand in the testing arena. This module
|
||||
# contains some methods to assist when testing plugins that contain fixtures.
|
||||
#
|
||||
# == Fixtures and plugins
|
||||
#
|
||||
# Since Rails' own fixtures method is fairly strict about where files can be loaded from,
|
||||
# the simplest approach when running plugin tests with fixtures is to simply copy all
|
||||
# fixtures into a single temporary location and inform the standard Rails mechanism to
|
||||
# use this directory, rather than RAILS_ROOT/test/fixtures.
|
||||
#
|
||||
# The Engines::Testing#setup_plugin_fixtures method does this, copying all plugin fixtures
|
||||
# into the temporary location before and tests are performed. This behaviour is invoked
|
||||
# the the rake tasks provided by the Engines plugin, in the "test:plugins" namespace. If
|
||||
# necessary, you can invoke the task manually.
|
||||
#
|
||||
# If you wish to take advantage of this, add a call to the Engines::Testing.set_fixture_path
|
||||
# method somewhere before your tests (in a test_helper file, or above the TestCase itself).
|
||||
#
|
||||
# = Testing plugins
|
||||
#
|
||||
# Normally testing a plugin will require that Rails is loaded, unless you are including
|
||||
# a skeleton Rails environment or set of mocks within your plugin tests. If you require
|
||||
# the Rails environment to be started, you must ensure that this actually happens; while
|
||||
# it's not obvious, your tests do not automatically run with Rails loaded.
|
||||
#
|
||||
# The simplest way to setup plugin tests is to include a test helper with the following
|
||||
# contents:
|
||||
#
|
||||
# # Load the normal Rails helper. This ensures the environment is loaded
|
||||
# require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
|
||||
# # Ensure that we are using the temporary fixture path
|
||||
# Engines::Testing.set_fixture_path
|
||||
#
|
||||
# Then run tests using the provided tasks (<tt>test:plugins</tt>, or the tasks that the engines
|
||||
# plugin provides - <tt>test:plugins:units</tt>, etc.).
|
||||
#
|
||||
# Alternatively, you can explicitly load the environment by adpating the contents of the
|
||||
# default <tt>test_helper</tt>:
|
||||
#
|
||||
# ENV["RAILS_ENV"] = "test"
|
||||
# # Note that we are requiring config/environment from the root of the enclosing application.
|
||||
# require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
|
||||
# require 'test_help'
|
||||
#
|
||||
module Engines::Testing
|
||||
mattr_accessor :temporary_fixtures_directory
|
||||
self.temporary_fixtures_directory = FileUtils.mkdir_p(File.join(Dir.tmpdir, "rails_fixtures"))
|
||||
|
||||
# Copies fixtures from plugins and the application into a temporary directory
|
||||
# (Engines::Testing.temporary_fixtures_directory).
|
||||
#
|
||||
# If a set of plugins is not given, fixtures are copied from all plugins in order
|
||||
# of precedence, meaning that plugins can 'overwrite' the fixtures of others if they are
|
||||
# loaded later; the application's fixtures are copied last, allowing any custom fixtures
|
||||
# to override those in the plugins. If no argument is given, plugins are loaded via
|
||||
# PluginList#by_precedence.
|
||||
#
|
||||
# This method is called by the engines-supplied plugin testing rake tasks
|
||||
def self.setup_plugin_fixtures(plugins = Engines.plugins.by_precedence)
|
||||
|
||||
# Copy all plugin fixtures, and then the application fixtures, into this directory
|
||||
plugins.each do |plugin|
|
||||
plugin_fixtures_directory = File.join(plugin.directory, "test", "fixtures")
|
||||
if File.directory?(plugin_fixtures_directory)
|
||||
Engines.mirror_files_from(plugin_fixtures_directory, self.temporary_fixtures_directory)
|
||||
end
|
||||
end
|
||||
Engines.mirror_files_from(File.join(RAILS_ROOT, "test", "fixtures"),
|
||||
self.temporary_fixtures_directory)
|
||||
end
|
||||
|
||||
# Sets the fixture path used by Test::Unit::TestCase to the temporary
|
||||
# directory which contains all plugin fixtures.
|
||||
def self.set_fixture_path
|
||||
Test::Unit::TestCase.fixture_path = self.temporary_fixtures_directory
|
||||
$LOAD_PATH.unshift self.temporary_fixtures_directory
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user