7

In a rails 3.2 app, I would like to check for the existence of a javascript file in the asset pipeline before including the javascript_include_tag in a file. Something like:

<% if javascript_file_exists? %>
  <%= javascript_include_tag "#{controller_name}_controller" %>
<% end %>

I would like to do this so that the absence of a javascript file will not result in an error. Is there a way to do this?

John
  • 13,125
  • 14
  • 52
  • 73

6 Answers6

19

A more Rails way is to use a helper. This allows you to check for a coffeescript or a javascript version of a file:

def javascript_exists?(script)
  script = "#{Rails.root}/app/assets/javascripts/#{params[:controller]}.js"
  File.exists?(script) || File.exists?("#{script}.coffee") 
end

Then you can use it in your layout:

<%= javascript_include_tag params[:controller], :media => "all"  if javascript_exists?(params[:controller]) %>

You can do the same with your CSS:

 -- helper --
 def stylesheet_exists?(stylesheet)
   stylesheet = "#{Rails.root}/app/assets/stylesheets/#{params[:controller]}.css"
   File.exists?(stylesheet) || File.exists?("#{stylesheet}.scss") 
 end

 -- layout --
 <%= stylesheet_link_tag params[:controller], :media => "all"  if stylesheet_exists?(params[:controller]) %>


EDIT: updated #javascript_exists?

I have recently made some changes to my javascript_exists? helper:

def javascript_exists?(script)
  script = "#{Rails.root}/app/assets/javascripts/#{script}.js"
  extensions = %w(.coffee .erb .coffee.erb) + [""]
  extensions.inject(false) do |truth, extension|
    truth || File.exists?("#{script}#{extension}")
  end
end

call it in the application layout:

<%= javascript_include_tag params[:controller]  if javascript_exists?(params[:controller]) %>

This will now handle more extensions and use an inject to determine if the file exists. You can then add a bunch more extensions to the extensions array, as needed for your app.


EDIT DEUX: Updated #stylesheet_exists?

Same, but for stylesheets:

def stylesheet_exists?(stylesheet)
  stylesheet = "#{Rails.root}/app/assets/stylesheets/#{stylesheet}.css"
  extensions = %w(.scss .erb .scss.erb) + [""]
  extensions.inject(false) do |truth, extension|
    truth || File.exists?("#{stylesheet}#{extension}")
  end
end


EDIT Last (probably): DRY it up

def asset_exists?(subdirectory, filename)
  File.exists?(File.join(Rails.root, 'app', 'assets', subdirectory, filename))
end

def image_exists?(image)
  asset_exists?('images', image)
end

def javascript_exists?(script)
  extensions = %w(.coffee .erb .coffee.erb) + [""]
  extensions.inject(false) do |truth, extension|
    truth || asset_exists?('javascripts', "#{script}.js#{extension}")
  end
end

def stylesheet_exists?(stylesheet)
  extensions = %w(.scss .erb .scss.erb) + [""]
  extensions.inject(false) do |truth, extension|
    truth || asset_exists?('stylesheets', "#{stylesheet}.css#{extension}")
  end
end
WattsInABox
  • 4,548
  • 2
  • 33
  • 43
7

Although I know about the assets pipeline and the manifest in application.js, my approach is to keep app's "essential" javascript in application.js and load only the specific javascript for each controller using

<%= javascript_include_tag "application" %> 
<%= javascript_include_tag controller_name if File.exists?("#{Rails.root}/app/assets/javascripts/#{controller_name}.js") %>

at the end of my application.html.erb before the </body>

I know that causes the browser to issue one more request to get the controller specific javascript but perhaps after the first request, that javascript will be cached by the browser.

hisa_py
  • 929
  • 11
  • 16
  • I hate doing this but it seems to be the only viable way :( – Pencilcheck Jun 05 '13 at 05:30
  • I found a better approach. Couldn't find the time to post it properly yet, but basically it's all about the module pattern for Javascript assets. That way you can include just the application.js file and execute the corresponding module based on some data-* attributes you can set in the of your layout. – hisa_py Jun 10 '13 at 16:23
  • 1
    @hisa_py That's actually a pretty bad idea, since it still loads a lot of unnecessary JS for each page (even if it doesn't execute it). Separate files is better, even if the Rails core team don't think so. :) – Marnen Laibow-Koser May 22 '15 at 05:24
5

I know this is a pretty old question, but I would suggest using the find_asset function. For Rails 4 you could do something like:

<%= javascript_include_tag params[:controller] if ::Rails.application.assets.find_asset("#{params[:controller]}.js") %>
Fernando Rybka
  • 238
  • 1
  • 4
  • 14
  • Nice idea. It may be depend on the version of Rails or our preferences, but for 4 and above, I'd go with `controller_name` rather than `params[:controller]` (and `action_name`, too, since page-specific js files are far more connected to actions than controllers). – Quv May 05 '14 at 15:28
  • 1
    Works with the latest Ruby 2.2.1 and Rails 4.2.3. You can even leave out the .js extension if you want. The default templates no longer add .js (my JS files have only a .coffee extension now) but Rails will still recognize it if added. – Ryan H. Aug 22 '15 at 00:42
2

Custom ViewHelpers to the Rescue!

Add this to your ApplicationHelper:

module ApplicationHelper
  def controller_stylesheet(opts = { media: :all })
    if Rails.application.assets.find_asset("#{controller_name}.css")
      stylesheet_link_tag(controller_name, opts)
    end
  end

  def controller_javascript(opts = {})
    if Rails.application.assets.find_asset("#{controller_name}.js")
      javascript_include_tag(controller_name, opts)
    end
  end
end

and you can use them like this in your application.html.erb:

<%= controller_stylesheet %>
<%= controller_javascript %>

Note: This works with all .js, .coffee, .css, .scss even though it just says .css and .js

Sheharyar
  • 73,588
  • 21
  • 168
  • 215
0

In Rails3.2 all what you need is to write the list of your js files at the top of application.js file. For example,

//=require jquery.min
//=require jquery_ujs
//=require general

If one of these files don't really exists (btw put them at app/assets/javascripts) - not a problem, no errors will be shown.

alex
  • 3,682
  • 3
  • 21
  • 22
  • So with the asset pipeline, does each controller automatically load the js file specified in the manifest file with the same name as the controller. I.e. users_controller.rb would load users_controller.js? – John Oct 10 '12 at 22:25
  • And furthermore, you don't need to specify the javascript_include_tag in your views? – John Oct 10 '12 at 22:25
  • No, you have to write `//=require users.js` to **application.js**. Only then all logic from users.js will be accessible. – alex Oct 10 '12 at 22:33
  • To your layout just put `<%= javascript_include_tag "application" %>`. See http://railscasts.com/episodes/279-understanding-the-asset-pipeline – alex Oct 10 '12 at 22:34
  • 1
    @alex's approach would overload *EVERY* view with this `users.js`. It'd be better to just do what you did above and then ask `Rails.application.assets` if the file exists if to render it. – jackyalcine Nov 14 '12 at 21:18
  • Downvoting: this does not accomplish what was asked in the original question. – Marnen Laibow-Koser May 22 '15 at 05:25
0

Catch the error:

    #/app/helpers/application_helpers.rb

    def javascript_include_tag_if_assets(*args)
      javascript_include_tag(*args)
    rescue Sprockets::Rails::Helper::AssetNotFound
      ''
    end

Alternatively, since this asks to check before using the method, javascript_include_tag uses javascript_path so you might as well use it to check and then catch that error. This works for all javascript-like assets. For css-like assets, use stylesheet_path.

    #/app/helpers/application_helpers.rb

    def javascript_include_tag_if_assets(*files, **opts)
      files.each { |file| javascript_path(file) }
      javascript_include_tag(*files, **opts)
    rescue Sprockets::Rails::Helper::AssetNotFound
      ''
    end
RWDJ
  • 734
  • 8
  • 13