Rails 3.1 asset pipeline: how to load controller-specific scripts?

36,969

Solution 1

To load only the necessary name_of_the_js_file.js file:

  1. remove the //=require_tree from application.js

  2. keep your js file (that you want to load when a specific page is loaded) in the asset pipeline

  3. add a helper in application_helper.rb

    def javascript(*files)
      content_for(:head) { javascript_include_tag(*files) }
    end
    
  4. yield into your layout:

    <%= yield(:head) %>
    
  5. add this in your view file:

    <% javascript 'name_of_the_js_file' %>
    

Then it should be ok

Solution 2

An elegant solution for this is to require controller_name in your javascript_include_tag

see http://apidock.com/rails/ActionController/Metal/controller_name/class

<%= javascript_include_tag "application", controller_name %>

controller_name.js will be loaded and is in the asset also, so you can require other files from here.

Example, rendering cars#index will give

<%= javascript_include_tag "application", "cars" %>

where cars.js can contain

//= require wheel
//= require tyre

Enjoy !

Solution 3

I always include this inside my layout files. It can scope your js to action

<%= javascript_include_tag params[:controller] if AppName::Application.assets.find_asset("#{params[:controller]}.js") %>
<%= javascript_include_tag "#{params[:controller]}_#{params[:action]}"  if AppName::Application.assets.find_asset("#{params[:controller]}_#{params[:action]}.js") %>

Solution 4

Your problem can be solved in different ways.

Add the assets dynamically

Please consider that this isn't a good solution for the production mode, because your controller specifics won't be precompiled!

  1. Add to our application helper the following method:

    module ApplicationHelper
        def include_related_asset(asset)
        #          v-----{Change this}
            if !YourApp::Application.assets.find_asset(asset).nil?
                case asset.split('.')[-1]
                    when 'js'
                        javascript_include_tag asset
                    when 'css'
                        stylesheet_link_tag asset
                end
            end
        end
    end
    
  2. Call the helper method in your layout-file:

    <%= include_related_asset(params[:controller].to_param + '_' + params[:action].to_param . 'js') %>
    
  3. Create specific assets for your controller actions. E. g. controller_action.js

Please don't forget to change YourApp to the name of your app.

Use yield

  1. Add <%= yield :head%> to your layout head
  2. Include your assets from your action views:

    <% content_for :head do %>
    <%= javascript_include_tag 'controller_action' %>
    <% end %>
    

Please see the Rails guides for further information.

Solution 5

I like albandiguer's solution. With which I've found that javascript/coffeescript assets are not individually precompiled. Which causes all sorts of errors trying to use javascript_path. I'll share my solution to that problem after I address an issue a few people mentioned in his comments. Mainly dealing with only a partial set of controller named JavaScript files.

So I built an application helper to detect if the file exists in the javascript directory regardless of .coffee/.js extension:

module ApplicationHelper
  def javascript_asset_path(basename)
    Sprockets::Rails::Helper.assets.paths.select{|i|
      i =~ /javascript/ and i =~ /#{Rails.root}/
    }.each do |directory|
      if Dir.entries(directory).map {|i| i.split('.')[0]}.compact.
          include? basename
        return File.join(directory, basename)
      end
    end
    nil
  end
end

This method will return the full path to the javascript file if it exists. Otherwise it returns nil. So following Pencilcheck's comment you can add this method for a conditional include:

<%= javascript_include_tag(controller_name) if javascript_asset_path(controller_name) %>

And now you have a proper conditional include. Now for the issue of precompiled assets. Generally for optimization you don't want assets precompiled individually. You can however do it if you must:

# Live Compilation
config.assets.compile = true

You can add this do your environment config file. Test it in your development environment file first. Again this is ill-advisable. The Rails asset pipeline uses Sprockets to optimize everything:

Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if Rails.application.config.assets.compress is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. Compression also reduces file size, enabling the browser to download them faster.

PLEASE READ the documentation for further details of the mechanics of Sprockets (Asset Pipeline) http://guides.rubyonrails.org/asset_pipeline.html

Assets aren't precompiled individually. For example when I try:

<%= javascript_include_tag 'event' %>

I get:

Sprockets::Rails::Helper::AssetFilteredError: Asset filtered out and will not be served: add Rails.application.config.assets.precompile += %w( event.js ) to config/initializers/assets.rb and restart your server

So you can include which assets to be precompiled individually. We just need to add the relevant controller named javascript files in our asset initializer. Well we can do this programatically.

To get a list of controller names I will use ecoologic's example:

all_controllers =  Dir[
    Rails.root.join('app/controllers/*_controller.rb')
  ].map { |path|
    path.match(/(\w+)_controller.rb/); $1
  }.compact

And now to get the name of all javascript files that match the basename of the controller name you can use the following:

javascripts_of_controllers = Sprockets::Rails::Helper.assets.paths.select{|a_path|
    a_path =~ /javascript/ and a_path =~ /#{Rails.root}/
  }.map {|a_path|
    Dir.entries(a_path)
  }.flatten.delete_if {|the_file|
    !the_file['.js']
  }.collect {|the_file|
    the_file if all_controllers.any? {|a_controller| the_file[a_controller]}
  }

Then you can try:

# config/initializers/assets.rb
Rails.application.config.assets.precompile += javascripts_of_controllers

This will get you a list of all javascript files, without directory path, that match your controller name. Note if your controller name is plural, the javascript name should be as well. Also note if the controller is singular and the javascript file is plural this will still include it because of the_file[a_controller] will succeed on a partial match.

Feel free to try this out in your Rails.application.config.assets.precompile setting. I know that this gets you the list of files correctly. But I'll leave you to test it. Let me know if there are any nuances involved with precompiling this way as I am curious.

For a very thorough explanation on how assets precompile see this blog: http://www.sitepoint.com/asset-precompile-works-part/

Share:
36,969

Related videos on Youtube

Mike Bevz
Author by

Mike Bevz

Software Developer: - Node.js - JavaScript / TypeScript - Machine Learning - Mobile apps (iOS, Android) - Cloud native apps (AWS, GC)

Updated on July 05, 2022

Comments

  • Mike Bevz
    Mike Bevz almost 2 years

    If I generate a new controller in Rails 3.1, also a javascript file with the name of the controller will added automatically. Firstly, I thought this javascript file will used only, when the related controller is called.

    By default there is the instruction //= require_tree . in the application.js-file, that include every javascript file on it's tree.

    How could I load only the controller specific script?

    • gdelfino
      gdelfino over 11 years
      It may not be a good idea to do that. Please see the answers to this related question: stackoverflow.com/questions/8250951/…
    • user2918201
      user2918201 about 11 years
      Write your javascript so that it is page specific, and then don't worry about the fact that everything is mashed together. If this were compiled code, that's what you'd do, right?
    • Ciro Santilli OurBigBook.com
      Ciro Santilli OurBigBook.com almost 10 years
  • cailinanne
    cailinanne over 12 years
    It's worth noting that this method works nicely in production. E.g. if you look at the source in production you'll see that the individual controller javascript file gets an appropriate cache-busting name, just like the main application.js file: <script src="/assets/mycontroller-923cef714b82e7dec46117f9aab7fb2c.j‌​s" type="text/javascript"></script>
  • Canopus
    Canopus over 12 years
    Yes because the file itself is in the assets pipeline. We just doesn't want it to be required in application.js.
  • Andrew Theis
    Andrew Theis over 12 years
    This doesn't appear to work in the first official release of Rails 3.1
  • Canopus
    Canopus over 12 years
    Can you be more specific? maybe I can help.
  • Andrew Burns
    Andrew Burns about 12 years
    While this is obvious after reading it, the solution did not immediately occur to me.
  • ZMorek
    ZMorek about 12 years
    make sure to add to your config/application.rb a line like config.assets.precompile += %w(name_of_js_file.js) or you might get precompile issues like I did. See also jalada.co.uk/2012/01/23/…
  • ZMorek
    ZMorek about 12 years
    If you don't have a file for every controller_name.js you might see some precompile issues and cache misses, especially if you don't explicitly precompile every one of those.
  • Tatiana Tyu
    Tatiana Tyu almost 12 years
    works for me in rails 3.2.3 (requires this config.assets.precompile option as specified by ZMorek above)
  • Tatiana Tyu
    Tatiana Tyu almost 12 years
    precompile issues are resolved by config.assets.precompile setting - see ZMorek's comment for another answer for this question.
  • rassom
    rassom over 11 years
    Newbie question: What does "keep your js file that you want to load when a specific page is loaded in the asset pipeline" mean? To have the page specific .js file in app/assets/javascripts without referencing it in application.js?
  • rassom
    rassom over 11 years
    Thank you, @NguyenChienCong :)
  • Ken W
    Ken W over 11 years
    Or you can just do <%= stylesheet_link_tag params[:controller] %> or <%= javascript_include_tag params[:controller] %> in your layout html.erb
  • janosrusiczki
    janosrusiczki about 11 years
    Although I really like this, it doesn't seem to work in production.
  • installero
    installero about 11 years
    The best solution, I suppose.
  • Joe Essey
    Joe Essey about 11 years
    @Nguyen Chien Cong, Can you please be more specific about what you mean by "yield into your layout:"
  • Canopus
    Canopus about 11 years
    @JoeEssey By "yield", I mean add the line <%= yield(:head) %> into your application.html.erb
  • Pencilcheck
    Pencilcheck almost 11 years
    Better: <%= javascript_include_tag controller_name if asset_path(controller_name) %> <%= stylesheet_link_tag controller_name, media: "all" if asset_path(controller_name) %>
  • Scott Carter
    Scott Carter almost 11 years
    This technique also works for stylesheets. Since I was using caching on my page view (but not layout view), I decided to add the helper code call into the layout. I qualified which page view to use with controller.action_name Example: <% if controller.action_name == "index" stylesheet 'contacts_index' javascript 'contacts_index' end %>
  • Scott Carter
    Scott Carter almost 11 years
    Hmm. A better formatted example: <% if controller.action_name == "index" then stylesheet 'contacts_index'; javascript 'contacts_index'; end %>
  • Alter Lagos
    Alter Lagos over 10 years
    @Pencilcheck Your solution doesn't work. asset_path returns always a path, even if the file doesn't exist
  • TomFuertes
    TomFuertes over 10 years
    @kitsched - you might need to add all your assets to config.assets.precompile via something like stackoverflow.com/a/18992685/94668
  • janosrusiczki
    janosrusiczki over 10 years
    Thanks, will give it a try.
  • Konstantin Voronov
    Konstantin Voronov almost 9 years
    where should i put all_controllers and javascripts_of_controllers statements ?
  • Konstantin Voronov
    Konstantin Voronov almost 9 years
    well. i put both in my assets initializer (assets.rb) but Sprockets::Rails::Helper.assets.paths was null here so i had to change it to Rails.application.config.assets.paths the rest was fine. very nice solution
  • Derrick Mar
    Derrick Mar over 8 years
    One tradeoff you make with this approach is that you will be making one extra request for the per each javascript file included. Additionally, some argue that your mixing javascript concerns with html views.
  • Jay
    Jay about 8 years
    This is the best solution for me. And it works in production.