Rails 3.1资产管道: 如何加载特定于控制器的脚本?

如果我在 Rails 3.1中生成一个新的控制器,也会自动添加一个包含控制器名称的 javascript 文件。首先,我认为这个 javascript 文件只会在调用相关控制器时使用。

默认情况下,在 application.js-file 中有指令 //= require_tree .,它包含其树上的每个 javascript 文件。

如何只加载控制器特定的脚本?

37094 次浏览

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

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 !

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.

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") %>

I recently found a simple approach to use generated scripts for specific controller. I use for that solution gem gon. Add in a controller:

class HomesController < ApplicationController
before_filter :remember_controller


private


def remember_controller
gon.controller = params[:controller]
end
end

After that open your homes.js.cofee and add in the beginning of file:

jQuery ->
if gon.controller == "sermons"
# Place all functions here...

That is all.

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/