Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I dynamically require assets in the Rails 3.1 asset pipeline?

I have a plugin-based system that I use for application development in Rails. Each plugin implements an engine with MVC components, etc. The main application is simply an empty harness that delegates all the work to the plugins that are installed.

I'm currently upgrading to Rails 3.1 from Rails 2.3.5, and am trying to get the asset pipeline working with my framework.

The problem I'm having is trying to programmatically require my plugin's assets into, for example, the application.js manifest.

I can manually add them like so:

//= require <plugin_manifest_path>

And everything works as expected. However, as there are dozens of plugins in my framework, and each installation has a different mix, I want to have this manifest change based on which plugins are installed. I tried this as a solution:

<%=
Rails.plugins.collect do |plugin|
  "//= require #{plugin}"
end.join("\n")
%>

But what I discovered is that the require/directive phase of the asset pipeline compilation happens before ERB expansion, so the generated comments were simply ending up as comments.

Is there another mechanism for including paths for compilation that might work? Any way to pre-process a manifest file before the directive processing kicks in?

If I can't think of anything better, I may have to write a rake/deployment task that generates a plugin.js manifest file on deploy, but I'd love something more clear and elegant if possible. Thanks!

EDIT: Solution found, will post full solution as soon as stackoverflow lets me. See comments below in the mean time...

like image 296
Irongaze.com Avatar asked Dec 09 '11 16:12

Irongaze.com


1 Answers

OK, so here's the solution:

Internally, the asset pipeline (aka Sprockets) require directive calls context.require_asset() to actually require whatever path is specified in the directive. Turns out, that means that the require_asset method is present and available during ERB expansion. So, the correct solution was:

// ... Standard application.js stuff here ...
//= require_tree .
<%
Rails.plugins.each do |plugin|
  require_asset(plugin.to_s)
end
%>

Added that in, and all worked as expected. Whew!

In case you're wondering about that Rails.plugins bit, I added an extension to the Rails module to get the actual list of plugins that are loaded, in load order, based on config.plugins. For completeness, here it is:

module Rails
  def self.plugins
    # Get sorted list of all installed plugins
    all = Dir.glob(Rails.path('vendor/plugins/*/init.rb')).collect {|p| p.extract(/\/([^\/]+)\/init.rb$/) }
    all.sort!
    all.collect! {|p| p.to_sym }

    # Get our load order specification
    load_order = Rails.application.config.plugins

    # Split the load order out, and re-assemble replacing the :all keyword with the
    # set of plugins not in head or tail
    head, tail = load_order.split(:all)
    all -= head
    all -= tail

    # All set!
    head + all + tail
  end
end

And the final detail was creating a <plugin_name>.js manifest file in each plugin's app/assets/javascripts directory.

Hope that helps someone!

like image 67
Irongaze.com Avatar answered Oct 23 '22 16:10

Irongaze.com