Rails - How to pass Sprockets::Context in manual sass compiling - ruby-on-rails

I'm using the following code snippet to manually compile a sass manifest with some variable overrides appended.
template = File.read("#{Rails.root}/app/assets/schemes/#{scheme}/css/styles.css.scss")
scheme_variables.each do |key, value|
template << "$#{key}:#{value};\n"
end
engine = Sass::Engine.new(template, {
:syntax => :scss,
:cache => false,
:read_cache => false,
:style => :compressed,
:filesystem_importer => Sass::Rails::SassImporter,
:load_paths => MyApp::Application.assets.paths,
:sprockets => {
:context => ?,
:environment => MyApp::Application.assets
}
})
output = engine.render
The Sass::Engine constructor wants a sprockets context and environment in the options hash. What do I put in for the context? The first thing I tried was...
:context => MyApp::Application.assets.context_class,
...but that gives me the following error "undefined method `font_path' for #" when it hits one of my sass asset helpers.
The second thing I tried was...
:context => ActionController::Base.helpers,
...That fixed the asset helper issue, but throws the following error "undefined method `depend_on' for #" when it tries to work through my glob imports (e.g. #import "mixins/*").
I'm using Rails 4.2 and sass-rails 5.0.3.
Any advice on this would be much appreciated. Thanks!

With using Sass::Rails::ScssTemplate you can render your sass code with this snippet:
template = '...' # Your sass code
logical_path = pathname = ''
environment = Rails.application.assets
context = environment.context_class.new(environment, logical_path, pathname)
template = Sass::Rails::ScssTemplate.new(pathname) { template }
output = template.render(context, {})
If you want to render from a file then just add its path to pathname and its asset path to logical_path.
For me it works with Rails 4.2.5.1 and sass-rails 5.0.4.

I ended up solving this in a slightly different way - using Sass::Rails::ScssTemplate's render method. Basically, I write my altered css string out to a file and pass it into the Sass::Rails::ScssTemplate constructor. I then compile and remove the temp file when it's done. This doesn't feel great, but it's working well for me.
scheme_css_dir = "#{Rails.root}/app/assets/schemes/#{scheme}/css"
css = File.read("#{scheme_css_dir}/styles.css.scss")
variables_str = ''
scheme_variables.each do |key, value|
variables_str << "$#{key}:#{value};\n"
end
css.gsub!('#import "./variables";', variables_str)
file = Tempfile.new(['styles', '.css.scss'], scheme_css_dir)
file.write(css)
file.close
abs_path = file.path
relative_path = abs_path[Rails.root.to_s.size + 1..-1]
template = Sass::Rails::ScssTemplate.new(abs_path)
environment = Evrconnect::Application.assets
context = environment.context_class.new(
:environment => environment,
:name => relative_path,
:filename => abs_path,
:metadata => {}
)
output = template.render(context)
file.unlink

To answer your original question, you need to supply either the current view_context, or create one with ActionView::Base.new.
http://apidock.com/rails/AbstractController/Rendering/view_context

In Rails 5.x, Sprockets v3.7.x
NOTE: Following method requires: Rails.application.config.assets.compile == true, i.e., set config.assets.compile = true. Check Sprockets README
I manually returned compiled source for sass/js, using the following code.
# For Scss Assets
def pdf_stylesheet_link_tag(name)
if Rails.env.development?
asset = Rails.application.assets.find_asset(name + '.scss')
raw ('<style type="text/css">' + asset.source + '</style>')
else
raw ('<style type="text/css">' + pdf_asset_contents(name, '.css') + '</style>')
end
end
# For Javascript Assets
def pdf_javascript_include_tag(name, *type)
if Rails.env.development?
if debug? # Can be used to check `debug == true` in params for current route
javascript_include_tag(name)
else
asset = Rails.application.assets.find_asset(name + '.js')
raw ('<script>' + asset.source + '</script>')
end
else
javascript_tag pdf_asset_contents(name, '.js')
end
end
Above code actually helps to dynamically pass compiled version of sass/js
to Wicked_PDF, which actually helps to load styles and js on Generated PDFs, for Dev Environments.
pdf_stylesheet_link_tag can be used as a helper for templates, in development/stage (where, config.assets.precompile == false), instead of using wicked_pdf_stylesheet_link_tag(which actually requires a path to precompiled source-file).

After overnight trial and errors, I found and want to share todays's way of doing it ("today" meaning: rails 5.2.1 and sprockets 3.7.2).
Work as expected: no need of a temp file, accept #import, allow asset path helpers.
# Compile SASS and return the resulting string
# Pass the file path and name without 'sass' extension, relative to assets/stylesheets
def compile_sass(stylesheet)
# Load file content
path = Rails.root.join 'app', 'assets', 'stylesheets', "#{stylesheet}.sass"
sass = File.read path
# Configure engine
environment = Sprockets::Railtie.build_environment Rails.application
scope = environment.context_class.new environment: environment, \
filename: "/", metadata: {}
scope.sass_config.merge! cache: false, style: :compressed
# Compile
engine = Sass::Rails::SassTemplate.new {sass}
engine.render scope
end
Other sass_config options can be found here : https://github.com/sass/ruby-sass/blob/stable/doc-src/SASS_REFERENCE.md#options

The following works with rails 5.2.0, sprockets 3.7.2, sassc-rails 1.3.0 and sassc 1.12.1:
template = '...' # Your sass code
environment = Sprockets::Railtie.build_environment(Rails.application)
engine = SassC::Rails::SassTemplate.new
engine.call(environment: environment,
filename: '/',
data: template,
metadata: {})[:data]

Related

Brakeman not skipping Gemfile.lock with --skip-files param

I'm adding Brakeman to a Rails product but I'm running into an issue. I want it to ignore my Gemfile and Gemfile.lock but when I run it with a command like
brakeman --skip-files Gemfile.lock,Gemfile
it's still touching the files. We use other systems to monitor our gems, but is it not possible to ignore the gem files completely? I can use a brakeman.ignore file of course but would prefer not to. Thanks for any assistance.
I believe this is the check to which you are referring:
https://github.com/presidentbeef/brakeman/blob/master/lib/brakeman/scanner.rb#L39-L40
Brakeman.notify "Processing gems..."
process_gems
The process_gems function is defined here:
https://github.com/presidentbeef/brakeman/blob/master/lib/brakeman/scanner.rb#L131-L152
#Process Gemfile
def process_gems
gem_files = {}
if #app_tree.exists? "Gemfile"
gem_files[:gemfile] = { :src => parse_ruby(#app_tree.read("Gemfile")), :file => "Gemfile" }
elsif #app_tree.exists? "gems.rb"
gem_files[:gemfile] = { :src => parse_ruby(#app_tree.read("gems.rb")), :file => "gems.rb" }
end
if #app_tree.exists? "Gemfile.lock"
gem_files[:gemlock] = { :src => #app_tree.read("Gemfile.lock"), :file => "Gemfile.lock" }
elsif #app_tree.exists? "gems.locked"
gem_files[:gemlock] = { :src => #app_tree.read("gems.locked"), :file => "gems.locked" }
end
if gem_files[:gemfile] or gem_files[:gemlock]
#processor.process_gems gem_files
end
rescue => e
Brakeman.notify "[Notice] Error while processing Gemfile."
tracker.error e.exception(e.message + "\nWhile processing Gemfile"), e.backtrace
end
The AppTree::exists? function is defined here:
https://github.com/presidentbeef/brakeman/blob/master/lib/brakeman/app_tree.rb#L82-L84
def exists?(path)
File.exist?(File.join(#root, path))
end
The GemProcessor::process_gems function is defined here:
https://github.com/presidentbeef/brakeman/blob/master/lib/brakeman/processors/gem_processor.rb#L11
...lots of code...
I don't see any code that would skip this functionality if a certain switch is provided to brakeman. It also looks like the AppTree::exists? function does not take into account if a file was provided to the --skip-files option.
Unfortunately, I believe the current answer is that you can not ignore the gem files completely.
You could create a PR to do what you want and see if the Brakeman team includes it in the next build:
https://brakemanscanner.org/docs/contributing/
Let us know if you discover a way to solve your problem.

Rails 4: compiling assets dynamically with sprockets

I want to precompile some of my scss files dynamically (at the runtime). In Rails 3 I used to use Sprockets::StaticCompiler like that.
env = Rails.application.assets
target = File.join(Rails.public_path, config.assets.prefix)
compiler = Sprockets::StaticCompiler.new(env,
target,
config.assets.precompile,
:manifest_path => config.assets.manifest,
:digest => config.assets.digest,
:manifest => digest.nil?)
compiler.compile
How should I do it in Rails 4? There is no documentation or anything on internet.
Thanks for help
I had the same problem and solved it by using the Sass engine to compile my asset and then writing it to the public/assets folder.
asset = env.find_asset(self.sass_file_path)
compressed_body = ::Sass::Engine.new(asset.body, {
:syntax => :scss,
:cache => false,
:read_cache => false,
:style => :compressed
}).render
File.open(File.join(Rails.root, "public", "assets", self.stylesheet_file(asset.digest)), 'w') { |f| f.write(compressed_body) }
Have a look at this article for an example: http://matteodepalo.github.io/blog/2013/01/31/how-to-create-custom-stylesheets-dynamically-with-rails-and-sass/

How do you output call tree profiling for KCacheGrind with ruby-prof for a Rails app?

According to the documentation, you can profile Rails apps
http://ruby-prof.rubyforge.org/
I added this to my config.ru
if Rails.env.development?
use Rack::RubyProf, :path => 'tmp/profile'
end
However it only outputs the following files
users-1-call_stack.html
users-1-flat.txt
users-1-graph.html
users-1-graph.txt
The output is completely incomprehensible. So I downloaded QCacheGrind for Windows.
http://sourceforge.net/projects/qcachegrindwin/?source=recommended
It won't read any of those files. The ruby-prof docs says that you can generate KCacheGrind files
RubyProf::CallTreePrinter - Creates a call tree report compatible with KCachegrind.
But it won't say how to do it with Rails. I looked at the page for RubyProf, but it was empty.
http://ruby-prof.rubyforge.org/classes/Rack/RubyProf.html
Ruby 2.0.0, Rails 4.0.3
helper_method :profile
around_action :profile, only: [:show]
def profile(prefix = "profile")
result = RubyProf.profile { yield }
# dir = File.join(Rails.root, "tmp", "profile", params[:controller].parameterize)
dir = File.join(Rails.root, "tmp", "profile")
FileUtils.mkdir_p(dir)
file = File.join(dir, "callgrind.%s.%s.%s" % [prefix.parameterize, params[:action].parameterize, Time.now.to_s.parameterize] )
open(file, "w") {|f| RubyProf::CallTreePrinter.new(result).print(f, :min_percent => 1) }
end
Change config.ru
use Rack::RubyProf, :path => ::File.expand_path('tmp/profile'),
:printers => {::RubyProf::FlatPrinter => 'flat.txt',
::RubyProf::GraphPrinter => 'graph.txt',
::RubyProf::GraphHtmlPrinter => 'graph.html',
::RubyProf::CallStackPrinter => 'call_stack.html',
::RubyProf::CallTreePrinter => 'call_grind.txt',
}

How to update/rename a carrierwave uploaded file?

I cant figure out how to update/rename a file uploaded/managed with Carrierwave-mongoid in rails 3.2.6. I want to rename the file in the db as well as on the filesystem.
Something like this maybe...
def rename( id , new_name )
f = UploadedFile.find(id)
if f.update_attributes({ f.file.original_filename: new_name }) # this is WRONG, what is right???
new_path = File.join( File.dirname( f.file.current_path ) , new_name ))
FileUtils.mv( f.file.current_path , new_path )
end
return f
end
Let me add this is after it has been uploaded already.
I was able to get the following working, although I'm sure there is a more elegant way. I'd appreciate any comments on the following
*add this to app/uploaders/file_uploader.rb
def rename(new_name)
sf = model.file.file
new_path = File.join( File.dirname( sf.file ) , "#{new_name}#{File.extname( sf.file )}")
new_sf = CarrierWave::SanitizedFile.new sf.move_to(new_path)
model.file.cache!(new_sf)
model.save!
return model
end
Thanks!
The most efficient way to do this is to just move the existing S3 object (assuming your storage layer is S3):
def rename(new_name)
bucket_name = "yourapp-#{Rails.env}"
resource = Aws::S3::Resource.new
bucket = resource.bucket(bucket_name)
object = bucket.object(path)
new_filename = "#{new_name}#{File.extname(path)}"
new_path = File.join(File.dirname(path), new_filename)
object.move_to(bucket: bucket_name, key: new_path)
model.update_column(mounted_as, new_filename)
model.reload
# Now call `recreate_versions!(*versions.keys)`
# if you want versions updated. Explicitly passing
# versions will prevent the base version getting
# reuploaded.
model
end
This is using the aws-sdk-s3 gem.
I store image files -- and derivative versions -- in an S3-compatible solution. I use Carrierwave (1.2.2) with the "fog-aws" gem (3.0.0) on Rails 5.1. The following public method works for me when added to the "uploader" file (eg, app/uploaders/example_uploader.rb):
class ExampleUploader < CarrierWave::Uploader::Base
<snip>
# Renames original file and versions to match given filename
#
# Options:
# * +:keep_original+ - Do not remove original file and versions (ie, copy only)
def rename(new_filename, options = {})
return if !file || new_filename == file.filename
target = File.join(store_path, new_filename)
file.copy_to(target)
versions.keys.each do |k|
target = File.join(store_path, "#{k}_#{new_filename}")
version = send(k).file
version.copy_to(target)
end
remove! unless options[:keep_original]
model.update_column(mounted_as, new_filename) && model.reload
end
<snip>
end
I used this rake task for reprocessing uploaded images after modifying version settings (filename and image size) in my uploader file:
# Usage: rake carrierwave:reprocess class=Model
namespace :carrierwave do
task :reprocess => :environment do
CLASS = ENV['class'].capitalize
MODEL = Kernel.const_get(CLASS)
records = MODEL.all
records.each do |record|
record.photo.recreate_versions! if record.photo?
end
end
end
Notes:
Replace "photo" with whatever you named your uploader.
Rake tasks go in the lib/tasks folder.
This is using Active Record, not sure if Mongoid needs something
different.
Based on #user892583, I worked on it and came up with a simpler solution:
def rename!(new_name)
new_path = File.join(File.dirname(file.file), new_name)
file.move_to(new_path)
end
I did this with this way:
def filename
if !cached? && file.present?
new_filename = 'foobar'
new_path = File.join(File.dirname(file.path), new_filename)
file.move_to(new_path)
recreate_versions!
new_filename
else
super
end
end
I think this is only right way to rename file.

Rails 2.3.8 Render a html as pdf

I am using Prawn pdf library however i am doing a complex design, So I need a quick solution as to convert a html to pdf file.
Thanks in advance
I would use wkhtmltopdf shell tool
together with the wicked_pdf ruby gem, its free, and uses qtwebkit to render your html to pdf. Also executes javascript as well, for charts for example. You can find more info about installation: https://github.com/mileszs/wicked_pdf
I have one Rails app that's been using PrinceXML in production for a few years. It is pricey - around $4K for a server license - but does a very good job of rendering HTML+CSS in PDF files. I haven't looked at newer solutions since this one is paid for and working quite well.
For what it's worth, here's some code I adapted from Subimage Interactive to make the conversion simple:
lib/prince.rb
# Prince XML Ruby interface.
# http://www.princexml.com
#
# Library by Subimage Interactive - http://www.subimage.com
#
#
# USAGE
# -----------------------------------------------------------------------------
# prince = Prince.new()
# html_string = render_to_string(:template => 'some_document')
# send_data(
# prince.pdf_from_string(html_string),
# :filename => 'some_document.pdf'
# :type => 'application/pdf'
# )
#
class Prince
attr_accessor :exe_path, :style_sheets, :log_file
# Initialize method
#
def initialize()
# Finds where the application lives, so we can call it.
#exe_path = '/usr/local/bin/prince'
case Rails.env
when 'production', 'staging'
# use default hard-coded path
else
if File.exist?(#exe_path)
# use default hard-coded path
else
#exe_path = `which prince`.chomp
end
end
#style_sheets = ''
#log_file = "#{::Rails.root}/log/prince.log"
end
# Sets stylesheets...
# Can pass in multiple paths for css files.
#
def add_style_sheets(*sheets)
for sheet in sheets do
#style_sheets << " -s #{sheet} "
end
end
# Returns fully formed executable path with any command line switches
# we've set based on our variables.
#
def exe_path
# Add any standard cmd line arguments we need to pass
#exe_path << " --input=html --server --log=#{#log_file} "
#exe_path << #style_sheets
return #exe_path
end
# Makes a pdf from a passed in string.
#
# Returns PDF as a stream, so we can use send_data to shoot
# it down the pipe using Rails.
#
def pdf_from_string(string)
path = self.exe_path()
# Don't spew errors to the standard out...and set up to take IO
# as input and output
path << ' --silent - -o -'
# Show the command used...
#logger.info "\n\nPRINCE XML PDF COMMAND"
#logger.info path
#logger.info ''
# Actually call the prince command, and pass the entire data stream back.
pdf = IO.popen(path, "w+")
pdf.puts(string)
pdf.close_write
output = pdf.gets(nil)
pdf.close_read
return output
end
end
lib/pdf_helper.rb
module PdfHelper
require 'prince'
private
def make_pdf(template_path, pdf_name, stylesheets = [], skip_base_pdf_stylesheet = false)
# application notices should never be included in PDFs, pull them from session here
notices = nil
if !flash.now[:notice].nil?
notices = flash.now[:notice]
flash.now[:notice] = nil
end
if !flash[:notice].nil?
notices = '' if notices.nil?
notices << flash[:notice]
flash[:notice] = nil
end
prince = Prince.new()
# Sets style sheets on PDF renderer.
stylesheet_base = "#{::Rails.root}/public/stylesheets"
prince.add_style_sheets(
"#{stylesheet_base}/application.css",
"#{stylesheet_base}/print.css"
)
prince.add_style_sheets("#{stylesheet_base}/pdf.css") unless skip_base_pdf_stylesheet
if 0 < stylesheets.size
stylesheets.each { |s| prince.add_style_sheets("#{stylesheet_base}/#{s}.css") }
end
# Set RAILS_ASSET_ID to blank string or rails appends some time after
# to prevent file caching, messing up local - disk requests.
ENV['RAILS_ASSET_ID'] = ''
html_string = render_to_string(:template => template_path, :layout => 'application')
# Make all paths relative, on disk paths...
html_string.gsub!("src=\"", "src=\"#{::Rails.root}/public")
html_string.gsub!("src=\"#{::Rails.root}/public#{::Rails.root}", "src=\"#{::Rails.root}")
# re-insert any application notices into the session
if !notices.nil?
flash[:notice] = notices
end
return prince.pdf_from_string(html_string)
end
def make_and_send_pdf(template_path, pdf_name, stylesheets = [], skip_base_pdf_stylesheet = false)
send_data(
make_pdf(template_path, pdf_name, stylesheets, skip_base_pdf_stylesheet),
:filename => pdf_name,
:type => 'application/pdf'
)
end
end
sample controller action
include PdfHelper
def pdf
index
make_and_send_pdf '/ads/index', "#{filename}.pdf"
end
You can directly convert your existing HTML to PDF using acts_as_flying_saucer library.For header and footer you can refer
https://github.com/amardaxini/acts_as_flying_saucer/wiki/PDF-Header-Footer

Resources