wicked_pdf + rails' asset pipeline + sass import in production - ruby-on-rails-3.1

I'm successfully using wicked_pdf with SASS in development. I'm including a single .scss file, which contains several import rules for other .sass and .scss files, via this helper:
def wicked_pdf_stylesheet_link_tag(*sources)
sources.collect { |source|
"<style type='text/css'>#{Rails.application.assets.find_asset("#{source}.css").body}</style>"
}.join("\n").gsub(/url\(['"](.+)['"]\)(.+)/,%[url("#{wicked_pdf_image_location("\\1")}")\\2]).html_safe
end
But switching to production the app still looks for the imported files which aren’t found.
I've added then a second manifest file to be pre–compiled in production.rb (config.assets.precompile += %w(pdf.css)) which contains a single require rule to pick up the mentioned .scss file. This file is compiled just fine but it seems that the helper doesn't pick up the right file in production and still looks to load the imported .sass files.
Has anyone experience how to solve this? The PDF creation requires absolute paths, which makes this task a bit more difficult.

I have wicked pdf working in development and production. This is the core of my wicked_pdf config:
I've updated WickedPdfHelper (loaded from initializers/wicked_pdf.rb) based on a wicked_pdf pull request from github user antti
module WickedPdfHelper
def wicked_pdf_stylesheet_link_tag(*sources)
sources.collect { |source|
"<style type='text/css'>#{Rails.application.assets.find_asset(source+".css")}</style>"
}.join("\n").html_safe
end
def wicked_pdf_image_tag(img, options={})
asset = Rails.application.assets.find_asset(img)
image_tag "file://#{asset.pathname.to_s}", options
end
def wicked_pdf_javascript_src_tag(jsfile, options={})
asset = Rails.application.assets.find_asset(jsfile)
javascript_include_tag "file://#{asset.pathname.to_s}", options
end
def wicked_pdf_javascript_include_tag(*sources)
sources.collect{ |source| "<script type='text/javascript'>#{Rails.application.assets.find_asset(source+".js")}</script>" }.join("\n").html_safe
end
end
then in app/assets/stylesheets/pdf.css I require a few sass stylesheets:
/* ...
*= require ./_colors
*= require_directory ./pdf
*= require_self
*/
(remember that if you're modifying initializers or anything in config/, you'll need to re-start your rails app to pull in the changes)

I wrote a article on this at: http://anlek.com/2011/09/wicked_pdf-working-in-rails-3-1/
It's very similar to Philip's solution with a few modifications.

Related

Rails 3.2 Assets in Production

I'm at my wits end with this. It seems all of the newer rails apps I make its set a couple of configs in environments/production.rb, deploy and move on with my life. But now we're migrating a few rails apps to a new server and it seems all of them have this issue when deploying to production.
What appears to be happening is that neither my javascripts or stylesheets are getting compiled. And I see none of the styles for the app and the javascript does not work.
config/application.rb:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
if defined?(Bundler)
Bundler.require(:default, :assets, Rails.env)
end
module MyApp
class Application < Rails::Application
... omitted code ...
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
config.generators.stylesheet_engine = :scss
end
end
config/environments/production.rb:
MyApp::Application.configure do
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
# Compress JavaScripts and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = true
# Generate digests for assets URLs
config.assets.digest = true
... omitted code ...
end
In my application-<...>.js:
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery
//= require jquery_ujs
//= require_tree
;
And my application-<...>.css is completely empty. What am I missing?
So after a long half day yesterday and about an hour today, I suddenly realized something... I ran into this before.
Sure enough... Updating rails to 3.2.14 and deploying again fixed the whole thing.

How do I generate a sprited image file with Compass?

Background
I have a directory of images, app/assets/images/sprites/flags/*.png that will be sprited. Compass, Sass, and Sprockets work together such that I can put
#import "sprites/flags/*.png";
...
.flag.en {
#include flags-sprite(en)
}
in my .scss file. By doing so, Compass generates the file app/assets/images/sprites/flags-abc123.png when I compile SCSS files.
Problem
I would like to precompile that flags-abc123.png file via a Rake task or shell command without compiling my .scss file. Is there a way to do so?
Rationale
Compiling all of the .scss files in the application is a lengthy part of the deploy process. We would like to speed up deploys by generating them on one machine and distributing them. Unfortunately, the other machines generate bad cache-busting URLs because they are missing the compiled sprite files. If we can precompile the sprite files, we can speed up deploys dramatically.
What I tried
I tried running bundle exec compass sprite app/assets/images/sprites/flags/*.png. That generates app/assets/stylesheets/_flags.scss, but doesn't generate app/assets/images/sprites/flags-abc123.png.
I ended up making a Rake task:
class SpriteTask
include Rake::DSL
attr_reader :file_task
def initialize(glob)
#glob = glob
build_compass_sprite_map
define_task
end
private
attr_reader :glob, :map
def build_compass_sprite_map
uri = Sass::Script::String.new(glob)
context = Object.new
kwargs = {}
kwargs.extend Compass::SassExtensions::Functions::Sprites::VariableReader
#map = Compass::SassExtensions::Sprites::SpriteMap.from_uri uri, context, kwargs
#map.options = {}
end
def define_task
#file_task = file(map.filename => map.image_filenames) do |task|
map.generate
end
end
end
and using it like so:
require 'sprite_task'
namespace :sprites do
task :generate do
SpriteTask.new('foo/*.png').file_task.invoke
SpriteTask.new('bar/*.png').file_task.invoke
end
end

Broken precompiled assets in Rails 3.1 when deploying to a sub-URI

I'm in the process of updating a Rails 3 app to use Rails 3.1 and as part of that, am making use of the new asset pipeline. So far, I've got everything working apart from one rather annoying problem I can't solve.
The application and all its assets works fine in development, but in production it is deployed to a sub-URI using Passenger (http://the-host/sub-uri/). The problem with this is that the assets are pre-compiled during deployment and one of my CSS (well, it's a .css.scss file) files is making use of the image-url helper from the sass-rails gem. Since during the pre-compilation process, the paths are hard-coded into the precompiled CSS file, the sub-uri is not taken account of:
In my .css.scss file:
body { background-image: image-url("bg.png"); }
The result in the compiled application-<md5-hash-here>.css file:
body { background-image: url(/assets/bg.png); }
What it should be to make it work correctly:
body { background-image: url(/sub-uri/assets/bg.png); }
Is this scenario just asking too much? If so, I'll have to switch back to the old non-asset-pipelined way and just serve my images and CSS from public. However it seems like something which should have been thought about and solved...? Am I missing the solution?
Edit 1: I should note that using the erb solution instead yields the same result, as one would expect.
Edit 2: in response to Benoit Garret's comment
No, the problem isn't related to the config.assets.prefix. I tried setting that (to /sub-uri/assets rather than the default of /assets) but it turned out that was the wrong thing to do - it seems like this setting is already in relation to the root of the Rails app, not the server. Removing that (and thus returning to the default) has fixed all the weird issues that caused (and there were many, all the assets ended up in /sub-uri/sub-uri/assets - it was all very strange). The only problem is that the image-url helper and friends do not pick up the sub-URI when they are pre-compiled. Needless to say, this is logical since when it is pre-compiled, it couldn't possibly know that when it's running under Passenger, it'll be configured in this way. My question is how to inform it of this and thus end up with the correct paths in the precompiled result. If indeed it can be done.
My current workaround is to reference the iamge in the CSS like this: url(../images/bg.png) and place it in the non-pipelined public/images location. Hardly ideal since it doesn't benefit from the fingerprinting and everything which the pipeline provides.
Finally I've worked out a couple of workarounds/solutions.
1) From https://github.com/rails/sass-rails/issues/17 it looks like this could get fixed in sass-rails. I've monkey-patched helpers.rb myself along the lines of the proposed patch in the link above. I simply set the required environment variable in the asset precompile line in deploy.rb.
I do all my monkey patching in a single file config/initializers/gem_patches.rb. In this file I patched this method as:
module Sass
module Rails
module Helpers
protected
def public_path(asset, kind)
path = options[:custom][:resolver].public_path(asset, kind.pluralize)
path = ENV['PRODUCTION_URI'] + path if ENV['PRODUCTION_URI']
path
end
end
end
end
2) Alternatively if you are ok to embed images in the CSS, changing the stylesheet to have a .erb extension, and replacing the image-url("bg.png") with url(<%= asset_data_uri "bg.png" %>) will work without any need to change sass-rails. asset-data-uri doesn't exist as a pure Sass function so you have to use the Rails helper asset_data_uri.
In the latest Rails 3.1.3 you need to monkey patch a different module now, for it to work
This is what I did
module Sprockets
module Helpers
module RailsHelper
def asset_path(source, options = {})
source = source.logical_path if source.respond_to?(:logical_path)
path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
path = options[:body] ? "#{path}?body=1" : path
if !asset_paths.send(:has_request?)
path = ENV['RAILS_RELATIVE_URL_ROOT'] + path if ENV['RAILS_RELATIVE_URL_ROOT']
end
path
end
end
end
end
And in my deploy.rb I have:
desc "precompile the assets"
namespace :assets do
task :precompile_assets do
run "cd #{release_path} && rm -rf public/assets/* && RAILS_ENV=production bundle exec rake assets:precompile RAILS_RELATIVE_URL_ROOT='/my_sub_uri'"
end
end
before "deploy:symlink", "assets:precompile_assets"
I'm using Rails 3.1.3 and deploying to a sub-URI successfully.
I have NOT monkey-patched anything.
The key problems with this setup have been better discussed here. As you can see, the solution was applied to Rails 3.2 and never backPorted to 3.1.4.
But, I have came to a solution using Rails 3.1.3 that works for my setup.
Try this: (I'm no expert, just trying to contribute to solve a problem that hassled me for hours...)
environment.rb:
#at top:
ENV['RAILS_RELATIVE_URL_ROOT'] = '/rais'
production.rb:
config.assets.prefix = ENV['RAILS_RELATIVE_URL_ROOT'] ? ENV['RAILS_RELATIVE_URL_ROOT'] + '/assets' : '/assets'
routes.rb:
Rais::Application.routes.draw do
scope ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do #see config/environment.rb
<<resources here>>
end
end
As you can see, I've put assets.prefix inside production.rb, not in application.rb
After that you do:
rake assets:clear
rake assets:precompile
and than, test with the console:
RAILS_ENV=production rails console
Results:
foo = ActionView::Base.new
foo.stylesheet_link_tag 'application'
=> "<link href=\"/rais/assets/layout.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/rais/assets/application.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
foo.image_tag('arrow-up.png')
=> "<img alt=\"Arrow-up\" src=\"/rais/assets/arrow-up-ca314ad9b991768ad2b9dcbeeb8760de.png\" />"
After a bit of digging around, I have found the issue. The issue is in Rails, specifically Sprockets::Helpers::RailsHelper::AssetPaths#compute_public_path. Sprockets::Helpers::RailsHelper::AssetPaths inherits from ActionView::AssetPaths and overrides a number of methods. When compute_public_path is called through the Sass::Rails::Resolver#public_path method is sass-rails, the rails sprocket helper picks up the task of resolving the asset. Sprockets::Helpers::RailsHelper::AssetPaths#compute_public_path defers to super which is ActionView::AssetPaths#compute_public_path. In this method there is a condition of has_request? on rewrite_relative_url_root as seen below:
def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil)
...
source = rewrite_relative_url_root(source, relative_url_root) if has_request?
...
end
def relative_url_root
config = controller.config if controller.respond_to?(:config)
config ||= config.action_controller if config.action_controller.present?
config ||= config
config.relative_url_root
end
If you look at the internals of rewrite_relative_url_root it relies on a request to be present and the ability to derive it from the controller variable in order to resolve the relative url root. The issue is that when sprockets resolves these assets for sass it does not have a controller present and therefore no request.
The solution above didn't work in development mode for me. Here is the solution that I am using to make it work for now:
module Sass
module Rails
module Helpers
protected
def public_path(asset, kind)
resolver = options[:custom][:resolver]
asset_paths = resolver.context.asset_paths
path = resolver.public_path(asset, kind.pluralize)
if !asset_paths.send(:has_request?) && ENV['RAILS_RELATIVE_URL_ROOT']
path = ENV['RAILS_RELATIVE_URL_ROOT'] + path
end
path
end
end
end
end

rack-offline in sinatra

I am trying to setup rack-offline in Sinatra, but I am having trouble setting it up. In rails it is prettty easy, but have no found any examples in Sinatra...
Basically, in your config.ru, map /application.manifest to Rack::Offline. (If you're not familiar with using config.ru with your Sinatra application, check out this part of Sinatra docs.) Here's an example, which caches all the files under directory public:
require 'your-app'
require 'rack/offline'
map "/application.manifest" do
offline = Rack::Offline.new :cache => true, :root => "public" do
# Cache all files under the directory public
Dir[File.join(settings.public, "**/*")].each do |file|
cache file.sub(File.join(settings.public, ""), "")
end
# All other files should be downloaded
network '/'
end
run offline
end
map "/" do
run Sinatra::Application
end
Remember to set manifest="/application.manifest" in your html tag and you should be good to go. You should take a look at rack-offline's README for more documentation and explanation of how it works.

How to specify custom Sass directory with Sinatra

Instead of serving my Sass files from the default 'views' directory I'd like to change this to /assets/sass
The following attempts are in my main ruby root file in the app:
Attempt 1:
set :sass, Proc.new { File.join(root, "assets/sass") }
get '/stylesheet.css' do
sass :core
end
With this I get the following error:
myapp.rb:17 NoMethodError: undefined method `merge' for "/Users/x/x/x/mysinatraapp/assets/sass":String
Attempt 2:
get '/stylesheet.css' do
sass :'/assets/sass/core'
end
Attempt 3:
get '/stylesheet.css' do
sass :'/assets/sass/core'
end
Both return the following error:
Errno::ENOENT: No such file or directory - ./views/assets/sass/core.sass
Attempt 4:
get '/stylesheet.css' do
sass :'../assets/sass/core'
end
This works! however, there must be something along the lines of set :sass, Proc.new { File.join(root, "assets/sass") } that sets this up for me?
Set your template directory then render a Sass::Engine manually.
require 'sinatra'
require 'sass'
SASS_DIR = File.expand_path("../stylesheets", __FILE__)
get "/" do
erb :index
end
get "/stylesheets/:stylesheet.css" do |stylesheet|
content_type "text/css"
template = File.read(File.join(SASS_DIR, "#{stylesheet}.sass"))
Sass::Engine.new(template).render
end
There is no such way at the moment, as Sinatra currently only accepts a single view directory.
You could try using sinatra-compass and set :compass, :sass_dir => 'assets' and only place a single sass file in your view folder, that will simply #import stylesheet.sass or you could overwrite #sass:
helpers do
def sass(template, *args)
template = :"#{settings.sass_dir}/#{template}" if template.is_a? Symbol
super(template, *args)
end
end
set :sass_dir, '../assets'
You might want to have a look at this article. http://railscoder.com/setting-up-sinatra-to-use-slim-sass-and-coffeescript/
After coming across numerous sites, I was able to achieve having my sass files in a different directory instead of the 'views' directory with this article.
I currently can't test this myself, but have you tried the following.
set :sass, File.dirname(__FILE__) + '/assets'
EDIT: The Sass reference might help as well.
This probably doesn't help since I'm guessing you have other stuff under views that you want to stay put, but you can change the views directory as well...
set :views, File.dirname(__FILE__) + '/assets'
Then you could do:
get '/stylesheet.css' do
sass :'sass/core'
end

Resources