pager-engines 2.0.20080513

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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