I use Rails 4. I can use URL helpers in my views to reference assets without thinking where the resources are located in the filesystem.
Is it easy to find location in the filesystem by providing only the asset name? I suppose that it should be possible as Rails do it somehow when serving assets.
For instance, if I use some gem and it has assets I'd like to have such a function:
f("chartkick.js") # => "/Users/username/.rvm/gems/ruby-2.0.0-p451/gems/chartkick-1.3.2/app/assets/javascripts/chartkick.js"
It was simple. Just went thru asset paths and checked the first existing file:
Rails.application.config.assets.paths.map {|x| x.to_s + "/chartkick.js" }.find { |x| File.exists?(x) }
Related
In our web application built in Rails we have several clients using the same application who will have different assets that are used dependant on which subdomain is used.
To achieve this we swap out what folder is being used on the CDN like so:
config.action_controller.asset_host = Proc.new { |source, request|
if request.subdomain.present?
"http#{request.ssl? ? 's' : ''}://cdn.domain.com/#{request.subdomain}/"
else
"http#{request.ssl? ? 's' : ''}://#{request.host_with_port}/"
end
}
Each time we create a new client we compile the assets manually using a custom build tool that uses Sprockets to build the assets the same way Rails would and then upload them to our CDN under a folder that matches the subdomain. This then allows us to have different sets of assets based purely on the subdomain.
Now this works fine except that when we update an asset the digest will change for that file but Rails will still try and load the old asset digests because the sprockets-manifest file (which is in /public/assets) e.g. .sprockets-manifest-12345.json is being loaded instead of the one that's on the CDN. Even though the asset host is different it still loads the local one.
Rails it seems doesn't care about other manifest files as the file itself only stores the filename to the fingerprinted version so even when things like the host changes it would normally be able to find the correct asset. It would seem as though Rails has been designed this way deliberately.
However we really need to get Rails to use the manifest file that is on the CDN itself rather than use the one in the public folder local to the application.
After reading the docs, it seems you can change the manifest location. We tried doing it by using the same logic as above for the manifest like so:
config.assets.manifest = Proc.new { |source, request|
if request.subdomain.present?
"http#{request.ssl? ? 's' : ''}://cdn.domain.com/#{request.subdomain}/"
else
"http#{request.ssl? ? 's' : ''}://#{request.host_with_port}/"
end
}
But Rails/Sprockets is still using the local sprockets file... Any ideas why?
The asset pipeline puts everything into the same directory. Images, stylesheets, and javascripts all go into /public/assets (although subdirectories are respected).
Is there a way to have them copied into /public/assets/images, public/assets/stylesheets, and public/assets/javascripts?
Adding to the confusion is this line in the rails guide:
http://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets
In regular views you can access images in the public/assets/images directory like this:
But rails doesn't use or make a public/assets/images directory.
Solution #1:
Create a new subdirectory within the app/assets/ like all and move the
current asset directories into that the new folder; e.g.
mkdir -p app/assets/all
mv app/assets/{javascripts,images,stylesheets} app/assets/all/
Then when precompiling assets with RAILS_ENV=production rails assets:precompile
it should create those javascripts, images and stylesheets directories
underneath the public/assets directory;
public/assets/javascripts
public/assets/images
public/assets/stylesheets
Solution #2:
You can create a new directory somewhere else in your project; like assets,
instead of app/assets; and add that the new directory to the current Rails
asset paths configuration within the assets.rb initializer
# app/config/initializers/assets.rb
Rails.application.config.assets.paths << Rails.application.root.join("assets")
Then precompiling assets should have the same effect as Solution #1.
Solution #3
So, as part of the Sprocket-Rails Engine it preloads the app/assets
subdirectories with this following block of code:
# ~/ruby/gems/2.3.0/gems/sprockets-rails-3.2.0/lib/sprockets/railtie.rb:54
module Rails
# [...]
class Engine < Railtie
# Skip defining append_assets_path on Rails <= 4.2
unless initializers.find { |init| init.name == :append_assets_path }
initializer :append_assets_path, :group => :all do |app|
app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
end
end
end
end
The line of interest is the
app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
It call to existent_directories off the *paths["app/assets"] is returning
all the subdirectories within the assets folder, hence why there are no
subdirectories in the public/assets folder when Sprocket computes its digested
assets.
In order to add those sub-directories back in, we have to modify the current
Rails configured environment for Sprockets; i.e.
Rails.application.config.configure. However, the Sprockets::Paths that is
included in the Sprockets::Configuration that
Rails.application.config.configure yields to does not allow public access to
its internal #paths variable, nor does it have an method to remove paths like
it does to add paths via its append_paths. Instead we have to duplicate the
current paths already included by the above Railtie initializer block, remove
the old "app/assets" subdirectories paths that we do not want, add in just the
"app/assets" directory we do want and then append them back into the configured
Sprockets environment; which looks something like this in a Rails initializer:
# app/config/initializers/assets.rb
Rails.application.config.assets.configure do |env|
old_paths = env.paths.dup
new_paths = old_paths - Rails.application.paths["app/assets"].existent_directories
new_paths << Rails.application.root.join("app", "assets")
env.clear_paths
new_paths.each { |path| env.append_path(path) }
end
Closing Comments
Using any of these solution should also mean that you will need to specify the subdirectory in all your
asset_path method calls within your view templates in order to find the
compiled asset; e.g.
<%= asset_path "images/example.png" %>
<%= asset_path "javascripts/application.js" %>
<%= asset_path "stylesheets/application.css" %>
/assets/images/example-ca63e56ac855bdfb187479a35a7476cd65c539727f84fea19e1ad258cf3d23f5.png
/assets/javascripts/application-a4f3e75c7f7aa6d6cbc2ebcbb436b12aca442553219883805baebdd2eecd5471.js
/assets/stylesheets/application-539757f75200b6ea43399bf5e25cbc2819c1e6a610f94d5c9beaf91fec89384e.css
I hope one these solutions help.
I would like to use a different public folder from a parent directory called client which contains the entire AngularJS app. Essentially I want to tell Rails to load AngularJS app and the only job that Rails has to do is serve JSON.
Is that possible in Ruby on Rails?
As others have mentioned, it may or may not be a great idea to override the existing paths['public'] folder. But you can do the following safely in somewhere like application.rb:
Rails.application.config.middleware.insert_after(
ActionDispatch::Static,
ActionDispatch::Static,
Rails.root.join("client").to_s,
Rails.application.config.static_cache_control
)
The public folder is exposed to the web server through the Rack middleware ActionDispatch::Static. There's nothing else special about it, and the above code simply adds another instance of the middleware that points to the directory client. So in the above case, the browser would be able to access everything in public as well as client.
Just had to do it myself for an Angular app.
Put this in your application.rb:
config.serve_static_files = true
paths['public'] = File.join 'client', 'app'
Or if you still use asset pipeline (config.assets.enabled = true):
paths['public/javascripts'] = File.join 'client', 'app', 'scripts'
paths['public/stylesheets'] = File.join 'client', 'app', 'styles'
Would be interesting to know if there are any consequences with the second bit as my front-end is served completely separately thus I keep asset pipeline switched off and use grunt instead.
You can define another path like
# config/application.rb
paths['my_website'] = 'website'
Then you can use this path in your routes like
# routes.rb
get '/my_website', to: redirect('my_website/index.html')
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
I have a few non-standard assets (i.e. files that are not images/javascript files/stylesheets such as json and binary files) that live within a mountable engine (without isolate_namespace) in app/assets/data. I want these to be part of the asset pipeline (in the same way as e.g. images).
I can add them to the asset paths collection, e.g.
class Engine < ::Rails::Engine
config.after_initialize do
Rails.application.config.assets.paths << root.join("app", "assets", "data")
end
end
and I can see in the Rails console that the assets are visible to the asset pipeline (e.g. via Rails.application.assets[] and ActionController::Base.helpers.asset_path). For instance, for a file app/assets/data/foo.json, the asset_path helper in the rails console for the hosting app gives me a path assets/foo.json, however that path does not work, I get a
ActionController::RoutingError (No route matches [GET] "/assets/foo.json")
error.
How can I get the hosting Rails app to serve these files?
Turns out, this is some odd behavior with json files with specific names. The files in question are named something like schema-[UUID].json. Rails seems to think these are calls to some controller (even though there is no such route, nor a schema controller) that want json-formatted data back. When I rename the files to [UUID]-schema.json, they all of a sudden work.