Add folder to asset pipeline path? - ruby-on-rails

We have a rails app that I recently updated from Rails 3.0 to Rails 3.2. This app services multiple clients. To customize it for each client, we have the directory app/themes. In there are submodules. Each submodule contains things like locales/en.yml, views/layouts, views/controller_name, etc. We use the prepend_view_path to add the theme views, and the I18n.load_path to add in the locales. We're looking at using the asset pipeline so we can keep all the mix of client material out of the public directory, and keep it contained in each theme.
Is there a way i can dynamically tell rails to load which theme/theme-name/assets folder i want? We use settings logic to set which theme is active. So if i have the theme set to "google", the ApplicationController then loads files from the path:
app/themes/google/locales/*.yml
app/themes/google/views
What I'd like to be able to do is have the manifest file,
app/themes/google/assets/stylesheets/application.css
easily accessible to the layout, much like you would in an app/views/layouts file:
= stylesheet_link_tag "application"
Is there a way i can do this? or do we need to manually move the assets into the actual assets directory?

Was able to do it in the application.rb:
require "#{Rails.root}/app/models/settings.rb"
config.assets.paths << "#{Rails.root}/app/themes/#{Settings.theme}/assets/stylesheets"
config.assets.paths << "#{Rails.root}/app/themes/#{Settings.theme}/assets/images"
config.assets.paths << "#{Rails.root}/app/themes/#{Settings.theme}/assets/javascripts"

Related

Different assets per subdomain in Rails

So I'm in the process of building a Rails app that will serve different content based on the subdomain.
The app will always be live, and when we want to release a new brand (subdomain), say newbrand.example.com, we will package up assets for that brand using Sprockets and then dump them on S3 (subdomain is the subfolder name eg. /brand/assets/... and /newbrand/assets/...).
Assets and locales will be the only thing that changes per subdomain, I already have a clean solution for the locales (using the i18n-active_record gem) but I'm stumped at different assets per subdomain.
I have some custom code that hooks into Sprockets and packages up an assets dir and creates the necessary manifest file etc:
env = Sprockets::Environment.new
# manifest.compile fails when the below method isn't present
env.context_class.class_eval do
def asset_path(path, options = {})
end
end
%w[config fonts images javascripts stylesheets].each do |folder|
Sprockets.append_path(File.join(Dir.pwd, 'assets', folder))
end
manifest = Sprockets::Manifest.new(env, File.join(Dir.pwd, 'compiled'))
assets = [%w[**/*.jpeg **/*.jpg **/*.svg **/*.png **/*.gif], ['**/*.js'], /(?:\/|\\|\A)application\.(css|js)$/]
manifest.compile(assets)
The idea is that in the background this precompiled assets folder will be sent to S3 via a separate task.
My problem is that I can't get Rails to look for the new assets when visiting on different subdomains.
The way I am switching the asset_host via subdomain is by doing the following:
config.action_controller.asset_host = Proc.new { |source, request|
"http://example.s3.amazonaws.com/#{request.subdomain}/"
}
So when I access newbrand.example.com I want it to load the assets from that subfolder on S3 and so on per brand. Is this possible?

How can I have assets compiled into image, stylesheet, and javascript directories?

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.

How to override public folder path in Rails 4?

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')

rails 4 asset pipeline image subdirectories

I know this is probably an easy question but I am stumped here.
The application I am working on houses assets like so:
app
--assets
----fonts
----images
----javascripts
I like to organize assets efficiently to avoid a mess down the road so I am trying to break up images like so:
app
--assets
----fonts
----images
------icons
------views
--------home
--------admin
Ideally I would like to reference images like image.png without having to add the folder path in front of the asset like views/home/image.png which I believe has to be possible although not setup like that out of the box.
It's possible if you manually add all paths underneath app/assets/images to the Rails asset paths in your application.rb:
Dir.glob("#{Rails.root}/app/assets/images/**/").each do |path|
config.assets.paths << path
end
In Rails 4+ any changes to asset paths should be made in:
config/initializers/assets.rb
To add all subdirectories in app/assets/images to the path, add the following:
Dir.glob("#{Rails.root}/app/assets/images/**/").each do |path|
Rails.application.config.assets.paths << path
end
Afterwards, you can verify the asset paths in the rails console with the following:
Rails.application.config.assets.paths.each do |p|
puts p
end

can I serve assets from inside of a gem

I would like to package some common assets like css, js, and icon images into a gem for my personal use.
Can I use the assets from inside of the gem directly, or do I have to have a generator move them into the main app?
What you need to do is:
Make a railtie:
module MyGemName
module Rails
class Engine < ::Rails::Engine
end
end
end
Put them in a directory that would otherwise be a proper asset path, like lib/assets/stylesheets.
Use sprockets to include javascripts:
//= require "foobar"
Use sass to include stylesheets:
#import "foobar";
Use the sass function image-url if you refer to images inside your stylesheets:
.widget {
background-image: image-url("widget-icon.png");
}
The assets directory should behave exactly the same as if it was inside your own application.
You can find an example in formalize-rails, which has stylesheets, javascripts and images.
With Rails 3.2 you can create an engine and put the assets in the assets directory where they'll be automatically picked up. Beware though if you create a mountable engine using the generator, it'll create namespaced directories under javascripts, images, and stylesheets. Don't put your stuff in those subdirectories or the parent app won't find them. Just put them directly in javascripts, images, or stylesheets.

Resources