Adds engines 2.1.0 plugin.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1654 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
b5444b5fcd
commit
c28cbd5790
|
@ -0,0 +1,3 @@
|
||||||
|
.DS_Store
|
||||||
|
test_app
|
||||||
|
doc
|
|
@ -0,0 +1,267 @@
|
||||||
|
= EDGE
|
||||||
|
|
||||||
|
* Refactored the view loading to work with changes in Edge Rails
|
||||||
|
|
||||||
|
* Fixed integration of plugin migrations with the new, default timestamped migrations in Edge Rails
|
||||||
|
|
||||||
|
* Refactored tests into the plugin itself - the plugin can now generate its own test_app harness and run tests within it.
|
||||||
|
|
||||||
|
|
||||||
|
= 2.0.0 - (ANOTHER) MASSIVE INTERNAL REFACTORING
|
||||||
|
|
||||||
|
* Engines now conforms to the new plugin loading mechanism, delegating plugin load order and lots of other things to Rails itself.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.2.2
|
||||||
|
|
||||||
|
* Added the ability to code mix different types of files, cleaning up the existing code-mixing implementation slightly (Ticket #271)
|
||||||
|
|
||||||
|
|
||||||
|
= 1.2.1
|
||||||
|
|
||||||
|
* Added documentation to clarify some of the issues with Rails unloading classes that aren't required using "require_dependency" (Ticket #266)
|
||||||
|
|
||||||
|
* Fixed a bug where test_help was being loaded when it wasn't needed, and was actually causing problems (Ticket #265)
|
||||||
|
|
||||||
|
|
||||||
|
= 1.2.0 - MASSIVE INTERNAL REFACTORING
|
||||||
|
|
||||||
|
* !!!Support for Rails < 1.2 has been dropped!!!; if you are using Rails =< 1.1.6, please use Engines 1.1.6, available from http://svn.rails-engines.org/engines/tags/rel_1.1.6
|
||||||
|
|
||||||
|
* Engines are dead! Long live plugins! There is now no meaningful notion of an engine - all plugins can take advantage of the more powerful features that the engines plugin provides by including app directories, etc.
|
||||||
|
|
||||||
|
* Init_engine.rb is no longer used; please use the plugin-standard init.rb instead.
|
||||||
|
|
||||||
|
* Engines.start is no longer required; please use the config.plugins array provided by Rails instead
|
||||||
|
|
||||||
|
* To get the most benefit from Engines, set config.plugins to ["engines", "*"] to load the engines plugin first, and then all other plugins in their normal order after.
|
||||||
|
|
||||||
|
* Access all loaded plugins via the new Rails.plugins array, and by name using Rails.plugins[:plugin_name].
|
||||||
|
|
||||||
|
* Access plugin metadata loaded automatically from about.yml: Rails.plugins[:name].about. Plugin#version is provided directly, for easy access.
|
||||||
|
|
||||||
|
* Module.config is has been removed - use mattr_accessor instead, and initialize your default values via the init.rb mechanism.
|
||||||
|
|
||||||
|
* Public asset helpers have been rewritten; instead of engine_stylesheet, now use stylesheet_link_tag :name, :plugin => "plugin_name"
|
||||||
|
|
||||||
|
* Plugin migrations have been reworked to integrate into the main migration stream. Please run script/generate plugin_migration to create plugin migrations in your main application.
|
||||||
|
|
||||||
|
* The fixture method for loading fixtures against any class has been removed; instead, engines will now provide a mechanism for loading fixtures from all plugins, by mirroring fixtures into a common location.
|
||||||
|
|
||||||
|
* All references to engines have been removed; For example, any rake tasks which applied to engines now apply to all plugins. The default Rails rake tasks for plugins are overridden where necessary.
|
||||||
|
|
||||||
|
* Layouts can now be shared via plugins - inspiration gratefully taken from PluginAWeek's plugin_routing :)
|
||||||
|
|
||||||
|
* Actual routing from plugins is now possible, by including routes.rb in your plugin directory and using the from_plugin method in config/routes.rb (Ticket #182)
|
||||||
|
|
||||||
|
* Controllers are no longer loaded twice if they're not present in the normal app/ directory (Ticket #177)
|
||||||
|
|
||||||
|
* The preferred location for javascripts/stylesheets/etc is now 'assets' rather than 'public'
|
||||||
|
|
||||||
|
* Ensure that plugins started before routing have their controllers appropriately added to config.controller_paths (Ticket #258)
|
||||||
|
|
||||||
|
* Removed Engines.version - it's not longer relevant, now we're loading version information from about.yml files.
|
||||||
|
|
||||||
|
* Added a huge amount of documentation to all new modules.
|
||||||
|
|
||||||
|
* Added new warning message if installation of engines 1.2.x is attempted in a Rails 1.1.x application
|
||||||
|
|
||||||
|
* Added details of the removal of the config method to UPGRADING
|
||||||
|
|
||||||
|
* Removed the plugins:info rake task in favour of adding information to script/about via the Rails::Info module (Ticket #261)
|
||||||
|
|
||||||
|
* Improved handling of testing and documentation tasks for plugins
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.1.4
|
||||||
|
|
||||||
|
* Fixed creation of multipart emails (Ticket #190)
|
||||||
|
|
||||||
|
* Added a temporary fix to the code-mixing issue. In your engine's test/test_helper.rb, please add the following lines:
|
||||||
|
|
||||||
|
# Ensure that the code mixing and view loading from the application is disabled
|
||||||
|
Engines.disable_app_views_loading = true
|
||||||
|
Engines.disable_app_code_mixing = true
|
||||||
|
|
||||||
|
which will prevent code mixing for controllers and helpers, and loading views from the application. One thing to remember is to load any controllers/helpers using 'require_or_load' in your tests, to ensure that the engine behaviour is respected (Ticket #135)
|
||||||
|
|
||||||
|
* Added tasks to easily test engines individually (Ticket #120)
|
||||||
|
|
||||||
|
* Fixture extensions will now fail with an exception if the corresponding class cannot be loaded (Ticket #138)
|
||||||
|
|
||||||
|
* Patch for new routing/controller loading in Rails 1.1.6. The routing code is now replaced with the contents of config.controller_paths, along with controller paths from any started engines (Ticket #196)
|
||||||
|
|
||||||
|
* Rails' Configuration instance is now stored, and available from all engines and plugins.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.1.3
|
||||||
|
|
||||||
|
* Fixed README to show 'models' rather than 'model' class (Ticket #167)
|
||||||
|
* Fixed dependency loading to work with Rails 1.1.4 (Ticket #180)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.1.2
|
||||||
|
|
||||||
|
* Added better fix to version checking (Ticket #130, jdell@gbdev.com).
|
||||||
|
|
||||||
|
* Fixed generated init_engine.rb so that VERSION module doesn't cause probems (Ticket #131, japgolly@gmail.com)
|
||||||
|
|
||||||
|
* Fixed error with Rails 1.0 when trying to ignore the engine_schema_info table (Ticket #132, snowblink@gmail.com)
|
||||||
|
|
||||||
|
* Re-added old style rake tasks (Ticket #133)
|
||||||
|
|
||||||
|
* No longer adding all subdirectories of <engine>/app or <engine>/lib, as this can cause issues when files are grouped in modules (Ticket #149, kasatani@gmail.com)
|
||||||
|
|
||||||
|
* Fixed engine precidence ordering for Rails 1.1 (Ticket #146)
|
||||||
|
|
||||||
|
* Added new Engines.each method to assist in processing the engines in the desired order (Ticket #146)
|
||||||
|
|
||||||
|
* Fixed annoying error message at appears when starting the console in development mode (Ticket #134)
|
||||||
|
|
||||||
|
* Engines is now super-careful about loading the correct version of Rails from vendor (Ticket #154)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.1.1
|
||||||
|
|
||||||
|
* Fixed migration rake task failing when given a specific version (Ticket #115)
|
||||||
|
|
||||||
|
* Added new rake task "test:engines" which will test engines (and other plugins) but ensure that the test database is cloned from development beforehand (Ticket #125)
|
||||||
|
|
||||||
|
* Fixed issue where 'engine_schema_info' table was included in schema dumps (Ticket #87)
|
||||||
|
|
||||||
|
* Fixed multi-part emails (Ticket #121)
|
||||||
|
|
||||||
|
* Added an 'install.rb' file to new engines created by the bundled generator, which installs the engines plugin automatically if it doesn't already exist (Ticket #122)
|
||||||
|
|
||||||
|
* Added a default VERSION module to generated engines (Ticket #123)
|
||||||
|
|
||||||
|
* Refactored copying of engine's public files to a method of an Engine instance. You can now call Engines.get(:engine_name).copy_public_files (Ticket #108)
|
||||||
|
|
||||||
|
* Changed engine generator templates from .rb files to .erb files (Ticket #106)
|
||||||
|
|
||||||
|
* Fixed the test_helper.erb file to use the correct testing extensions and not load any schema - the schema will be cloned automatically via rake test:engines
|
||||||
|
|
||||||
|
* Fixed problem when running with Rails 1.1.1 where version wasn't determined correctly (Ticket #129)
|
||||||
|
|
||||||
|
* Fixed bug preventing engines from loading when both Rails 1.1.0 and 1.1.1 gems are installed and in use.
|
||||||
|
|
||||||
|
* Updated version (d'oh!)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.1.0
|
||||||
|
|
||||||
|
* Improved regexp matching for Rails 1.0 engines with peculiar paths
|
||||||
|
|
||||||
|
* Engine instance objects can be accessed via Engines[:name], an alias for Engines.get(:name) (Ticket #99)
|
||||||
|
|
||||||
|
* init_engine.rb is now processed as the final step in the Engine.start process, so it can access files within the lib directory, which is now in the $LOAD_PATH at that point. (Ticket #99)
|
||||||
|
|
||||||
|
* Clarified MIT license (Ticket #98)
|
||||||
|
|
||||||
|
* Updated Rake tasks to integrate smoothly with Rails 1.1 namespaces
|
||||||
|
|
||||||
|
* Changed the version to "1.1.0 (svn)"
|
||||||
|
|
||||||
|
* Added more information about using the plugin with Edge Rails to the README
|
||||||
|
|
||||||
|
* moved extensions into lib/engines/ directory to enable use of Engines module in extension code.
|
||||||
|
|
||||||
|
* Added conditional require_or_load method which attempts to detect the current Rails version. To use the Edge Rails version of the loading mechanism, add the line:
|
||||||
|
|
||||||
|
* Engines.config :edge, true
|
||||||
|
|
||||||
|
* to your environment.rb file.
|
||||||
|
|
||||||
|
* Merged changes from /branches/edge and /branches/rb_1.0 into /trunk
|
||||||
|
|
||||||
|
* engine_schema_info now respects the prefix/suffixes set for ActiveRecord::Base (Ticket #67)
|
||||||
|
|
||||||
|
* added ActiveRecord::Base.wrapped_table_name(name) method to assist in determining the correct table name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.0.6
|
||||||
|
|
||||||
|
* Added ability to determine version information for engines: rake engine_info
|
||||||
|
|
||||||
|
* Added a custom logger for the Engines module, to stop pollution of the Rails logs.
|
||||||
|
|
||||||
|
* Added some more tests (in particular, see rails_engines/applications/engines_test).
|
||||||
|
|
||||||
|
* Another attempt at solving Ticket #53 - controllers and helpers should now be loadable from modules, and if a full path (including RAILS_ROOT/ENGINES_ROOT) is given, it should be safely stripped from the require filename such that corresponding files can be located in any active engines. In other words, controller/helper overloading should now completely work, even if the controllers/helpers are in modules.
|
||||||
|
|
||||||
|
* Added (finally) patch from Ticket #22 - ActionMailer helpers should now load
|
||||||
|
|
||||||
|
* Removed support for Engines.start :engine, :engine_name => 'whatever'. It was pointless.
|
||||||
|
|
||||||
|
* Fixed engine name referencing; engine_stylesheet/engine_javascript can now happily use shorthand engine names (i.e. :test == :test_engine) (Ticket #45)
|
||||||
|
|
||||||
|
* Fixed minor documentation error ('Engine.start' ==> 'Engines.start') (Ticket #57)
|
||||||
|
|
||||||
|
* Fixed double inclusion of RAILS_ROOT in engine_migrate rake task (Ticket #61)
|
||||||
|
|
||||||
|
* Added ability to force config values even if given as a hash (Ticket #62)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.0.5
|
||||||
|
|
||||||
|
* Fixed bug stopping fixtures from loading with PostgreSQL
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.0.4
|
||||||
|
|
||||||
|
* Another attempt at loading controllers within modules (Ticket #56)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.0.3
|
||||||
|
|
||||||
|
* Fixed serious dependency bug stopping controllers being loaded (Ticket #56)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.0.2
|
||||||
|
|
||||||
|
* Fixed bug with overloading controllers in modules from /app directory
|
||||||
|
|
||||||
|
* Fixed exception thrown when public files couldn't be created; exception is now logged (Ticket #52)
|
||||||
|
|
||||||
|
* Fixed problem with generated test_helper.rb file via File.expand_path (Ticket #50)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= 1.0.1
|
||||||
|
|
||||||
|
* Added engine generator for creation of new engines
|
||||||
|
|
||||||
|
* Fixed 'Engine' typo in README
|
||||||
|
|
||||||
|
* Fixed bug in fixtures extensions
|
||||||
|
|
||||||
|
* Fixed /lib path management bug
|
||||||
|
|
||||||
|
* Added method to determine public directory location from Engine object
|
||||||
|
|
||||||
|
* Fixed bug in the error message in get_engine_dir()
|
||||||
|
|
||||||
|
* Added proper component loading
|
||||||
|
|
||||||
|
* Added preliminary tests for the config() methods module
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
= pre-v170
|
||||||
|
|
||||||
|
* Fixed copyright notices to point to DHH, rather than me.
|
||||||
|
|
||||||
|
* Moved extension require statements into lib/engines.rb, so the will be loaded if another module/file calls require 'engines
|
||||||
|
|
||||||
|
* Added a CHANGELOG file (this file)
|
|
@ -0,0 +1,21 @@
|
||||||
|
Copyright (c) 2008 James Adam
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,83 @@
|
||||||
|
The engines plugin enhances Rails' own plugin framework, making it simple to share controllers, helpers, models, public assets, routes and migrations in plugins.
|
||||||
|
|
||||||
|
For more information, see http://rails-engines.org
|
||||||
|
|
||||||
|
= Using the plugin
|
||||||
|
|
||||||
|
Once you've installed the engines plugin, you'll need to add a single line to the top of config/environment.rb:
|
||||||
|
|
||||||
|
require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
|
||||||
|
|
||||||
|
You should add this line just below the require for Rails' own boot.rb file. This will enabled the enhanced plugin loading mechanism automatically for you (i.e. you don't need to set config.plugin_loader manually).
|
||||||
|
|
||||||
|
With that aside, you're now ready to start using more powerful plugins in your application. Read on to find out more about what the engines plugin enables.
|
||||||
|
|
||||||
|
|
||||||
|
== Better plugins
|
||||||
|
|
||||||
|
In addition to the regular set of plugin-supported files (lib, init.rb, tasks, generators, tests), plugins can carry the following when the engines plugin is also installed.
|
||||||
|
|
||||||
|
|
||||||
|
=== Controllers, Helpers, and Views
|
||||||
|
|
||||||
|
Include these files in an <tt>app</tt> directory just like you would in a normal Rails application. If you need to override a method, view or partial, create the corresponding file in your main <tt>app</tt> directory and it will be used instead.
|
||||||
|
|
||||||
|
* Controllers & Helpers: See Engines::RailsExtensions::Dependencies for more information.
|
||||||
|
* Views: now handled almost entirely by ActionView itself (see Engines::Plugin#add_plugin_view_paths for more information)
|
||||||
|
|
||||||
|
=== Models
|
||||||
|
|
||||||
|
Model code can similarly be placed in an <tt>app/models/</tt> directory. Unfortunately, it's not possible to automatically override methods within a model; if your application needs to change the way a model behaves, consider creating a subclass, or replacing the model entirely within your application's <tt>app/models/</tt> directory. See Engines::RailsExtensions::Dependencies for more information.
|
||||||
|
|
||||||
|
IMPORTANT NOTE: when you load code from within plugins, it is typically not handled well by Rails in terms of unloading and reloading changes. Look here for more information - http://rails-engines.org/development/common-issues-when-overloading-code-from-plugins/
|
||||||
|
|
||||||
|
=== Routes
|
||||||
|
|
||||||
|
Include your route declarations in a <tt>routes.rb</tt> file at the root of your plugins, e.g.:
|
||||||
|
|
||||||
|
connect "/my/url", :controller => "some_controller"
|
||||||
|
my_named_route "do_stuff", :controller => "blah", :action => "stuff"
|
||||||
|
# etc.
|
||||||
|
|
||||||
|
You can then load these files into your application by declaring their inclusion in the application's <tt>config/routes.rb</tt>:
|
||||||
|
|
||||||
|
map.from_plugin :plugin_name
|
||||||
|
|
||||||
|
See Engines::RailsExtensions::Routing for more information.
|
||||||
|
|
||||||
|
=== Migrations
|
||||||
|
|
||||||
|
Migrations record the changes in your database as your application evolves. With engines 1.2, migrations from plugins can also join in this evolution as first-class entities. To add migrations to a plugin, include a <tt>db/migrate/</tt> folder and add migrations there as normal. These migrations can then be integrated into the main flow of database evolution by running the plugin_migration generator:
|
||||||
|
|
||||||
|
script/generate plugin_migration
|
||||||
|
|
||||||
|
This will produce a migration in your application. Running this migration (via <tt>rake db:migrate</tt>, as normal) will migrate the database according to the latest migrations in each plugin. See Engines::RailsExtensions::Migrations for more information.
|
||||||
|
|
||||||
|
|
||||||
|
=== More powerful Rake tasks
|
||||||
|
|
||||||
|
The engines plugin enhances and adds to the suite of default rake tasks for working with plugins. The <tt>doc:plugins</tt> task now includes controllers, helpers and models under <tt>app</tt>, and anything other code found under the plugin's <tt>code_paths</tt> attribute. New testing tasks have been added to run unit, functional and integration tests from plugins, whilst making it easier to load fixtures from plugins. See Engines::Testing for more details about testing, and run
|
||||||
|
|
||||||
|
rake -T
|
||||||
|
|
||||||
|
to see the set of rake tasks available.
|
||||||
|
|
||||||
|
= Testing the engines plugin itself
|
||||||
|
|
||||||
|
Because of the way the engines plugin modifies Rails, the simplest way to consistently test it against multiple versions is by generating a test harness application - a full Rails application that includes tests to verify the engines plugin behaviour in a real, running environment.
|
||||||
|
|
||||||
|
Run the tests like this:
|
||||||
|
|
||||||
|
$ cd engines
|
||||||
|
$ rake test
|
||||||
|
|
||||||
|
This will generate a test_app directory within the engines plugin (using the default 'rails' command), import tests and code into that application and then run the test suite.
|
||||||
|
|
||||||
|
If you wish to test against a specific version of Rails, run the tests with the RAILS environment variable set to the local directory containing your Rails checkout
|
||||||
|
|
||||||
|
$ rake test RAILS=/Users/james/Code/rails_edge_checkout
|
||||||
|
|
||||||
|
Alternatively, you can clone the latest version of Rails ('edge rails') from github like so:
|
||||||
|
|
||||||
|
$ rake test RAILS=edge
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
require 'rake'
|
||||||
|
require 'rake/rdoctask'
|
||||||
|
require 'tmpdir'
|
||||||
|
|
||||||
|
task :default => :doc
|
||||||
|
|
||||||
|
desc 'Generate documentation for the engines plugin.'
|
||||||
|
Rake::RDocTask.new(:doc) do |doc|
|
||||||
|
doc.rdoc_dir = 'doc'
|
||||||
|
doc.title = 'Engines'
|
||||||
|
doc.main = "README"
|
||||||
|
doc.rdoc_files.include("README", "CHANGELOG", "MIT-LICENSE")
|
||||||
|
doc.rdoc_files.include('lib/**/*.rb')
|
||||||
|
doc.options << '--line-numbers' << '--inline-source'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run the engine plugin tests within their test harness'
|
||||||
|
task :cruise do
|
||||||
|
# checkout the project into a temporary directory
|
||||||
|
version = "rails_2.0"
|
||||||
|
test_dir = "#{Dir.tmpdir}/engines_plugin_#{version}_test"
|
||||||
|
puts "Checking out test harness for #{version} into #{test_dir}"
|
||||||
|
`svn co http://svn.rails-engines.org/test/engines/#{version} #{test_dir}`
|
||||||
|
|
||||||
|
# run all the tests in this project
|
||||||
|
Dir.chdir(test_dir)
|
||||||
|
load 'Rakefile'
|
||||||
|
puts "Running all tests in test harness"
|
||||||
|
['db:migrate', 'test', 'test:plugins'].each do |t|
|
||||||
|
Rake::Task[t].invoke
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
task :clean => [:clobber_doc, "test:clean"]
|
||||||
|
|
||||||
|
namespace :test do
|
||||||
|
|
||||||
|
# Yields a block with STDOUT and STDERR silenced. If you *really* want
|
||||||
|
# to output something, the block is yielded with the original output
|
||||||
|
# streams, i.e.
|
||||||
|
#
|
||||||
|
# silence do |o, e|
|
||||||
|
# puts 'hello!' # no output produced
|
||||||
|
# o.puts 'hello!' # output on STDOUT
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# (based on silence_stream in ActiveSupport.)
|
||||||
|
def silence
|
||||||
|
yield(STDOUT, STDERR) if ENV['VERBOSE']
|
||||||
|
streams = [STDOUT, STDERR]
|
||||||
|
actual_stdout = STDOUT.dup
|
||||||
|
actual_stderr = STDERR.dup
|
||||||
|
streams.each do |s|
|
||||||
|
s.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
|
||||||
|
s.sync = true
|
||||||
|
end
|
||||||
|
yield actual_stdout, actual_stderr
|
||||||
|
ensure
|
||||||
|
STDOUT.reopen(actual_stdout)
|
||||||
|
STDERR.reopen(actual_stderr)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_app_dir
|
||||||
|
File.join(File.dirname(__FILE__), 'test_app')
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(cmd)
|
||||||
|
cmd = cmd.join(" && ") if cmd.is_a?(Array)
|
||||||
|
system(cmd) || raise("failed running '#{cmd}'")
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Remove the test application'
|
||||||
|
task :clean do
|
||||||
|
FileUtils.rm_r(test_app_dir) if File.exist?(test_app_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Build the test rails application (use RAILS=[edge,<directory>] to test against specific version)'
|
||||||
|
task :generate_app do
|
||||||
|
silence do |out, err|
|
||||||
|
out.puts "> Creating test application at #{test_app_dir}"
|
||||||
|
|
||||||
|
if ENV['RAILS']
|
||||||
|
vendor_dir = File.join(test_app_dir, 'vendor')
|
||||||
|
FileUtils.mkdir_p vendor_dir
|
||||||
|
|
||||||
|
if ENV['RAILS'] == 'edge'
|
||||||
|
out.puts " Cloning Edge Rails from GitHub"
|
||||||
|
run "cd #{vendor_dir} && git clone --depth 1 git://github.com/rails/rails.git"
|
||||||
|
elsif ENV['RAILS'] =~ /\d\.\d\.\d/
|
||||||
|
if ENV['CURL']
|
||||||
|
out.puts " Cloning Rails Tag #{ENV['RAILS']} from GitHub using curl and tar"
|
||||||
|
run ["cd #{vendor_dir}",
|
||||||
|
"mkdir rails",
|
||||||
|
"cd rails",
|
||||||
|
"curl -s -L http://github.com/rails/rails/tarball/v2.1.0 | tar xzv --strip-components 1"]
|
||||||
|
else
|
||||||
|
out.puts " Cloning Rails Tag #{ENV['RAILS']} from GitHub (can be slow - set CURL=true to use curl)"
|
||||||
|
run ["cd #{vendor_dir}",
|
||||||
|
"git clone git://github.com/rails/rails.git",
|
||||||
|
"cd rails",
|
||||||
|
"git pull",
|
||||||
|
"git checkout v#{ENV['RAILS']}"]
|
||||||
|
end
|
||||||
|
elsif File.exist?(ENV['RAILS'])
|
||||||
|
out.puts " Linking rails from #{ENV['RAILS']}"
|
||||||
|
run "cd #{vendor_dir} && ln -s #{ENV['RAILS']} rails"
|
||||||
|
else
|
||||||
|
raise "Couldn't build test application from '#{ENV['RAILS']}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
out.puts " generating rails default directory structure"
|
||||||
|
run "ruby #{File.join(vendor_dir, 'rails', 'railties', 'bin', 'rails')} #{test_app_dir}"
|
||||||
|
else
|
||||||
|
version = `rails --version`.chomp.split.last
|
||||||
|
out.puts " building rails using the 'rails' command (rails version: #{version})"
|
||||||
|
run "rails #{test_app_dir}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# get the database config and schema in place
|
||||||
|
out.puts " writing database.yml"
|
||||||
|
require 'yaml'
|
||||||
|
File.open(File.join(test_app_dir, 'config', 'database.yml'), 'w') do |f|
|
||||||
|
f.write(%w(development test).inject({}) do |h, env|
|
||||||
|
h[env] = {"adapter" => "sqlite3", "database" => "engines_#{env}.sqlite3"} ; h
|
||||||
|
end.to_yaml)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# We can't link the plugin, as it needs to be present for script/generate to find
|
||||||
|
# the plugin generator.
|
||||||
|
# TODO: find and +1/create issue for loading generators from symlinked plugins
|
||||||
|
desc 'Mirror the engines plugin into the test application'
|
||||||
|
task :copy_engines_plugin do
|
||||||
|
puts "> Copying engines plugin into test application"
|
||||||
|
engines_plugin = File.join(test_app_dir, "vendor", "plugins", "engines")
|
||||||
|
FileUtils.rm_r(engines_plugin) if File.exist?(engines_plugin)
|
||||||
|
FileUtils.mkdir_p(engines_plugin)
|
||||||
|
FileList["*"].exclude("test_app").each do |file|
|
||||||
|
FileUtils.cp_r(file, engines_plugin)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_line(line, options)
|
||||||
|
line = line + "\n"
|
||||||
|
target_file = File.join(test_app_dir, options[:into])
|
||||||
|
lines = File.readlines(target_file)
|
||||||
|
return if lines.include?(line)
|
||||||
|
|
||||||
|
if options[:after]
|
||||||
|
if options[:after].is_a?(String)
|
||||||
|
after_line = options[:after] + "\n"
|
||||||
|
else
|
||||||
|
after_line = lines.find { |l| l =~ options[:after] }
|
||||||
|
raise "couldn't find a line matching #{options[:after].inspect} in #{target_file}" unless after_line
|
||||||
|
end
|
||||||
|
index = lines.index(after_line)
|
||||||
|
raise "couldn't find line '#{after_line}' in #{target_file}" unless index
|
||||||
|
lines.insert(index + 1, line)
|
||||||
|
else
|
||||||
|
lines << line
|
||||||
|
end
|
||||||
|
File.open(target_file, 'w') { |f| f.write lines.join }
|
||||||
|
end
|
||||||
|
|
||||||
|
def mirror_test_files(src, dest=nil)
|
||||||
|
destination_dir = File.join(*([test_app_dir, dest].compact))
|
||||||
|
FileUtils.cp_r(File.join(File.dirname(__FILE__), 'test', src), destination_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Update the plugin and tests files in the test application from the plugin'
|
||||||
|
task :mirror_engine_files => [:test_app, :copy_engines_plugin] do
|
||||||
|
puts "> Modifying default config files to load engines plugin"
|
||||||
|
insert_line("require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')",
|
||||||
|
:into => 'config/environment.rb',
|
||||||
|
:after => "require File.join(File.dirname(__FILE__), 'boot')")
|
||||||
|
|
||||||
|
insert_line('map.from_plugin :test_routing', :into => 'config/routes.rb',
|
||||||
|
:after => /\AActionController::Routing::Routes/)
|
||||||
|
|
||||||
|
insert_line("require 'engines_test_helper'", :into => 'test/test_helper.rb')
|
||||||
|
|
||||||
|
puts "> Mirroring test application files into #{test_app_dir}"
|
||||||
|
mirror_test_files('app')
|
||||||
|
mirror_test_files('lib')
|
||||||
|
mirror_test_files('plugins', 'vendor')
|
||||||
|
mirror_test_files('unit', 'test')
|
||||||
|
mirror_test_files('functional', 'test')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Prepare the engines test environment'
|
||||||
|
task :test_app do
|
||||||
|
version_tag = File.join(test_app_dir, 'RAILS_VERSION')
|
||||||
|
existing_version = File.read(version_tag).chomp rescue 'unknown'
|
||||||
|
if existing_version == ENV['RAILS']
|
||||||
|
puts "> Reusing existing test application (#{ENV['RAILS']})"
|
||||||
|
else
|
||||||
|
puts "> Recreating test application"
|
||||||
|
Rake::Task["test:clean"].invoke
|
||||||
|
Rake::Task["test:generate_app"].invoke
|
||||||
|
|
||||||
|
File.open(version_tag, "w") { |f| f.write ENV['RAILS'] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
task :test => "test:mirror_engine_files" do
|
||||||
|
puts "> Loading the test application environment and running tests"
|
||||||
|
# We use exec here to replace the current running rake process
|
||||||
|
exec("cd #{test_app_dir} && rake")
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
author: James Adam
|
||||||
|
email: james.adam@gmail.com
|
||||||
|
homepage: http://www.rails-engines.org
|
||||||
|
summary: Enhances the plugin mechanism to perform more flexible sharing
|
||||||
|
description: The Rails Engines plugin allows the sharing of almost any type of code or asset that you could use in a Rails application, including controllers, models, stylesheets, and views.
|
||||||
|
license: MIT
|
||||||
|
version: 2.1.0
|
|
@ -0,0 +1,18 @@
|
||||||
|
begin
|
||||||
|
require 'rails/version'
|
||||||
|
unless Rails::VERSION::MAJOR >= 2 ||
|
||||||
|
(Rails::VERSION::MAJOR >= 1 && Rails::VERSION::MINOR >= 99)
|
||||||
|
raise "This version of the engines plugin requires Rails 2.0 or later!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.join(File.dirname(__FILE__), 'lib/engines')
|
||||||
|
|
||||||
|
# initialize Rails::Configuration with our own default values to spare users
|
||||||
|
# some hassle with the installation and keep the environment cleaner
|
||||||
|
|
||||||
|
{ :default_plugin_locators => [Engines::Plugin::FileSystemLocator],
|
||||||
|
:default_plugin_loader => Engines::Plugin::Loader,
|
||||||
|
:default_plugins => [:engines, :all] }.each do |name, default|
|
||||||
|
Rails::Configuration.send(:define_method, name) { default }
|
||||||
|
end
|
|
@ -0,0 +1,45 @@
|
||||||
|
Description:
|
||||||
|
The plugin migration generator assists in working with schema additions
|
||||||
|
required by plugins. Instead of running migrations from plugins directly,
|
||||||
|
the generator creates a regular Rails migration which will be responsible
|
||||||
|
for migrating the plugins from their current version to the latest version
|
||||||
|
installed.
|
||||||
|
|
||||||
|
This is important because the set of application migrations remains an
|
||||||
|
accurate record of the state of the database, even as plugins are installed
|
||||||
|
and removed during the development process.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
./script/generate plugin_migration [<plugin_name> <another_plugin_name> ...]
|
||||||
|
|
||||||
|
This will generate:
|
||||||
|
|
||||||
|
RAILS_ROOT
|
||||||
|
|- db
|
||||||
|
|-migrate
|
||||||
|
|- xxx_plugin_migrations.rb
|
||||||
|
|
||||||
|
which contains the migrations for the given plugin(s).
|
||||||
|
|
||||||
|
|
||||||
|
Advanced Usage:
|
||||||
|
|
||||||
|
There may be situations where you need *complete* control over the migrations
|
||||||
|
of plugins in your application, migrating a certainly plugin down to X, and
|
||||||
|
another plugin up to Y, where neither X or Y are the latest migrations for those
|
||||||
|
plugins.
|
||||||
|
|
||||||
|
For those unfortunate few, I have two pieces of advice:
|
||||||
|
|
||||||
|
1. Why? This is a code smell [http://c2.com/xp/CodeSmell.html].
|
||||||
|
|
||||||
|
2. Well, OK. Don't panic. You can completely control plugin migrations by
|
||||||
|
creating your own migrations. To manually migrate a plugin to a specific
|
||||||
|
version, simply use
|
||||||
|
|
||||||
|
Engines.plugins[:your_plugin_name].migrate(version)
|
||||||
|
|
||||||
|
where version is the integer of the migration this plugin should end
|
||||||
|
up at.
|
||||||
|
|
||||||
|
With great power comes great responsibility. Use this wisely.
|
79
vendor/plugins/engines/generators/plugin_migration/plugin_migration_generator.rb
vendored
Normal file
79
vendor/plugins/engines/generators/plugin_migration/plugin_migration_generator.rb
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
# Generates a migration which migrates all plugins to their latest versions
|
||||||
|
# within the database.
|
||||||
|
class PluginMigrationGenerator < Rails::Generator::Base
|
||||||
|
|
||||||
|
def initialize(runtime_args, runtime_options={})
|
||||||
|
super
|
||||||
|
@options = {:assigns => {}}
|
||||||
|
|
||||||
|
ensure_plugin_schema_table_exists
|
||||||
|
get_plugins_to_migrate(runtime_args)
|
||||||
|
|
||||||
|
if @plugins_to_migrate.empty?
|
||||||
|
puts "All plugins are migrated to their latest versions"
|
||||||
|
exit(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
@options[:migration_file_name] = build_migration_name
|
||||||
|
@options[:assigns][:class_name] = build_migration_name.classify
|
||||||
|
end
|
||||||
|
|
||||||
|
def manifest
|
||||||
|
record do |m|
|
||||||
|
m.migration_template 'plugin_migration.erb', 'db/migrate', @options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# Create the plugin schema table if it doesn't already exist. See
|
||||||
|
# Engines::RailsExtensions::Migrations#initialize_schema_migrations_table_with_engine_additions
|
||||||
|
def ensure_plugin_schema_table_exists
|
||||||
|
ActiveRecord::Base.connection.initialize_schema_migrations_table
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine all the plugins which have migrations that aren't present
|
||||||
|
# according to the plugin schema information from the database.
|
||||||
|
def get_plugins_to_migrate(plugin_names)
|
||||||
|
|
||||||
|
# First, grab all the plugins which exist and have migrations
|
||||||
|
@plugins_to_migrate = if plugin_names.empty?
|
||||||
|
Engines.plugins
|
||||||
|
else
|
||||||
|
plugin_names.map do |name|
|
||||||
|
Engines.plugins[name] ? Engines.plugins[name] : raise("Cannot find the plugin '#{name}'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@plugins_to_migrate.reject! { |p| p.latest_migration.nil? }
|
||||||
|
|
||||||
|
# Then find the current versions from the database
|
||||||
|
@current_versions = {}
|
||||||
|
@plugins_to_migrate.each do |plugin|
|
||||||
|
@current_versions[plugin.name] = Engines::Plugin::Migrator.current_version(plugin)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Then find the latest versions from their migration directories
|
||||||
|
@new_versions = {}
|
||||||
|
@plugins_to_migrate.each do |plugin|
|
||||||
|
@new_versions[plugin.name] = plugin.latest_migration
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove any plugins that don't need migration
|
||||||
|
@plugins_to_migrate.map { |p| p.name }.each do |name|
|
||||||
|
@plugins_to_migrate.delete(Engines.plugins[name]) if @current_versions[name] == @new_versions[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
@options[:assigns][:plugins] = @plugins_to_migrate
|
||||||
|
@options[:assigns][:new_versions] = @new_versions
|
||||||
|
@options[:assigns][:current_versions] = @current_versions
|
||||||
|
end
|
||||||
|
|
||||||
|
# Construct a unique migration name based on the plugins involved and the
|
||||||
|
# versions they should reach after this migration is run.
|
||||||
|
def build_migration_name
|
||||||
|
@plugins_to_migrate.map do |plugin|
|
||||||
|
"#{plugin.name}_to_version_#{@new_versions[plugin.name]}"
|
||||||
|
end.join("_and_")
|
||||||
|
end
|
||||||
|
end
|
13
vendor/plugins/engines/generators/plugin_migration/templates/plugin_migration.erb
vendored
Normal file
13
vendor/plugins/engines/generators/plugin_migration/templates/plugin_migration.erb
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
class <%= class_name %> < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
<%- plugins.each do |plugin| -%>
|
||||||
|
Engines.plugins["<%= plugin.name %>"].migrate(<%= new_versions[plugin.name] %>)
|
||||||
|
<%- end -%>
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
<%- plugins.each do |plugin| -%>
|
||||||
|
Engines.plugins["<%= plugin.name %>"].migrate(<%= current_versions[plugin.name] %>)
|
||||||
|
<%- end -%>
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Only call Engines.init once, in the after_initialize block so that Rails
|
||||||
|
# plugin reloading works when turned on
|
||||||
|
config.after_initialize do
|
||||||
|
Engines.init if defined? :Engines
|
||||||
|
end
|
|
@ -0,0 +1,174 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
unless source_files.empty?
|
||||||
|
base_target_dir = File.join(destination, File.dirname(source_files.first))
|
||||||
|
FileUtils.mkdir_p(base_target_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
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.view_paths.insert(1, 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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,145 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
module ::Dependencies #:nodoc:
|
||||||
|
include Engines::RailsExtensions::Dependencies
|
||||||
|
end
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,178 @@
|
||||||
|
# This code lets us redefine existing Rake tasks, which is extremely
|
||||||
|
# handy for modifying existing Rails rake tasks.
|
||||||
|
# Credit for the original snippet of code goes to Jeremy Kemper
|
||||||
|
# http://pastie.caboo.se/9620
|
||||||
|
unless Rake::TaskManager.methods.include?('redefine_task')
|
||||||
|
module Rake
|
||||||
|
module TaskManager
|
||||||
|
def redefine_task(task_class, args, &block)
|
||||||
|
task_name, arg_names, deps = resolve_args([args])
|
||||||
|
task_name = task_class.scope_name(@scope, task_name)
|
||||||
|
deps = [deps] unless deps.respond_to?(:to_ary)
|
||||||
|
deps = deps.collect {|d| d.to_s }
|
||||||
|
task = @tasks[task_name.to_s] = task_class.new(task_name, self)
|
||||||
|
task.application = self
|
||||||
|
task.add_description(@last_description)
|
||||||
|
@last_description = nil
|
||||||
|
task.enhance(deps, &block)
|
||||||
|
task
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
class Task
|
||||||
|
class << self
|
||||||
|
def redefine_task(args, &block)
|
||||||
|
Rake.application.redefine_task(self, [args], &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :db do
|
||||||
|
namespace :migrate do
|
||||||
|
desc 'Migrate database and plugins to current status.'
|
||||||
|
task :all => [ 'db:migrate', 'db:migrate:plugins' ]
|
||||||
|
|
||||||
|
desc 'Migrate plugins to current status.'
|
||||||
|
task :plugins => :environment do
|
||||||
|
Engines.plugins.each do |plugin|
|
||||||
|
next unless File.exists? plugin.migration_directory
|
||||||
|
puts "Migrating plugin #{plugin.name} ..."
|
||||||
|
plugin.migrate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Migrate a specified plugin.'
|
||||||
|
task({:plugin => :environment}, :name, :version) do |task, args|
|
||||||
|
name = args[:name] || ENV['NAME']
|
||||||
|
if plugin = Engines.plugins[name]
|
||||||
|
version = args[:version] || ENV['VERSION']
|
||||||
|
puts "Migrating #{plugin.name} to " + (version ? "version #{version}" : 'latest version') + " ..."
|
||||||
|
plugin.migrate(version ? version.to_i : nil)
|
||||||
|
else
|
||||||
|
puts "Plugin #{name} does not exist."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
namespace :db do
|
||||||
|
namespace :fixtures do
|
||||||
|
namespace :plugins do
|
||||||
|
|
||||||
|
desc "Load plugin fixtures into the current environment's database."
|
||||||
|
task :load => :environment do
|
||||||
|
require 'active_record/fixtures'
|
||||||
|
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
|
||||||
|
Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', ENV['PLUGIN'] || '**',
|
||||||
|
'test', 'fixtures', '*.yml')).each do |fixture_file|
|
||||||
|
Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# this is just a modification of the original task in railties/lib/tasks/documentation.rake,
|
||||||
|
# because the default task doesn't support subdirectories like <plugin>/app or
|
||||||
|
# <plugin>/component. These tasks now include every file under a plugin's code paths (see
|
||||||
|
# Plugin#code_paths).
|
||||||
|
namespace :doc do
|
||||||
|
|
||||||
|
plugins = FileList['vendor/plugins/**'].collect { |plugin| File.basename(plugin) }
|
||||||
|
|
||||||
|
namespace :plugins do
|
||||||
|
|
||||||
|
# Define doc tasks for each plugin
|
||||||
|
plugins.each do |plugin|
|
||||||
|
desc "Create plugin documentation for '#{plugin}'"
|
||||||
|
Rake::Task.redefine_task(plugin => :environment) do
|
||||||
|
plugin_base = RAILS_ROOT + "/vendor/plugins/#{plugin}"
|
||||||
|
options = []
|
||||||
|
files = Rake::FileList.new
|
||||||
|
options << "-o doc/plugins/#{plugin}"
|
||||||
|
options << "--title '#{plugin.titlecase} Plugin Documentation'"
|
||||||
|
options << '--line-numbers' << '--inline-source'
|
||||||
|
options << '-T html'
|
||||||
|
|
||||||
|
# Include every file in the plugin's code_paths (see Plugin#code_paths)
|
||||||
|
if Engines.plugins[plugin]
|
||||||
|
files.include("#{plugin_base}/{#{Engines.plugins[plugin].code_paths.join(",")}}/**/*.rb")
|
||||||
|
end
|
||||||
|
if File.exists?("#{plugin_base}/README")
|
||||||
|
files.include("#{plugin_base}/README")
|
||||||
|
options << "--main '#{plugin_base}/README'"
|
||||||
|
end
|
||||||
|
files.include("#{plugin_base}/CHANGELOG") if File.exists?("#{plugin_base}/CHANGELOG")
|
||||||
|
|
||||||
|
if files.empty?
|
||||||
|
puts "No source files found in #{plugin_base}. No documentation will be generated."
|
||||||
|
else
|
||||||
|
options << files.to_s
|
||||||
|
sh %(rdoc #{options * ' '})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace :test do
|
||||||
|
task :warn_about_multiple_plugin_testing_with_engines do
|
||||||
|
puts %{-~============== A Moste Polite Warninge ===========================~-
|
||||||
|
|
||||||
|
You may experience issues testing multiple plugins at once when using
|
||||||
|
the code-mixing features that the engines plugin provides. If you do
|
||||||
|
experience any problems, please test plugins individually, i.e.
|
||||||
|
|
||||||
|
$ rake test:plugins PLUGIN=my_plugin
|
||||||
|
|
||||||
|
or use the per-type plugin test tasks:
|
||||||
|
|
||||||
|
$ rake test:plugins:units
|
||||||
|
$ rake test:plugins:functionals
|
||||||
|
$ rake test:plugins:integration
|
||||||
|
$ rake test:plugins:all
|
||||||
|
|
||||||
|
Report any issues on http://dev.rails-engines.org. Thanks!
|
||||||
|
|
||||||
|
-~===============( ... as you were ... )============================~-}
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :plugins do
|
||||||
|
|
||||||
|
desc "Run the plugin tests in vendor/plugins/**/test (or specify with PLUGIN=name)"
|
||||||
|
task :all => [:warn_about_multiple_plugin_testing_with_engines,
|
||||||
|
:units, :functionals, :integration]
|
||||||
|
|
||||||
|
desc "Run all plugin unit tests"
|
||||||
|
Rake::TestTask.new(:units => :setup_plugin_fixtures) do |t|
|
||||||
|
t.pattern = "vendor/plugins/#{ENV['PLUGIN'] || "**"}/test/unit/**/*_test.rb"
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Run all plugin functional tests"
|
||||||
|
Rake::TestTask.new(:functionals => :setup_plugin_fixtures) do |t|
|
||||||
|
t.pattern = "vendor/plugins/#{ENV['PLUGIN'] || "**"}/test/functional/**/*_test.rb"
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Integration test engines"
|
||||||
|
Rake::TestTask.new(:integration => :setup_plugin_fixtures) do |t|
|
||||||
|
t.pattern = "vendor/plugins/#{ENV['PLUGIN'] || "**"}/test/integration/**/*_test.rb"
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Mirrors plugin fixtures into a single location to help plugin tests"
|
||||||
|
task :setup_plugin_fixtures => :environment do
|
||||||
|
Engines::Testing.setup_plugin_fixtures
|
||||||
|
end
|
||||||
|
|
||||||
|
# Patch the default plugin testing task to have setup_plugin_fixtures as a prerequisite
|
||||||
|
Rake::Task["test:plugins"].prerequisites << "test:plugins:setup_plugin_fixtures"
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue