13

Situation

I use a gem that brings its own JavaScript and stylesheet assets. This gem uses a standard application.js and application.css manifest to require all its assets:

[gem]/app/assets/javascripts/gem_name/application.js
  require_tree .

[gem]/app/assets/javascripts/gem_name/backoffice/menu.js
  … some JavaScript code …

[gem]/app/assets/javascripts/gem_name/backoffice/forms.js
  … some JavaScript code …

In development mode I can then override single asset files by placing them in [my_app]/app/assets folder, e.g.:

[my_app]/app/assets/javascripts/gem_name/backoffice/menu.js

The load paths configured in Rails then ensure that files in [my_app]/app/assets override files in [gem]/app/assets.

But when I precompile the assets using rake assets:precompile, the overriding file menu.js doesn't replace the original file from the gem. The compiled application.js contains the code from the original menu.js.

Demo

See the behavior in action with this test app: https://github.com/widescape/AssetCompileTest

Considerations

So far, I found out that require_tree looks up files only in the directory of the current file and does not look across multiple load paths (e.g. app/assets or vendor/assets). So if the current file is [gem]/app/assets/javascripts/gem_name/application.js, Sprockets#require_tree will only look in [gem]/app/assets/javascripts/gem_name/ for more files and will not use the other load paths like [my_app]/app/assets/….

I am reluctant to copy all asset files from the gem into my app/assets only to override one single file. And forking the gem to customize a single file doesn't have a great appeal, too.

Is there any other way?

Note: I am aware that I could create a custom JavaScript file menu_patched.js that overrides the objects and methods defined in the original file and place it so that its definitions will be applied before the original objects and methods are called (monkey patching JavaScript style). But I am looking for a cleaner solution here.

Léo Lam
  • 3,870
  • 4
  • 34
  • 44
Robert
  • 1,936
  • 27
  • 38

2 Answers2

9

In the demo code you posted at on GitHub, your application.js has:

//= require any_gem

That does not bring in a directory, it brings in any_gem.js, which is the Gem's manifest file that brings in all of the Gem's JS files. If you do not want all of the files, then you need to change that to bring in only the files you want by listing them explicitly:

//= require ../../../vendor/assets/javascripts/any_gem/do_not_override.js

Then you can add back in all of your own JS files, including your overrides, with:

//= require_tree .

BTW, great job on creating the demo app! You're a role model for the kids.

Old Pro
  • 24,624
  • 7
  • 58
  • 106
  • You are correct that ```require_tree ./any_gem``` will bring in the directory contents of ```[my_app]/app/assets/any_gem/*```. However, require_tree will then *not* include all other JavaScript files that are contained in the gem. It will only include the files that are relative to the including file. That's [how require_tree works](https://github.com/sstephenson/sprockets/blob/master/lib/sprockets/directive_processor.rb#L289), unfortunately. Sprocket doesn't look up the load paths for each file in the require_tree file list. – Robert Jun 02 '13 at 13:39
  • I fear, the answer I am looking for, is probably not a (changed) line of code, but rather a different coding pattern... – Robert Jun 02 '13 at 13:41
  • 1
    Yes, Gems are supposed to provide their own ways of staying out of the way and allowing you to override behavior; Sprockets doesn't give you a good mechanism for overriding. Still, it would be more maintainable and better for you to require the Gem's JS files one by one (giving up on the convenience of the Gem's provided `any_gem.js`) in order to exclude the file you do not want. E.g. `//= require ../../../vendor/assets/javascripts/any_gem/other` – Old Pro Jun 02 '13 at 16:27
  • OK, following your approach, I created the branch [solution-with-individual-inclusions](https://github.com/widescape/AssetCompileTest/tree/solution-with-individual-inclusions) that also includes a verfication that the solution works in development and production environment. [See the changes for details.](https://github.com/widescape/AssetCompileTest/compare/master...solution-with-individual-inclusions) – Robert Jun 03 '13 at 11:37
  • Would you like to change your answer to reflect this solution? Then I'd be happy to accept your answer and reward you with the bounty. :) – Robert Jun 03 '13 at 11:40
  • Once again, good work with the GitHub code. I've updated the answer. – Old Pro Jun 03 '13 at 14:45
2

In application.rb or production.rb, you can prepend or append more assets path.

config.assets.prepend_path 'vendor/assets/jquery'
config.assets.append_path 'vendor/assets/something'

References:

catcyborg
  • 344
  • 1
  • 10
  • The problem is *not* that the gem assets might not be loaded. The problem is, that the app/assets do not override the gem/assets in the compiled static assets. See the example app https://github.com/widescape/AssetCompileTest to see the behavior in action. – Robert May 29 '13 at 12:52