pager-engines 2.0.20080513

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/CHANGELOG +267 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +83 -0
  4. data/Rakefile +188 -0
  5. data/about.yml +7 -0
  6. data/boot.rb +19 -0
  7. data/generators/plugin_migration/USAGE +45 -0
  8. data/generators/plugin_migration/plugin_migration_generator.rb +79 -0
  9. data/generators/plugin_migration/templates/plugin_migration.erb +13 -0
  10. data/init.rb +5 -0
  11. data/lib/engines.rb +174 -0
  12. data/lib/engines/assets.rb +38 -0
  13. data/lib/engines/plugin.rb +142 -0
  14. data/lib/engines/plugin/list.rb +30 -0
  15. data/lib/engines/plugin/loader.rb +18 -0
  16. data/lib/engines/plugin/locator.rb +37 -0
  17. data/lib/engines/plugin/migrator.rb +73 -0
  18. data/lib/engines/rails_extensions/action_mailer.rb +85 -0
  19. data/lib/engines/rails_extensions/asset_helpers.rb +119 -0
  20. data/lib/engines/rails_extensions/dependencies.rb +145 -0
  21. data/lib/engines/rails_extensions/migrations.rb +161 -0
  22. data/lib/engines/rails_extensions/rails.rb +11 -0
  23. data/lib/engines/rails_extensions/routing.rb +84 -0
  24. data/lib/engines/testing.rb +87 -0
  25. data/lib/engines_initializer.rb +5 -0
  26. data/rails/init.rb +5 -0
  27. data/tasks/engines.rake +179 -0
  28. data/test/app/controllers/app_and_plugin_controller.rb +5 -0
  29. data/test/app/controllers/application.rb +18 -0
  30. data/test/app/controllers/namespace/app_and_plugin_controller.rb +5 -0
  31. data/test/app/helpers/mail_helper.rb +5 -0
  32. data/test/app/models/app_and_plugin_model.rb +3 -0
  33. data/test/app/models/notify_mail.rb +26 -0
  34. data/test/app/things/thing.rb +3 -0
  35. data/test/app/views/app_and_plugin/a_view.html.erb +1 -0
  36. data/test/app/views/namespace/app_and_plugin/a_view.html.erb +1 -0
  37. data/test/app/views/notify_mail/implicit_multipart.text.html.erb +1 -0
  38. data/test/app/views/notify_mail/implicit_multipart.text.plain.erb +1 -0
  39. data/test/app/views/notify_mail/multipart_html.html.erb +1 -0
  40. data/test/app/views/notify_mail/multipart_plain.html.erb +1 -0
  41. data/test/app/views/notify_mail/signup.text.plain.erb +5 -0
  42. data/test/app/views/plugin_mail/mail_from_plugin_with_application_template.text.plain.erb +1 -0
  43. data/test/app/views/plugin_mail/multipart_from_plugin_with_application_template_plain.html.erb +1 -0
  44. data/test/functional/controller_loading_test.rb +51 -0
  45. data/test/functional/routes_test.rb +33 -0
  46. data/test/functional/view_helpers_test.rb +32 -0
  47. data/test/functional/view_loading_test.rb +60 -0
  48. data/test/lib/app_and_plugin_lib_model.rb +3 -0
  49. data/test/lib/engines_test_helper.rb +36 -0
  50. data/test/plugins/alpha_plugin/app/controllers/alpha_plugin_controller.rb +8 -0
  51. data/test/plugins/alpha_plugin/app/controllers/app_and_plugin_controller.rb +5 -0
  52. data/test/plugins/alpha_plugin/app/controllers/namespace/alpha_plugin_controller.rb +5 -0
  53. data/test/plugins/alpha_plugin/app/controllers/namespace/app_and_plugin_controller.rb +5 -0
  54. data/test/plugins/alpha_plugin/app/controllers/namespace/shared_plugin_controller.rb +5 -0
  55. data/test/plugins/alpha_plugin/app/controllers/shared_plugin_controller.rb +5 -0
  56. data/test/plugins/alpha_plugin/app/models/alpha_plugin_model.rb +3 -0
  57. data/test/plugins/alpha_plugin/app/models/app_and_plugin_model.rb +7 -0
  58. data/test/plugins/alpha_plugin/app/models/shared_plugin_model.rb +3 -0
  59. data/test/plugins/alpha_plugin/app/views/alpha_plugin/a_view.html.erb +1 -0
  60. data/test/plugins/alpha_plugin/app/views/app_and_plugin/a_view.html.erb +1 -0
  61. data/test/plugins/alpha_plugin/app/views/layouts/plugin_layout.erb +1 -0
  62. data/test/plugins/alpha_plugin/app/views/namespace/alpha_plugin/a_view.html.erb +1 -0
  63. data/test/plugins/alpha_plugin/app/views/namespace/app_and_plugin/a_view.html.erb +1 -0
  64. data/test/plugins/alpha_plugin/app/views/namespace/shared_plugin/a_view.html.erb +1 -0
  65. data/test/plugins/alpha_plugin/app/views/shared_plugin/a_view.html.erb +1 -0
  66. data/test/plugins/alpha_plugin/lib/alpha_plugin_lib_model.rb +3 -0
  67. data/test/plugins/alpha_plugin/lib/app_and_plugin_lib_model.rb +7 -0
  68. data/test/plugins/beta_plugin/app/controllers/app_and_plugin_controller.rb +5 -0
  69. data/test/plugins/beta_plugin/app/controllers/namespace/shared_plugin_controller.rb +5 -0
  70. data/test/plugins/beta_plugin/app/controllers/shared_plugin_controller.rb +5 -0
  71. data/test/plugins/beta_plugin/app/models/shared_plugin_model.rb +3 -0
  72. data/test/plugins/beta_plugin/app/views/namespace/shared_plugin/a_view.html.erb +1 -0
  73. data/test/plugins/beta_plugin/app/views/shared_plugin/a_view.html.erb +1 -0
  74. data/test/plugins/beta_plugin/init.rb +1 -0
  75. data/test/plugins/not_a_plugin/public/should_not_be_copied.txt +0 -0
  76. data/test/plugins/test_assets/app/controllers/assets_controller.rb +2 -0
  77. data/test/plugins/test_assets/app/views/assets/index.html.erb +3 -0
  78. data/test/plugins/test_assets/app/views/layouts/assets.html.erb +3 -0
  79. data/test/plugins/test_assets/init.rb +0 -0
  80. data/test/plugins/test_assets/public/file.txt +0 -0
  81. data/test/plugins/test_assets/public/subfolder/file_in_subfolder.txt +0 -0
  82. data/test/plugins/test_assets_with_assets_directory/assets/file.txt +0 -0
  83. data/test/plugins/test_assets_with_assets_directory/assets/subfolder/file_in_subfolder.txt +0 -0
  84. data/test/plugins/test_assets_with_assets_directory/init.rb +0 -0
  85. data/test/plugins/test_assets_with_no_subdirectory/assets/file.txt +0 -0
  86. data/test/plugins/test_assets_with_no_subdirectory/init.rb +0 -0
  87. data/test/plugins/test_code_mixing/app/things/thing.rb +3 -0
  88. data/test/plugins/test_code_mixing/init.rb +1 -0
  89. data/test/plugins/test_load_path/init.rb +0 -0
  90. data/test/plugins/test_migration/db/migrate/001_create_tests.rb +11 -0
  91. data/test/plugins/test_migration/db/migrate/002_create_others.rb +11 -0
  92. data/test/plugins/test_migration/init.rb +0 -0
  93. data/test/plugins/test_plugin_mailing/app/models/plugin_mail.rb +26 -0
  94. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/mail_from_plugin.text.plain.erb +1 -0
  95. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_html.html.erb +1 -0
  96. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_plain.html.erb +1 -0
  97. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_with_application_template_html.html.erb +1 -0
  98. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_with_application_template_plain.html.erb +1 -0
  99. data/test/plugins/test_plugin_mailing/init.rb +0 -0
  100. data/test/plugins/test_routing/app/controllers/namespace/test_routing_controller.rb +5 -0
  101. data/test/plugins/test_routing/app/controllers/test_routing_controller.rb +9 -0
  102. data/test/plugins/test_routing/init.rb +0 -0
  103. data/test/plugins/test_routing/routes.rb +2 -0
  104. data/test/plugins/test_testing/init.rb +0 -0
  105. data/test/plugins/test_testing/test/fixtures/testing_fixtures.yml +0 -0
  106. data/test/unit/action_mailer_test.rb +54 -0
  107. data/test/unit/arbitrary_code_mixing_test.rb +41 -0
  108. data/test/unit/assets_test.rb +48 -0
  109. data/test/unit/backwards_compat_test.rb +8 -0
  110. data/test/unit/load_path_test.rb +58 -0
  111. data/test/unit/migration_test.rb +43 -0
  112. data/test/unit/model_and_lib_test.rb +37 -0
  113. data/test/unit/plugins_test.rb +11 -0
  114. data/test/unit/testing_test.rb +18 -0
  115. metadata +255 -0
@@ -0,0 +1,30 @@
1
+ # The PluginList class is an array, enhanced to allow access to loaded plugins
2
+ # by name, and iteration over loaded plugins in order of priority. This array is used
3
+ # by Engines::RailsExtensions::RailsInitializer to create the Engines.plugins array.
4
+ #
5
+ # Each loaded plugin has a corresponding Plugin instance within this array, and
6
+ # the order the plugins were loaded is reflected in the entries in this array.
7
+ #
8
+ # For more information, see the Rails module.
9
+ module Engines
10
+ class Plugin
11
+ class List < Array
12
+ # Finds plugins with the set with the given name (accepts Strings or Symbols), or
13
+ # index. So, Engines.plugins[0] returns the first-loaded Plugin, and Engines.plugins[:engines]
14
+ # returns the Plugin instance for the engines plugin itself.
15
+ def [](name_or_index)
16
+ if name_or_index.is_a?(Fixnum)
17
+ super
18
+ else
19
+ self.find { |plugin| plugin.name.to_s == name_or_index.to_s }
20
+ end
21
+ end
22
+
23
+ # Go through each plugin, highest priority first (last loaded first). Effectively,
24
+ # this is like <tt>Engines.plugins.reverse</tt>
25
+ def by_precedence
26
+ reverse
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ module Engines
2
+ class Plugin
3
+ class Loader < Rails::Plugin::Loader
4
+ protected
5
+ def register_plugin_as_loaded(plugin)
6
+ super plugin
7
+ Engines.plugins << plugin
8
+ register_to_routing(plugin)
9
+ end
10
+
11
+ # Registers the plugin's controller_paths for the routing system.
12
+ def register_to_routing(plugin)
13
+ initializer.configuration.controller_paths += plugin.select_existing_paths(:controller_paths)
14
+ initializer.configuration.controller_paths.uniq!
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ module Engines
2
+ class Plugin
3
+ class FileSystemLocator < Rails::Plugin::FileSystemLocator
4
+ def create_plugin(path)
5
+ plugin = Engines::Plugin.new(path)
6
+ plugin.valid? ? plugin : nil
7
+ end
8
+ end
9
+
10
+ # FIXME Rails GemLocator requires refactoring
11
+ class GemLocator < Rails::Plugin::GemLocator
12
+ def plugins
13
+ # FIXME think of it more
14
+ specs = initializer.configuration.gems.map(&:specification) +
15
+ Gem.loaded_specs.values
16
+
17
+ # FIXME it should be in rails too
18
+ # Remove not installed gems specs to allow run rake gems:install
19
+ specs.compact!
20
+
21
+ # FIXME think of it more
22
+ specs.reject! { |spec| !spec.loaded_from ||
23
+ !File.exist?(File.join(spec.full_gem_path, "rails", "init.rb"))}
24
+
25
+ require "rubygems/dependency_list"
26
+
27
+ deps = Gem::DependencyList.new
28
+ deps.add(*specs) unless specs.empty?
29
+
30
+ deps.dependency_order.collect do |spec|
31
+ Engines::GemPlugin.new(spec)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,73 @@
1
+ # The Plugin::Migrator class contains the logic to run migrations from
2
+ # within plugin directories. The directory in which a plugin's migrations
3
+ # should be is determined by the Plugin#migration_directory method.
4
+ #
5
+ # To migrate a plugin, you can simple call the migrate method (Plugin#migrate)
6
+ # with the version number that plugin should be at. The plugin's migrations
7
+ # will then be used to migrate up (or down) to the given version.
8
+ #
9
+ # For more information, see Engines::RailsExtensions::Migrations
10
+ class Engines::Plugin::Migrator < ActiveRecord::Migrator
11
+
12
+ # We need to be able to set the 'current' engine being migrated.
13
+ cattr_accessor :current_plugin
14
+
15
+ # Runs the migrations from a plugin, up (or down) to the version given
16
+ def self.migrate_plugin(plugin, version)
17
+ self.current_plugin = plugin
18
+ # There seems to be a bug in Rails' own migrations, where migrating
19
+ # to the existing version causes all migrations to be run where that
20
+ # migration number doesn't exist (i.e. zero). We could fix this by
21
+ # removing the line if the version hits zero...?
22
+ return if current_version(plugin) == version
23
+ migrate(plugin.migration_directory, version)
24
+ end
25
+
26
+ # Returns the name of the table used to store schema information about
27
+ # installed plugins.
28
+ #
29
+ # See Engines.schema_info_table for more details.
30
+ def self.schema_info_table_name
31
+ proper_table_name Engines.schema_info_table
32
+ end
33
+
34
+ # Returns the current version of the given plugin
35
+ def self.current_version(plugin=current_plugin)
36
+ result = ActiveRecord::Base.connection.select_one(<<-ESQL
37
+ SELECT version FROM #{schema_info_table_name}
38
+ WHERE plugin_name = '#{plugin.name}'
39
+ ESQL
40
+ )
41
+ if result
42
+ result["version"].to_i
43
+ else
44
+ # There probably isn't an entry for this engine in the migration info table.
45
+ # We need to create that entry, and set the version to 0
46
+ ActiveRecord::Base.connection.execute(<<-ESQL
47
+ INSERT INTO #{schema_info_table_name} (version, plugin_name)
48
+ VALUES (0,'#{plugin.name}')
49
+ ESQL
50
+ )
51
+ 0
52
+ end
53
+ end
54
+
55
+ def migrated(plugin=current_plugin)
56
+ ActiveRecord::Base.connection.select_values(<<-ESQL
57
+ SELECT version FROM #{self.class.schema_info_table_name}
58
+ WHERE plugin_name = '#{plugin.name}'
59
+ ESQL
60
+ ).map(&:to_i).sort - [0]
61
+ end
62
+
63
+ # Sets the version of the plugin in Engines::Plugin::Migrator.current_plugin to
64
+ # the given version.
65
+ def record_version_state_after_migrating(version)
66
+ ActiveRecord::Base.connection.update(<<-ESQL
67
+ UPDATE #{self.class.schema_info_table_name}
68
+ SET version = #{down? ? version.to_i - 1 : version.to_i}
69
+ WHERE plugin_name = '#{self.current_plugin.name}'
70
+ ESQL
71
+ )
72
+ end
73
+ end
@@ -0,0 +1,85 @@
1
+ # The way ActionMailer is coded in terms of finding templates is very restrictive, to the point
2
+ # where all templates for rendering must exist under the single base path. This is difficult to
3
+ # work around without re-coding significant parts of the action mailer code.
4
+ #
5
+ # ---
6
+ #
7
+ # The MailTemplates module overrides two (private) methods from ActionMailer to enable mail
8
+ # templates within plugins:
9
+ #
10
+ # [+template_path+] which now produces the contents of #template_paths
11
+ # [+initialize_template_class+] which now find the first matching template and creates
12
+ # an ActionVew::Base instance with the correct view_paths
13
+ #
14
+ # Ideally ActionMailer would use the same template-location logic as ActionView, and the same
15
+ # view paths as ActionController::Base.view_paths, but it currently does not.
16
+ module Engines::RailsExtensions::ActionMailer
17
+ def self.included(base) #:nodoc:
18
+ base.class_eval do
19
+ # TODO commented this out because it seems to break ActionMailer
20
+ # how can this be fixed?
21
+
22
+ alias_method_chain :template_path, :engine_additions
23
+ alias_method_chain :initialize_template_class, :engine_additions
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ #--
30
+ # ActionMailer::Base#create uses two mechanisms to determine the proper template file(s)
31
+ # to load. Firstly, it searches within the template_root for files that much the explicit
32
+ # (or implicit) part encodings (like signup.text.plain.erb for the signup action).
33
+ # This is how implicit multipart emails are built, by the way.
34
+ #
35
+ # Secondly, it then creates an ActionMailer::Base instance with it's view_paths parameter
36
+ # set to the template_root, so that ActionMailer will then take over rendering the
37
+ # templates.
38
+ #
39
+ # Ideally, ActionMailer would pass the same set of view paths as it gets in a normal
40
+ # request (i.e. ActionController::Base.view_paths), so that all possible view paths
41
+ # were searched. However, this seems to introduce some problems with helper modules.
42
+ #
43
+ # So instead, and because we have to fool these two independent parts of ActionMailer,
44
+ # we fudge with the mechanisms it uses to find the templates (via template_paths, and
45
+ # template_path_with_engine_additions), and then intercept the creation of the ActionView
46
+ # instance so we can set the view_paths (in initialize_template_class_with_engine_additions).
47
+ #++
48
+
49
+ # Returns all possible template paths for the current mailer, including those
50
+ # within the loaded plugins.
51
+ def template_paths
52
+ paths = Engines.plugins.by_precedence.map { |p| "#{p.directory}/app/views/#{mailer_name}" }
53
+ paths.unshift(template_path_without_engine_additions) unless Engines.disable_application_view_loading
54
+ paths
55
+ end
56
+
57
+ # Return something that Dir[] can glob against. This method is called in
58
+ # ActionMailer::Base#create! and used as part of an argument to Dir. We can
59
+ # take advantage of this by using some of the features of Dir.glob to search
60
+ # multiple paths for matching files.
61
+ def template_path_with_engine_additions
62
+ "{#{template_paths.join(",")}}"
63
+ end
64
+
65
+ # Return an instance of ActionView::Base with the view paths set to all paths
66
+ # in ActionController::Base.view_paths (i.e. including all plugin view paths)
67
+ def initialize_template_class_with_engine_additions(assigns)
68
+ # I'd like to just return this, but I get problems finding methods in helper
69
+ # modules if the method implemention from the regular class is not called
70
+ #
71
+ # ActionView::Base.new(ActionController::Base.view_paths.dup, assigns, self)
72
+ renderer = initialize_template_class_without_engine_additions(assigns)
73
+ renderer.finder.view_paths = ActionController::Base.view_paths.dup
74
+ renderer
75
+ end
76
+ end
77
+
78
+ # We don't need to do this if ActionMailer hasn't been loaded.
79
+ if Object.const_defined?(:ActionMailer)
80
+ module ::ActionMailer #:nodoc:
81
+ class Base #:nodoc:
82
+ include Engines::RailsExtensions::ActionMailer
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,119 @@
1
+ # The engines plugin makes it trivial to share public assets using plugins.
2
+ # To do this, include an <tt>assets</tt> directory within your plugin, and put
3
+ # your javascripts, stylesheets and images in subdirectories of that folder:
4
+ #
5
+ # my_plugin
6
+ # |- init.rb
7
+ # |- lib/
8
+ # |- assets/
9
+ # |- javascripts/
10
+ # | |- my_functions.js
11
+ # |
12
+ # |- stylesheets/
13
+ # | |- my_styles.css
14
+ # |
15
+ # |- images/
16
+ # |- my_face.jpg
17
+ #
18
+ # Files within the <tt>asset</tt> structure are automatically mirrored into
19
+ # a publicly-accessible folder each time your application starts (see
20
+ # Engines::Assets#mirror_assets).
21
+ #
22
+ #
23
+ # == Using plugin assets in views
24
+ #
25
+ # It's also simple to use Rails' helpers in your views to use plugin assets.
26
+ # The default helper methods have been enhanced by the engines plugin to accept
27
+ # a <tt>:plugin</tt> option, indicating the plugin containing the desired asset.
28
+ #
29
+ # For example, it's easy to use plugin assets in your layouts:
30
+ #
31
+ # <%= stylesheet_link_tag "my_styles", :plugin => "my_plugin", :media => "screen" %>
32
+ # <%= javascript_include_tag "my_functions", :plugin => "my_plugin" %>
33
+ #
34
+ # ... and similarly in views and partials, it's easy to use plugin images:
35
+ #
36
+ # <%= image_tag "my_face", :plugin => "my_plugin" %>
37
+ # <!-- or -->
38
+ # <%= image_path "my_face", :plugin => "my_plugin" %>
39
+ #
40
+ # Where the default helpers allow the specification of more than one file (i.e. the
41
+ # javascript and stylesheet helpers), you can do similarly for multiple assets from
42
+ # within a single plugin.
43
+ #
44
+ # ---
45
+ #
46
+ # This module enhances four of the methods from ActionView::Helpers::AssetTagHelper:
47
+ #
48
+ # * stylesheet_link_tag
49
+ # * javascript_include_tag
50
+ # * image_path
51
+ # * image_tag
52
+ #
53
+ # Each one of these methods now accepts the key/value pair <tt>:plugin => "plugin_name"</tt>,
54
+ # which can be used to specify the originating plugin for any assets.
55
+ #
56
+ module Engines::RailsExtensions::AssetHelpers
57
+ def self.included(base) #:nodoc:
58
+ base.class_eval do
59
+ [:stylesheet_link_tag, :javascript_include_tag, :image_path, :image_tag].each do |m|
60
+ alias_method_chain m, :engine_additions
61
+ end
62
+ end
63
+ end
64
+
65
+ # Adds plugin functionality to Rails' default stylesheet_link_tag method.
66
+ def stylesheet_link_tag_with_engine_additions(*sources)
67
+ stylesheet_link_tag_without_engine_additions(*Engines::RailsExtensions::AssetHelpers.pluginify_sources("stylesheets", *sources))
68
+ end
69
+
70
+ # Adds plugin functionality to Rails' default javascript_include_tag method.
71
+ def javascript_include_tag_with_engine_additions(*sources)
72
+ javascript_include_tag_without_engine_additions(*Engines::RailsExtensions::AssetHelpers.pluginify_sources("javascripts", *sources))
73
+ end
74
+
75
+ #--
76
+ # Our modified image_path now takes a 'plugin' option, though it doesn't require it
77
+ #++
78
+
79
+ # Adds plugin functionality to Rails' default image_path method.
80
+ def image_path_with_engine_additions(source, options={})
81
+ options.stringify_keys!
82
+ source = Engines::RailsExtensions::AssetHelpers.plugin_asset_path(options["plugin"], "images", source) if options["plugin"]
83
+ image_path_without_engine_additions(source)
84
+ end
85
+
86
+ # Adds plugin functionality to Rails' default image_tag method.
87
+ def image_tag_with_engine_additions(source, options={})
88
+ options.stringify_keys!
89
+ if options["plugin"]
90
+ source = Engines::RailsExtensions::AssetHelpers.plugin_asset_path(options["plugin"], "images", source)
91
+ options.delete("plugin")
92
+ end
93
+ image_tag_without_engine_additions(source, options)
94
+ end
95
+
96
+ #--
97
+ # The following are methods on this module directly because of the weird-freaky way
98
+ # Rails creates the helper instance that views actually get
99
+ #++
100
+
101
+ # Convert sources to the paths for the given plugin, if any plugin option is given
102
+ def self.pluginify_sources(type, *sources)
103
+ options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
104
+ sources.map! { |s| plugin_asset_path(options["plugin"], type, s) } if options["plugin"]
105
+ options.delete("plugin") # we don't want it appearing in the HTML
106
+ sources << options # re-add options
107
+ end
108
+
109
+ # Returns the publicly-addressable relative URI for the given asset, type and plugin
110
+ def self.plugin_asset_path(plugin_name, type, asset)
111
+ raise "No plugin called '#{plugin_name}' - please use the full name of a loaded plugin." if Engines.plugins[plugin_name].nil?
112
+ "/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}/#{asset}"
113
+ end
114
+
115
+ end
116
+
117
+ module ::ActionView::Helpers::AssetTagHelper #:nodoc:
118
+ include Engines::RailsExtensions::AssetHelpers
119
+ end
@@ -0,0 +1,145 @@
1
+ # One of the magic features that that engines plugin provides is the ability to
2
+ # override selected methods in controllers and helpers from your application.
3
+ # This is achieved by trapping requests to load those files, and then mixing in
4
+ # code from plugins (in the order the plugins were loaded) before finally loading
5
+ # any versions from the main +app+ directory.
6
+ #
7
+ # The behaviour of this extension is output to the log file for help when
8
+ # debugging.
9
+ #
10
+ # == Example
11
+ #
12
+ # A plugin contains the following controller in <tt>plugin/app/controllers/my_controller.rb</tt>:
13
+ #
14
+ # class MyController < ApplicationController
15
+ # def index
16
+ # @name = "HAL 9000"
17
+ # end
18
+ # def list
19
+ # @robots = Robot.find(:all)
20
+ # end
21
+ # end
22
+ #
23
+ # In one application that uses this plugin, we decide that the name used in the
24
+ # index action should be "Robbie", not "HAL 9000". To override this single method,
25
+ # we create the corresponding controller in our application
26
+ # (<tt>RAILS_ROOT/app/controllers/my_controller.rb</tt>), and redefine the method:
27
+ #
28
+ # class MyController < ApplicationController
29
+ # def index
30
+ # @name = "Robbie"
31
+ # end
32
+ # end
33
+ #
34
+ # The list method remains as it was defined in the plugin controller.
35
+ #
36
+ # The same basic principle applies to helpers, and also views and partials (although
37
+ # view overriding is performed in Engines::RailsExtensions::Templates; see that
38
+ # module for more information).
39
+ #
40
+ # === What about models?
41
+ #
42
+ # Unfortunately, it's not possible to provide this kind of magic for models.
43
+ # The only reason why it's possible for controllers and helpers is because
44
+ # they can be recognised by their filenames ("whatever_controller", "jazz_helper"),
45
+ # whereas models appear the same as any other typical Ruby library ("node",
46
+ # "user", "image", etc.).
47
+ #
48
+ # If mixing were allowed in models, it would mean code mixing for *every*
49
+ # file that was loaded via +require_or_load+, and this could result in
50
+ # problems where, for example, a Node model might start to include
51
+ # functionality from another file called "node" somewhere else in the
52
+ # <tt>$LOAD_PATH</tt>.
53
+ #
54
+ # One way to overcome this is to provide model functionality as a module in
55
+ # a plugin, which developers can then include into their own model
56
+ # implementations.
57
+ #
58
+ # Another option is to provide an abstract model (see the ActiveRecord::Base
59
+ # documentation) and have developers subclass this model in their own
60
+ # application if they must.
61
+ #
62
+ # ---
63
+ #
64
+ # The Engines::RailsExtensions::Dependencies module includes a method to
65
+ # override Dependencies.require_or_load, which is called to load code needed
66
+ # by Rails as it encounters constants that aren't defined.
67
+ #
68
+ # This method is enhanced with the code-mixing features described above.
69
+ #
70
+ module Engines::RailsExtensions::Dependencies
71
+ def self.included(base) #:nodoc:
72
+ base.class_eval { alias_method_chain :require_or_load, :engine_additions }
73
+ end
74
+
75
+ # Attempt to load the given file from any plugins, as well as the application.
76
+ # This performs the 'code mixing' magic, allowing application controllers and
77
+ # helpers to override single methods from those in plugins.
78
+ # If the file can be found in any plugins, it will be loaded first from those
79
+ # locations. Finally, the application version is loaded, using Ruby's behaviour
80
+ # to replace existing methods with their new definitions.
81
+ #
82
+ # If <tt>Engines.disable_code_mixing == true</tt>, the first controller/helper on the
83
+ # <tt>$LOAD_PATH</tt> will be used (plugins' +app+ directories are always lower on the
84
+ # <tt>$LOAD_PATH</tt> than the main +app+ directory).
85
+ #
86
+ # If <tt>Engines.disable_application_code_loading == true</tt>, controllers will
87
+ # not be loaded from the main +app+ directory *if* they are present in any
88
+ # plugins.
89
+ #
90
+ # Returns true if the file could be loaded (from anywhere); false otherwise -
91
+ # mirroring the behaviour of +require_or_load+ from Rails (which mirrors
92
+ # that of Ruby's own +require+, I believe).
93
+ def require_or_load_with_engine_additions(file_name, const_path=nil)
94
+ return require_or_load_without_engine_additions(file_name, const_path) if Engines.disable_code_mixing
95
+
96
+ file_loaded = false
97
+
98
+ # try and load the plugin code first
99
+ # can't use model, as there's nothing in the name to indicate that the file is a 'model' file
100
+ # rather than a library or anything else.
101
+ Engines.code_mixing_file_types.each do |file_type|
102
+ # if we recognise this type
103
+ # (this regexp splits out the module/filename from any instances of app/#{type}, so that
104
+ # modules are still respected.)
105
+ if file_name =~ /^(.*app\/#{file_type}s\/)?(.*_#{file_type})(\.rb)?$/
106
+ base_name = $2
107
+ # ... go through the plugins from first started to last, so that
108
+ # code with a high precedence (started later) will override lower precedence
109
+ # implementations
110
+ Engines.plugins.each do |plugin|
111
+ plugin_file_name = File.expand_path(File.join(plugin.directory, 'app', "#{file_type}s", base_name))
112
+ Engines.logger.debug("checking plugin '#{plugin.name}' for '#{base_name}'")
113
+ if File.file?("#{plugin_file_name}.rb")
114
+ Engines.logger.debug("==> loading from plugin '#{plugin.name}'")
115
+ file_loaded = true if require_or_load_without_engine_additions(plugin_file_name, const_path)
116
+ end
117
+ end
118
+
119
+ # finally, load any application-specific controller classes using the 'proper'
120
+ # rails load mechanism, EXCEPT when we're testing engines and could load this file
121
+ # from an engine
122
+ if Engines.disable_application_code_loading
123
+ Engines.logger.debug("loading from application disabled.")
124
+ else
125
+ # Ensure we are only loading from the /app directory at this point
126
+ app_file_name = File.join(RAILS_ROOT, 'app', "#{file_type}s", "#{base_name}")
127
+ if File.file?("#{app_file_name}.rb")
128
+ Engines.logger.debug("loading from application: #{base_name}")
129
+ file_loaded = true if require_or_load_without_engine_additions(app_file_name, const_path)
130
+ else
131
+ Engines.logger.debug("(file not found in application)")
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ # if we managed to load a file, return true. If not, default to the original method.
138
+ # Note that this relies on the RHS of a boolean || not to be evaluated if the LHS is true.
139
+ file_loaded || require_or_load_without_engine_additions(file_name, const_path)
140
+ end
141
+ end
142
+
143
+ module ::Dependencies #:nodoc:
144
+ include Engines::RailsExtensions::Dependencies
145
+ end