I currently have a Rails 3.1 app that hosts multiple sites for several different customers. Each site is represented by a model that knows the domain name and path to where the assets are stored, along with some other info.
I've been using app/sites/domain-name
as a location for storing assets, views and locales that are specific to a given site, and are running a custom middleware and controller actions for modifying the load path for Sprockets, setting up the view paths and so on.
middleware (loaded using config.middleware.use "Configurator"
in application.rb
):
class Configurator
def initialize(app)
@app = app
end
def call(env)
@env = env
clear_existing_paths
prepend_local_assets
@app.call(env)
end
def current_site
# find site using @env
end
def current_site_path
Rails.root.join('app', 'sites', current_site.path)
end
def clear_existing_paths
paths = Rails.application.assets.paths
Rails.application.assets.clear_paths
paths.each do |path|
next if path.include?("app/sites/")
Rails.application.assets.append_path path
end
end
def prepend_local_assets
path = current_site_path.join('assets')
return unless Dir.exists?(path)
['images', 'javascripts', 'misc', 'stylesheets'].each do |subdir|
asset_dir = path.join(subdir).to_s
next unless Dir.exists?(asset_dir)
next if Rails.application.assets.paths.include? asset_dir
Rails.application.assets.prepend_path asset_dir
end
end
end
Asset bits from application.rb
:
module MyApp
class Application < Rails::Application
...
config.middleware.use "Configurator"
config.assets.enabled = true
config.assets.version = '1.0'
end
end
And environments/production.rb
:
MyApp::Application.configure do
config.serve_static_assets = false
config.assets.compress = true
config.assets.compile = true
config.assets.digest = true
# Defaults to Rails.root.join("public/assets")
# config.assets.manifest = YOUR_PATH
end
The problem is that while this setup works, it prevents me from precompiling the assets that is not shared with the entire app, making them be generated over and over again, from the looks of it.
Is there any way I could tell the precompiler to find the assets located here, and create a version of those as well? Each site has a site.css.scss
and a site.js.coffee
file that might require other assets inside the site-dir. Would be nice if I could get it precompiled to public/assets/domain-name/site.(js|css)
, so I could easily set up a separate subdomain for assets down the line when I need to optimize further
After implementing what was suggested by Brian, I have ended up with
Main stylesheet/javascript stored in app/sites/<sitename>/assets/<shortname>/site.css|js
, where sitename
is the domain for this site, and shortname
is the main part of the domain, with no subdomain or com|org|net|ccTLD.
Modified all views and stylesheets to prepend shortname
to my asset paths.
In config/application.rb
:
{ "sitename" => "shortname", ... }.each_pair do |domain, short|
%w{stylesheets javascripts}.each do |dir|
config.assets.paths << Rails.root.join("app/sites/#{domain}/assets/#{dir}").to_s
end # Had to specify each dir to make it work
config.assets.precompile += ["#{short}/site.css", "#{short}/site.js"]
end
When running rake assets:precompile
this creates public/assets/shortname
filled with all the assets for that site, and public/assets
have all the shared assets as well. Works great for my needs.
And since everything ended up in public/assets
, I was able to drop the Configurator
-middleware, since the default configuration was able to find all the assets
To compile your assets locally, run the assets:precompile task locally on your app. Make sure to use the production environment so that the production version of your assets are generated. A public/assets directory will be created. Inside this directory you'll find a manifest.
Two cleanup tasks: rake assets:clean is now a safe cleanup that only removes older assets that are no longer used, while rake assets:clobber nukes the entire public/assets directory. The clean task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, Sass and ERB. Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets.
I think the problem is, by adding each site as a path, sprockets only finds the first site.scss
I've tried this using compass, but it should be the same for plain sprockets. I haven't tried your configurator approach, but it looks straightforward to adapt my arrangement to it.
Can you change your app/sites/* to the more standard file arrangement?
./app/assets/javascripts/application.js
./app/assets/stylesheets/screen.css.scss
./app/assets/stylesheets/othersite/screen.css.scss
Change your config/application.rb, and add each of your sites. This will pregenerate all styles, on each of your hosts:
config.assets.precompile += ['screen.css', 'othersite/screen.css']
In your view/layouts/application, you'll need to configure the path to sitename:
= stylesheet_link_tag '[your sitename here]/screen'
After I rake assets:clean and precompile, I see this in public:
./assets/othersite/screen.css
./assets/othersite/screen.css.gz
./assets/screen.css
./assets/screen.css.gz
Looks like Rails manages to find the assets at least when I changed config/environments/production.rb
to include
config.assets.precompile += %w( site.js site.css )
and config/application.rb
to include
config.assets.paths << Rails.root.join("app/sites/sitename/assets/stylesheets").to_s
This gives me a precompiled version, but only for the first site to have a site.css
. I guess renaming them to sitename.css
, or add a extra subdirectory sitename/site.css
might work as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With