ioquatix-engines 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/CHANGELOG +280 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +95 -0
  4. data/Rakefile +199 -0
  5. data/about.yml +7 -0
  6. data/bin/rails_engines +103 -0
  7. data/generators/plugin_migration/USAGE +45 -0
  8. data/generators/plugin_migration/plugin_migration_generator.rb +78 -0
  9. data/generators/plugin_migration/templates/plugin_migration.erb +13 -0
  10. data/lib/engines.rb +185 -0
  11. data/lib/engines/assets.rb +38 -0
  12. data/lib/engines/load_engine_tasks.rake +6 -0
  13. data/lib/engines/plugin.rb +173 -0
  14. data/lib/engines/plugin/list.rb +57 -0
  15. data/lib/engines/plugin/migrator.rb +45 -0
  16. data/lib/engines/rails_extensions/action_mailer.rb +85 -0
  17. data/lib/engines/rails_extensions/asset_helpers.rb +149 -0
  18. data/lib/engines/rails_extensions/dependencies.rb +151 -0
  19. data/lib/engines/rails_extensions/routing.rb +88 -0
  20. data/lib/engines/testing.rb +87 -0
  21. data/lib/engines/version.rb +10 -0
  22. data/rails/init.rb +6 -0
  23. data/tasks/engines.rake +249 -0
  24. data/test/app/controllers/app_and_plugin_controller.rb +5 -0
  25. data/test/app/controllers/application.rb +18 -0
  26. data/test/app/controllers/namespace/app_and_plugin_controller.rb +5 -0
  27. data/test/app/helpers/mail_helper.rb +5 -0
  28. data/test/app/models/app_and_plugin_model.rb +3 -0
  29. data/test/app/models/notify_mail.rb +26 -0
  30. data/test/app/things/thing.rb +3 -0
  31. data/test/app/views/app_and_plugin/a_view.html.erb +1 -0
  32. data/test/app/views/namespace/app_and_plugin/a_view.html.erb +1 -0
  33. data/test/app/views/notify_mail/implicit_multipart.text.html.erb +1 -0
  34. data/test/app/views/notify_mail/implicit_multipart.text.plain.erb +1 -0
  35. data/test/app/views/notify_mail/multipart_html.html.erb +1 -0
  36. data/test/app/views/notify_mail/multipart_plain.html.erb +1 -0
  37. data/test/app/views/notify_mail/signup.text.plain.erb +5 -0
  38. data/test/app/views/plugin_mail/mail_from_plugin_with_application_template.text.plain.erb +1 -0
  39. data/test/app/views/plugin_mail/multipart_from_plugin_with_application_template_plain.html.erb +1 -0
  40. data/test/functional/controller_loading_test.rb +51 -0
  41. data/test/functional/exception_notification_compatibility_test.rb +29 -0
  42. data/test/functional/routes_test.rb +33 -0
  43. data/test/functional/view_helpers_test.rb +32 -0
  44. data/test/functional/view_loading_test.rb +60 -0
  45. data/test/lib/app_and_plugin_lib_model.rb +3 -0
  46. data/test/lib/engines_test_helper.rb +36 -0
  47. data/test/plugins/alpha_plugin/app/controllers/alpha_plugin_controller.rb +8 -0
  48. data/test/plugins/alpha_plugin/app/controllers/app_and_plugin_controller.rb +5 -0
  49. data/test/plugins/alpha_plugin/app/controllers/namespace/alpha_plugin_controller.rb +5 -0
  50. data/test/plugins/alpha_plugin/app/controllers/namespace/app_and_plugin_controller.rb +5 -0
  51. data/test/plugins/alpha_plugin/app/controllers/namespace/shared_plugin_controller.rb +5 -0
  52. data/test/plugins/alpha_plugin/app/controllers/shared_plugin_controller.rb +5 -0
  53. data/test/plugins/alpha_plugin/app/models/alpha_plugin_model.rb +3 -0
  54. data/test/plugins/alpha_plugin/app/models/app_and_plugin_model.rb +7 -0
  55. data/test/plugins/alpha_plugin/app/models/shared_plugin_model.rb +3 -0
  56. data/test/plugins/alpha_plugin/app/views/alpha_plugin/a_view.html.erb +1 -0
  57. data/test/plugins/alpha_plugin/app/views/app_and_plugin/a_view.html.erb +1 -0
  58. data/test/plugins/alpha_plugin/app/views/layouts/plugin_layout.erb +1 -0
  59. data/test/plugins/alpha_plugin/app/views/namespace/alpha_plugin/a_view.html.erb +1 -0
  60. data/test/plugins/alpha_plugin/app/views/namespace/app_and_plugin/a_view.html.erb +1 -0
  61. data/test/plugins/alpha_plugin/app/views/namespace/shared_plugin/a_view.html.erb +1 -0
  62. data/test/plugins/alpha_plugin/app/views/shared_plugin/a_view.html.erb +1 -0
  63. data/test/plugins/alpha_plugin/lib/alpha_plugin_lib_model.rb +3 -0
  64. data/test/plugins/alpha_plugin/lib/app_and_plugin_lib_model.rb +7 -0
  65. data/test/plugins/beta_plugin/app/controllers/app_and_plugin_controller.rb +5 -0
  66. data/test/plugins/beta_plugin/app/controllers/namespace/shared_plugin_controller.rb +5 -0
  67. data/test/plugins/beta_plugin/app/controllers/shared_plugin_controller.rb +5 -0
  68. data/test/plugins/beta_plugin/app/models/shared_plugin_model.rb +3 -0
  69. data/test/plugins/beta_plugin/app/views/namespace/shared_plugin/a_view.html.erb +1 -0
  70. data/test/plugins/beta_plugin/app/views/shared_plugin/a_view.html.erb +1 -0
  71. data/test/plugins/beta_plugin/init.rb +1 -0
  72. data/test/plugins/not_a_plugin/public/should_not_be_copied.txt +0 -0
  73. data/test/plugins/test_assets/app/controllers/assets_controller.rb +2 -0
  74. data/test/plugins/test_assets/app/views/assets/index.html.erb +3 -0
  75. data/test/plugins/test_assets/app/views/layouts/assets.html.erb +3 -0
  76. data/test/plugins/test_assets/init.rb +0 -0
  77. data/test/plugins/test_assets/public/file.txt +0 -0
  78. data/test/plugins/test_assets/public/subfolder/file_in_subfolder.txt +0 -0
  79. data/test/plugins/test_assets_with_assets_directory/assets/file.txt +0 -0
  80. data/test/plugins/test_assets_with_assets_directory/assets/subfolder/file_in_subfolder.txt +0 -0
  81. data/test/plugins/test_assets_with_assets_directory/init.rb +0 -0
  82. data/test/plugins/test_assets_with_no_subdirectory/assets/file.txt +0 -0
  83. data/test/plugins/test_assets_with_no_subdirectory/init.rb +0 -0
  84. data/test/plugins/test_code_mixing/app/things/thing.rb +3 -0
  85. data/test/plugins/test_code_mixing/init.rb +1 -0
  86. data/test/plugins/test_load_path/init.rb +0 -0
  87. data/test/plugins/test_migration/db/migrate/001_create_tests.rb +11 -0
  88. data/test/plugins/test_migration/db/migrate/002_create_others.rb +11 -0
  89. data/test/plugins/test_migration/db/migrate/003_create_extras.rb +11 -0
  90. data/test/plugins/test_migration/init.rb +0 -0
  91. data/test/plugins/test_plugin_mailing/app/models/plugin_mail.rb +26 -0
  92. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/mail_from_plugin.text.plain.erb +1 -0
  93. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_html.html.erb +1 -0
  94. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_plain.html.erb +1 -0
  95. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_with_application_template_html.html.erb +1 -0
  96. data/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_with_application_template_plain.html.erb +1 -0
  97. data/test/plugins/test_plugin_mailing/init.rb +0 -0
  98. data/test/plugins/test_routing/app/controllers/namespace/test_routing_controller.rb +5 -0
  99. data/test/plugins/test_routing/app/controllers/test_routing_controller.rb +9 -0
  100. data/test/plugins/test_routing/init.rb +0 -0
  101. data/test/plugins/test_routing/routes.rb +2 -0
  102. data/test/plugins/test_testing/init.rb +0 -0
  103. data/test/plugins/test_testing/test/fixtures/testing_fixtures.yml +0 -0
  104. data/test/unit/action_mailer_test.rb +54 -0
  105. data/test/unit/arbitrary_code_mixing_test.rb +41 -0
  106. data/test/unit/assets_test.rb +48 -0
  107. data/test/unit/load_path_test.rb +58 -0
  108. data/test/unit/migration_test.rb +63 -0
  109. data/test/unit/model_and_lib_test.rb +37 -0
  110. data/test/unit/plugins_test.rb +11 -0
  111. data/test/unit/testing_test.rb +18 -0
  112. metadata +259 -0
@@ -0,0 +1,6 @@
1
+
2
+ require RAILS_ROOT + "/config/environment"
3
+
4
+ if defined? Engines
5
+ Engines.load_plugin_tasks
6
+ end
@@ -0,0 +1,173 @@
1
+ # An instance of Plugin is created for each plugin loaded by Rails, and
2
+ # stored in the <tt>Engines.plugins</tt> PluginList
3
+ # (see Engines::RailsExtensions::RailsInitializer for more details).
4
+ #
5
+ # Engines.plugins[:plugin_name]
6
+ #
7
+ # If this plugin contains paths in directories other than <tt>app/controllers</tt>,
8
+ # <tt>app/helpers</tt>, <tt>app/models</tt> and <tt>components</tt>, authors can
9
+ # declare this by adding extra paths to #code_paths:
10
+ #
11
+ # Rails.plugin[:my_plugin].code_paths << "app/sweepers" << "vendor/my_lib"
12
+ #
13
+ # Other properties of the Plugin instance can also be set.
14
+ module Rails
15
+ class Plugin
16
+ # Plugins can add code paths to this attribute in init.rb if they
17
+ # need plugin directories to be added to the load path, i.e.
18
+ #
19
+ # plugin.code_paths << 'app/other_classes'
20
+ #
21
+ # Defaults to ["app/controllers", "app/helpers", "app/models", "components"]
22
+ attr_accessor :code_paths
23
+
24
+ # Plugins can add paths to this attribute in init.rb if they need
25
+ # controllers loaded from additional locations.
26
+ attr_accessor :controller_paths
27
+
28
+ # The directory in this plugin to mirror into the shared directory
29
+ # under +public+.
30
+ #
31
+ # Defaults to "assets" (see default_public_directory).
32
+ attr_accessor :public_directory
33
+
34
+ protected
35
+
36
+ def rails_path
37
+ File.join(directory, 'rails')
38
+ end
39
+
40
+ # Helper
41
+ def rails_path_exists?(*args)
42
+ File.exists? File.join(rails_path, *args)
43
+ end
44
+
45
+ def rails_plugin_root
46
+ if rails_path_exists?
47
+ rails_path
48
+ else
49
+ directory
50
+ end
51
+ end
52
+
53
+ # The default set of code paths which will be added to $LOAD_PATH
54
+ # and Dependencies.load_paths
55
+ def default_code_paths
56
+ # lib will actually be removed from the load paths when we call
57
+ # uniq! in #inject_into_load_paths, but it's important to keep it
58
+ # around (for the documentation tasks, for instance).
59
+ %w(app/controllers app/helpers app/models components lib)
60
+ end
61
+
62
+ # The default set of code paths which will be added to the routing system
63
+ def default_controller_paths
64
+ %w(app/controllers components)
65
+ end
66
+
67
+ # Attempts to detect the directory to use for public files.
68
+ # If +assets+ exists in the plugin, this will be used. If +assets+ is missing
69
+ # but +public+ is found, +public+ will be used.
70
+ def default_public_directory
71
+ Engines.select_existing_paths(%w(assets public).map { |p| File.join(rails_path, p) }).first
72
+ end
73
+
74
+ alias_method :original_initialize, :initialize
75
+ alias_method :original_load, :load
76
+
77
+ public
78
+ def initialize(*args)
79
+ original_initialize(*args)
80
+
81
+ @code_paths = default_code_paths
82
+ @controller_paths = default_controller_paths
83
+ @public_directory = default_public_directory
84
+ end
85
+
86
+ # Returns a list of paths this plugin wishes to make available in $LOAD_PATH
87
+ #
88
+ # Overwrites the correspondend method in the superclass
89
+ def load_paths
90
+ report_nonexistant_or_empty_plugin! unless valid?
91
+ select_existing_paths :code_paths
92
+ end
93
+
94
+ # Extends the superclass' load method to additionally mirror public assets
95
+
96
+ def load(*args)
97
+ return if loaded?
98
+ original_load(*args)
99
+
100
+ add_plugin_view_paths
101
+ Engines::Assets.mirror_files_for(self)
102
+ end
103
+
104
+ # for code_paths and controller_paths select those paths that actually
105
+ # exist in the plugin's directory
106
+ def select_existing_paths(name)
107
+ Engines.select_existing_paths(self.send(name).map { |p| File.join(rails_plugin_root, p) })
108
+ end
109
+
110
+ def add_plugin_view_paths
111
+ view_path = File.join(rails_plugin_root, 'app', 'views')
112
+ if File.exist?(view_path)
113
+ ActionController::Base.view_paths.insert(1, view_path) # push it just underneath the app
114
+ ActionView::TemplateFinder.process_view_paths(view_path)
115
+ end
116
+ end
117
+
118
+ # The path to this plugin's public files
119
+ def public_asset_directory
120
+ "#{File.basename(Engines.public_directory)}/#{name}"
121
+ end
122
+
123
+ # The path to this plugin's routes file
124
+ def routes_path
125
+ File.join(rails_plugin_root, 'routes.rb')
126
+ end
127
+
128
+ def tasks_path
129
+ File.join(rails_plugin_root, 'tasks')
130
+ end
131
+
132
+ # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
133
+ def migration_directory
134
+ File.join(rails_plugin_root, 'db', 'migrate')
135
+ end
136
+
137
+ # Returns the version number of the latest migration for this plugin. Returns
138
+ # nil if this plugin has no migrations.
139
+ def latest_migration
140
+ migrations.last
141
+ end
142
+
143
+ # Returns the version numbers of all migrations for this plugin.
144
+ def migrations
145
+ migrations = Dir[migration_directory+"/*.rb"]
146
+ migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
147
+ end
148
+
149
+ # Migrate this plugin to the given version. See Engines::Plugin::Migrator for more
150
+ # information.
151
+ def migrate(version = nil)
152
+ Engines::Plugin::Migrator.migrate_plugin(self, version)
153
+ end
154
+
155
+ class Loader
156
+ protected
157
+ alias_method :original_register_plugin_as_loaded, :register_plugin_as_loaded
158
+ def register_plugin_as_loaded(plugin)
159
+ original_register_plugin_as_loaded(plugin)
160
+
161
+ Engines.plugins << plugin
162
+ register_to_routing(plugin)
163
+ end
164
+
165
+ # Registers the plugin's controller_paths for the routing system.
166
+ def register_to_routing(plugin)
167
+ initializer.configuration.controller_paths += plugin.select_existing_paths(:controller_paths)
168
+ initializer.configuration.controller_paths.uniq!
169
+ end
170
+ end
171
+ end
172
+ end
173
+
@@ -0,0 +1,57 @@
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
+ module Plugin
11
+ class List
12
+ def initialize
13
+ @by_index = []
14
+ @by_symbol = {}
15
+ @by_string = {}
16
+ end
17
+
18
+ # Finds plugins with the set with the given name (accepts Strings or Symbols), or
19
+ # index. So, Engines.plugins[0] returns the first-loaded Plugin, and Engines.plugins[:engines]
20
+ # returns the Plugin instance for the engines plugin itself.
21
+ def [](name_or_index)
22
+ if name_or_index.is_a? Symbol
23
+ return @by_symbol[name_or_index]
24
+ elsif name_or_index.is_a? String
25
+ return @by_string[name_or_index]
26
+ else
27
+ return @by_index[name_or_index.to_i]
28
+ end
29
+ end
30
+
31
+ attr :by_index
32
+ attr :by_symbol
33
+ attr :by_string
34
+
35
+ def <<(plugin)
36
+ # Gemified plugins are sometimes username-plugin_name.
37
+ # We have to strip out username- so that canonical names work
38
+ # in this case.
39
+ sym_name = plugin.name.match(/([^-]+)$/)[1].to_sym
40
+
41
+ @by_index << plugin
42
+ @by_symbol[sym_name] = plugin
43
+ @by_string[plugin.name] = plugin
44
+ end
45
+
46
+ # Go through each plugin, highest priority first (last loaded first). Effectively,
47
+ # this is like <tt>Engines.plugins.reverse</tt>
48
+ def by_precedence
49
+ @by_index.reverse
50
+ end
51
+
52
+ def method_missing(*args, &block)
53
+ @by_index.send(*args, &block)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,45 @@
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
+ module Engines
11
+ module Plugin
12
+ class Migrator < ActiveRecord::Migrator
13
+
14
+ # We need to be able to set the 'current' engine being migrated.
15
+ cattr_accessor :current_plugin
16
+
17
+ class << self
18
+ # Runs the migrations from a plugin, up (or down) to the version given
19
+ def migrate_plugin(plugin, version)
20
+ self.current_plugin = plugin
21
+ return if current_version(plugin) == version
22
+ migrate(plugin.migration_directory, version)
23
+ end
24
+
25
+ def current_version(plugin=current_plugin)
26
+ # Delete migrations that don't match .. to_i will work because the number comes first
27
+ ::ActiveRecord::Base.connection.select_values(
28
+ "SELECT version FROM #{schema_migrations_table_name}"
29
+ ).delete_if{ |v| v.match(/-#{plugin.name}/) == nil }.map(&:to_i).max || 0
30
+ end
31
+ end
32
+
33
+ def migrated
34
+ sm_table = self.class.schema_migrations_table_name
35
+ ::ActiveRecord::Base.connection.select_values(
36
+ "SELECT version FROM #{sm_table}"
37
+ ).delete_if{ |v| v.match(/-#{current_plugin.name}/) == nil }.map(&:to_i).sort
38
+ end
39
+
40
+ def record_version_state_after_migrating(version)
41
+ super(version.to_s + "-" + current_plugin.name)
42
+ end
43
+ end
44
+ end
45
+ 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.unshift(*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,149 @@
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
+ # Returns the publicly-addressable relative URI for the given asset, type and plugin
97
+ def self.plugin_asset_path(plugin_name, type, asset)
98
+ raise "No plugin called '#{plugin_name}' - please use the full name of a loaded plugin." if Engines.plugins[plugin_name].nil?
99
+ "/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}/#{asset}"
100
+ end
101
+
102
+ #--
103
+ # The following are methods on this module directly because of the weird-freaky way
104
+ # Rails creates the helper instance that views actually get
105
+ #++
106
+
107
+ # Convert sources to the paths for the given plugin, if any plugin option is given
108
+ def self.pluginify_sources(type, *sources)
109
+ options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
110
+ result_paths = []
111
+
112
+ if options.key? "plugin"
113
+ # Old method
114
+ sources.each do |s|
115
+ result_paths << plugin_asset_path(options["plugin"], type, s)
116
+ end
117
+
118
+ options.delete "plugin"
119
+ else
120
+ # New method
121
+ sources.each do |s|
122
+ if s.is_a? Array
123
+ plugin_name = s.shift
124
+ s.each { |p| result_paths += find_asset_paths(plugin_name, type, p) }
125
+ else
126
+ result_paths << s
127
+ end
128
+ end
129
+ end
130
+ result_paths << options # re-add options
131
+ end
132
+
133
+ # This function can handle the case where asset == :all, and will return an array
134
+ def self.find_asset_paths(plugin_name, subdir, asset)
135
+ raise "No plugin called '#{plugin_name}' - please use the full name of a loaded plugin." if Engines.plugins[plugin_name].nil?
136
+
137
+ asset_root = "/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}"
138
+
139
+ if asset == :all
140
+ Dir["#{asset_root}/*"]
141
+ else
142
+ ["#{asset_root}/#{asset}"]
143
+ end
144
+ end
145
+ end
146
+
147
+ module ::ActionView::Helpers::AssetTagHelper #:nodoc:
148
+ include Engines::RailsExtensions::AssetHelpers
149
+ end