How to tell Rails assets that new public asset file was generated - ruby-on-rails

I have a Rails application that is dynamically creating and compiling assets depending from what domain you are accessing the website (let say different colors, where values of colors are stored in database )
let say for www.hello.example it will generate public/assets/hello-application-52777d36dec6f6e311560156da9da1c2.css so browser source file will point to
layout:
= stylesheet_link_tag 'hello-application'
generated html:
<link href="/assets/hello-application-52777d36dec6f6e311560156da9da1c2.css" media="all" rel="stylesheet" />
If you add www.goodbye.example it will compile public/assets/goodbye-application-52777d36dec6f6e311560156da9da1c2.css so browser source file will point to
layout:
= stylesheet_link_tag 'goodbye-application'
generated html:
<link data-turbolinks-track="true" href="/assets/goodbye-application-52777d36dec6f6e311560156da9da1c2.css" media="all" rel="stylesheet" />
Assets are compiled correctly on the fly without need to restart the server.
Examples above work fine if I restart the server
The thing I cannot figure out is how to tell Rails that new public asset file was generated so that = stylesheet_link_tag 'goodbye-application' will pick it up without the need to restart the production server.
My guess is that it has something to do to tell Rails to reload the public/manifest.json I just cannot figure it out
Rails 4.0.2
Ruby 2.1
Unicorn production server
My functionality is kinda similar to krautcomputing article however he is solwing this problem with "Digested" which is not working for rails 4 (+ it will be deprecated soon)
Update
One dude provided an answer (which he had remove) suggesting I don't need to do this because Unicorn have zero time restart, so I can just restart the unicorn and it will load the new public assets... That's true, I'm doing this this way before I've asked this question. However I'm looking for solution that avoid this. (let say users generate 1000 styles per day)
He also suggested to use Grunt... Well, that's plan B, I'm really wondering if there is a way to do this in Rails-sprockets :)

Why not just roll your own helper method?
def subdomain_application_stylesheet_link_tag(subdomain)
if Rails.env.production?
# This assumes you've precompiled the spreadsheet into the public directory via sprockets
stylesheet_link_tag File.basename(
Dir.glob("#{Rails.root}/public/assets/#{subdomain}-application-*.css").first
)
else
# Logic to development stylesheet selection
end
end

Here is the full solution to manifest reloading issue. As stated in the original post, the new manifest.json that you dynamically generate will not reload unless you fully reload your server (or kill works, etc). Just to reiterate:
If you are dynamically creating assets 'live', along with a manifest
to prevent caching, Sprockets doesn't reload the manifest between
requests.
I needed this manifest reloading capability for development only because I am rolling-my-own asset pipeline with Gulp for a performance boost. Basically, using this solution allows the assets to function exactly how they would in production, while still being able to live-reload them when developing with a new manifest...pretty slick, I hope you find it useful for your production needs.
create the file config/initializers/live-manifest.rb:
require 'sprockets/rails/helper'
module Sprockets
module Rails
module Helper
def asset_digest_path(path, options = {})
# create the new manifest, overriding sprockets' old one
if assets_manifest and manifest = Sprockets::Manifest.new(assets, Dir.glob(File.join(assets_manifest.dir, 'manifest-*.json')).first)
if digest_path = manifest.assets[path]
return digest_path
end
end
if environment = assets_environment
if asset = environment[path]
return asset.digest_path
end
end
end
end
end
end
Also, you're more than welcome to customize the new manifest directory if you desire. Mine is in the same place as the previous, with a different name, as you can see from the Dir.glob method call.

Related

Generating list or paths of stylesheets and javascript files in Rails 4

I'm writing a Rails4 app that uses a custom cache manifest file which needs to include references to all the required Javascript and CSS files. Due to the nature of the application, the Rack Offline gem can't be used.
The stylesheet_link_tag and javascript_include_tag calls produce the correct list of files (as generated by the asset pipeline) but embed them in HTML tags.
Is there a way to get the paths to all the compiled javascript and stylesheet files in the controller?
eg.
/assets/custom.css?body=1
/assets/incidents.css?body=1
/assets/users.css?body=1
/assets/application.css?body=
/assets/jquery.js?body=1
/assets/bootstrap/affix.js?body=1
...
That one was fun! Had to go into the Sprockets source to figure it out.
asset_list = Rails.application.assets.each_logical_path(*Rails.application.config.assets).to_a
You can then go in a grep through the asset list, something like:
asset_list.grep(/\.(js|css)/)
EDIT:
If you want the hex digests, you could do something like:
environment = Rails.application.assets
asset_list = environment.each_logical_path(*Rails.application.config.assets).to_a
asset_list.map! { |asset| environment.find_asset(asset).digest_path rescue nil }.compact
Based on #kdeisz research, this code worked in the controller for the manifest file:
#assets = Rails.application.assets.each_logical_path(*Rails.application.config.assets).to_a
#assets = #assets.map{ |p| view_context.compute_asset_path(p) }
render 'manifest.text', content_type: 'text/cache-manifest'
The compute_asset_path function is needed to get the actual asset path.
Note: I haven't yet tested this in production mode. It works in development mode if you set config.assets.debug = false

How do you tell Rails / the asset pipeline that a js.erb depends on a YAML file?

I have a js.erb file that loads YAML from a config file. The problem is that Rails / the asset pipeline will cache the results and never invalidate that cache, even when I change the YAML file contents. I can restart the rails server and even reboot the machine to no avail. The only workaround I've found so far is doing a "rake assets:clean".
I would like to find a way to tell the asset pipeline that when the YAML file changes, it needs to re-compute my js.erb. Or, alternatively, tell it it can only cache the js.erb for the lifetime of the rails server / ensure somehow that re-generation occurs every time the rails server comes up or is restarted.
Any suggestions would be greatly appreciated.
Add this into a file under config/initializers and it will tell the asset pipeline to re-compute the js.erb file that loads the YAML data whenever one of the backing YAML files changes:
class ConstantsPreprocessor < Sprockets::Processor
CONSTANTS_ASSET = "support/constants"
def evaluate(context, locals)
if (context.logical_path == CONSTANTS_ASSET)
Constants.load_path.each do |dir|
dir.each do |yml|
next unless yml.end_with?".yml"
context.depend_on("#{dir.path}/#{yml}")
end
end
end
data
end
end
Rails.application.assets.register_preprocessor(
'application/javascript',
ConstantsPreprocessor)
If you're using Sprockets 3 (with Rails 5, for example), you can use // depends_on. For example, my-constants.js.erb:
//= depend_on my_constants.yml
angular
.module('services.myConstants', [])
.factory('myConstants', [
function() {
return <%= YAML::load_file(Rails.root.join('config/shared/my_constants.yml')).to_json %>;
}
]);
Just make sure the directory containing my_constants.yml is included in asset paths in application.rb:
config.assets.paths.unshift Rails.root.join('config', 'shared').to_s
I think you have 2 options:
Disable the asset pipeline and let Rails do the compilation on the go (bad for performance)
Create a daemon process, separated from Rails (look for Ruby Daemon) to look for any changes in that specific file and recompile the assets.
3 (extra!). Remove the js-YAML dependency and read the content of the YAML from a AJAX call to the app. The scenario is: the JS make a AJAX call, the controller read the YAML file and return the content of it to the JS file. So no need to recompile or watch for changes in the YAML file.
if you choose the 3, don't read the YAML in the controller, create a utility class to do it and let the controller ask that class to read the file and pass it's content.
You can add your own processor directive that works on files outside of the assets directory. = depend_on only works with asset files (https://github.com/rails/sprockets#depend_on)
In config/initializers/sprockets.rb:
Sprockets::DirectiveProcessor.class_eval do
def process_depend_on_project_file_directive(file)
path = Rails.root.join(file).to_s
if File.exists?(path)
deps = Set.new
deps << #environment.build_file_digest_uri(path)
#dependencies.merge(deps)
end
end
end
Usage:
//= depend_on_project_file "config/setting.yml"
See this comment on github for details: https://github.com/rails/sprockets/issues/500#issuecomment-491043517

Yeoman & Rails app: using grunt-rev task to revision shared assets

I have a Rails app which is not using the asset pipeline. The assets are managed by Yeoman (http://yeoman.io/) and Grunt tasks. We recently introduced a browser cache busting task called grunt-rev to revision the assets before deploying. This task re-names the assets with some uniqueness, then flow passes on to another task called grunt-usemin which updates the html and css files with the new asset names before copying them to the public folder.
For example, main.css in index.html looks like:
<link rel="stylesheet" href="styles/main.css">
And will be re-witten to:
<link rel="stylesheet" href="styles/f43ce6bf.main.css">
The Rails app works fine with local development as the assets are dynamically generated by Yeoman. But once deployed, the assets public folder contains all the revved assets and the Rails views (written in haml) cannot find them. Links like the one below are now dead...
%link{:href => "/styles/main.css", :media => "screen,print", :rel => "stylesheet"}
So I've come up with a few ideas I think might work.
Symlink main.css -> f43ce6bf.main.css
Add a view helper to find the assets
Inline the javascript & css into the view as it is not too big
Fork grunt-usemin to add haml support, then run it against the views within Rails
But am not sure whats best? or am I missing a much easier solution?
UPDATE: I'm now using the helper like below
module ApplicationHelper
def busted_css
#busted_css ||= begin
if Rails.env != "development"
File.join("/styles",
File.basename(Dir["public/styles/*main.css"].sort_by {|f| File.mtime(f)}.reverse[0])
)
else
"/styles/main.css"
end
end
end
end

Rails 3: Scopes: Routing: JS/CSS helpers

I want to run my Rails application in a different scope so that I can deploy it in a war file (mydepartment.war) which will share a Tomcat instance with another Java app WAR. The solution I chose was to modify the rackup file (/config.ru).
map '/mydepartment' do
run Myapp::Application
end
When I do this, my base URL becomes http://localhost:3000/mydepartment instead of just http://localhost:3000. The application runs fine, but it doesn't download CSS/JS specified by stylesheet and script helpers.
However, when I try to include stylesheets and Javascript using helpers, such as
<%= stylesheet_link_tag :all %>
<%= javascript_include_tags :defaults %>
The URLs they generate include localhost:3000/javascripts/jquery.js instead of localhost:3000/mydepartment/javascripts/jquery.js. I actually tried typing the latter in the browser, and the sheet downloads fine.
How can I coax the Rails Javascript/CSS helpers to download files in the new scope without hardcoding it?
if you're not on rails 3.1:
Add this to your config/environments/production.rb (if on production mode):
config.action_controller.asset_path = proc { |path| "/mydepartment#{path}" }

Always preprocess a specific Javascript file with Rail 3.1 asset pipeline

Is there a way to always run the ERB preprocessor on a Javascript file?
I'm using Mustache to use the same templates on the client and server. I'd like to include these templates in my application.js files so they're available on the client. So I'm preprocessing my Javascript file (templates.js.erb, which then gets required in application.js) with erb:
App.templates.productShow = <%= MustacheController.read("product/show").to_json %>;
This works great but when I edit the "product/show.html.mustache" template I need to also edit "templates.js.erb" so Rails knows to recompile this file which then picks up the latest changes from the mustache template.
There's no issue running this in production since the assets get compiled when I deploy, but it's annoying when developing. Ideally I could set the preprocessor to run on "templates.js.erb" every time I reload. My current solution is to inline the Javascript in the application layout but it would be nice to keep it separate.
I ended up writing a guardfile for this that adds a timestamp to the end of the file. Just touching the file is enough for sprockets to recompile but if you're using git you need to actually alter the file. Otherwise anyone else who pulls in the code won't get the latest preprocessed files. Here's the code...
#!/usr/bin/ruby
guard 'mustache' do
watch(%r{app/templates/.+\.mustache})
end
require 'guard/guard'
module ::Guard
class Mustache < ::Guard::Guard
def run_on_change(paths)
# file to be updated when any mustache template is updated
file_name = "./app/assets/javascripts/templates.js.erb"
# get array of lines in file
lines = File.readlines(file_name)
# if last line is a comment delete
lines.delete_at(-1) if lines[-1].match /\/\//
# add timestamp
lines << "// #{Time.now}"
# rewrite file
File.open(file_name, "w") do |f|
lines.each{|line| f.puts(line)}
end
end
end
end
This seems to be a common complaint with the pipeline - that you have to touch a file that references variables for those changes to be reflected in development mode. A few people have asked similar questions, and I do not think there is a way around it.
Forcing Sprockets to recompile for every single request is not really a viable solution because it takes so long to do the compilation.
Maybe you could set up guard to watch your mustache directory and recompile templates.js.erb when you make changes to it. Similar to how guard-bundler watches your Gemfile and rebundles on change.

Resources