paperclip run processors on selected style - ruby-on-rails

I have an :xxx image processor, and I have two styles in the model :big and :thumb.
How I can process with :xxx only the :thumb image leaving the :big image untouched ?

I recently had a similar problem and found this solution on a message board. Hope it helps!
has_attached_file :screenshot,
:default_style => :full,
:styles => {
:full => "280x210",
:cropped => { :processors => [:screenshot_crop] }
}

By default, the Rake task refreshes all thumbnails. Keep in mind that it won't touch / process the original image.
You could have a look at the Rakefile and the Attachment class and modify to allow you to specify a specific thumbnail size, but the current design assumes that you want to take the original and redo all thumbnails from the original.

Add this code to your paperclip.rake file:
desc "Reprocesses your attachments style (set CLASS, ATTACHMENT and STYLE)"
task :style => :environment do
module JustForOneDay
NAME = ENV['STYLE']
end
module ::Paperclip
class Attachment
def post_process_styles #:nodoc:
#styles.each do |name, args|
if JustForOneDay::NAME == name
begin
raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
#queued_for_write[name] = args[:processors].inject(#queued_for_write[:original]) do |file, processor|
Paperclip.processor(processor).make(file, args, self)
end
rescue PaperclipError => e
log("An error was received while processing: #{e.inspect}")
(#errors[:processing] ||= []) << e.message if #whiny
end
end
end
end
end
end
for_all_attachments do |instance, name|
result = instance.send(name).reprocess!
end
end
end
Tested with Paperclip 2.3.1.1
In Paperclip 2.3.3 this should be:
def post_process_styles #:nodoc:
styles.each do |name, style|
if JustForOneDay::NAME == name
begin
raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
#queued_for_write[name] = style.processors.inject(#queued_for_write[:original]) do |file, processor|
Paperclip.processor(processor).make(file, style.processor_options, self)
end
rescue PaperclipError => e
log("An error was received while processing: #{e.inspect}")
(#errors[:processing] ||= []) << e.message if #whiny
end
end
end
end
It's easy, just go to attachment.rb file in your paperclip version.

I kludged this--it's not elegant, but it worked for me.
One of your styles should have dimensions different from all the other styles. In this way, in your custom Paperclip Processor, you can see if the contents of the command string contain the given dimensions. If so you would do special processing, if not, you would not.
(I clipped this code -- and modified it -- from Ryan Bate's Railscast Episode 182.)
module Paperclip
class Cropper < Thumbnail
def transformation_command
SPECIAL_PROCESSING_FLAG = "150x150"
if crop_command && super.include?(SPECIAL_PROCESSING_FLAG)
crop_command + super.sub(/ -crop \S+/, '')
else
super 'do nothing fancy
end
end
def crop_command
target = #attachment.instance
if target.cropping?
" -crop '#{target.crop_w.to_i}x#{target.crop_h.to_i}+#{target.crop_x.to_i}+#{target.crop_y.to_i}'"
end
end
end
end
In my situation it didn't matter that we reprocessed in the non-special case too, since the end result changed nothing.

Related

How to crop image manually with paperclip?

I'm building a website with Rails 4.2 and Mongoid. I'm using mongoid-paperclip, and I'm trying to crop an image down to a square while preserving the dimensions of the short side (so the image will fill the full square). Here's my custom processor:
module Paperclip
class Cropper < Thumbnail
def initialize(file, options = {}, attachment = nil)
super
#preserved_size = [#current_geometry.width, #current_geometry.height].min
#current_geometry.width = #preserved_size
#current_geometry.height = #preserved_size
end
def target
#attachment.instance
end
def transformation_command
if crop_command
crop_command + super.join(' ').sub(/ -crop \S+/, '').split(' ')
else
super
end
end
def crop_command
["-crop", "#{#preserved_size}x#{#preserved_size}+#{#preserved_size}+#{#preserved_size}"]
end
end
end
And the model that it's attached to looks has this line:
has_mongoid_attached_file :image, styles: {square: {processors: [:cropper]}}
But it doesn't seem to work. A version of the image named 'square' is saved, but it's identical to the original. How can I get this to work?
I was able to fix this without using a paperclip processor. In my model, I specified the styles for the image using a lambda:
has_mongoid_attached_file :image, styles: lambda {|a|
tmp = a.queued_for_write[:original]
return {} if tmp.nil?
geometry = Paperclip::Geometry.from_file(tmp)
preserved_size = [geometry.width.to_i, geometry.height.to_i].min
{square: "#{preserved_size}x#{preserved_size}#"}
}
Note that the # at the end of the dimensions ensured that the cropped image was always the specified dimensions, rather than just scaling down the image.

Carrierwave: Process Temp file and then upload via fog

I am processing a pdf uploaded by an user by extracting the text from it and saving the output in an text file for processing later.
Locally I store the pdf in my public folder but when I work on Heroku I need to use S3.
I thought that the pdf path was the problem, so I included
if Rails.env.test? || Rails.env.cucumber?
But still I receive
ArgumentError (input must be an IO-like object or a filename):
Is there a way of temporarily storing the pdf in my root/tmp folder on Heroku, get the text from it, and then after that is done, upload the document to S3?
def convert_pdf
if Rails.env.test? || Rails.env.cucumber?
pdf_dest = File.join(Rails.root, "public", #application.document_url)
else
pdf_dest = #application.document_url
end
txt_file_dest = Rails.root + 'tmp/pdf-parser/text'
document_file_name = /\/uploads\/application\/document\/\d{1,}\/(?<file_name>.*).pdf/.match(#application.document_url)[:file_name]
PDF::Reader.open(pdf_dest) do |reader|
File.open(File.join(txt_file_dest, document_file_name + '.txt'), 'w+') do |f|
reader.pages.each do |page|
f.puts page.text
end
end
end
end
You're going to want to set up a custom processor in your uploader. And on top of that, since the output file (.txt) isn't going to have the same extension as the input file (.pdf), you're going to want to change the filename. The following belongs in your Uploader:
process :convert_to_text
def convert_to_text
temp_dir = Rails.root.join('tmp', 'pdf-parser', 'text')
temp_path = temp_dir.join(filename)
FileUtils.mkdir_p(temp_dir)
PDF::Reader.open(current_path) do |pdf|
File.open(temp_path, 'w') do |f|
pdf.pages.each do |page|
f.puts page.text
end
end
end
File.unlink(current_path)
FileUtils.cp(temp_path, current_path)
end
def filename
super + '.txt' if original_filename.present?
end
I haven't run this code, so there are probably some bugs, but that should give you the idea at least.

Can't generate proper PDF files with PDFtk

I am developing a web app using Ruby on Rails 3. One of the features of the app is to use data from a MySQL database to fill PDF template forms that have been designed in Adobe LiveCycle Designer.
I am using the technique of generating an XFDF file with the data and use it to fill in the actual PDF file. I am using PDFtk to do this and if I run it from my command prompt (Windows 7 64bit) it works fine.
I used code by Greg Lappen at http://bleep.lapcominc.com/2012/02/07/filling-pdf-forms-with-ruby-and-pdftk/ to implement this process in my Rails app, but it does not seem to work
The output PDF cannot be opened in Acrobat as it states the file has been damaged. If I open it using a normal text editor all it contains is #<StringIO:0x5958f30> with the HEX value changing after each output.
The code generating the XML data is correct. I was able to save it to a file and run it through the command prompt myself.
def self.generate_xfdf(fields, filename)
xml = Builder::XmlMarkup.new
xml.instruct!
xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
xml.f :href => filename
xml.fields {
fields.each do |field, value|
xml.field(:name => field) {
if value.is_a? Array
value.each {|item| xml.value(item.to_s) }
else
xml.value(value.to_s)
end
}
end
}
}
xml.target!
end
I suspect the real problem is in either of the two code snippets below. I just started learning Ruby on Rails and I am unable to debug this. I have tried various different methods but no success so far. I would really appreciate any help.
def self.stamp(input_pdf, fields)
stdin, stdout, stderr = Open3.popen3("pdftk #{input_pdf} fill_form - output - flatten")
stdin << generate_xfdf(fields, File.basename(input_pdf))
stdin.close
yield stdout
stdout.close
stderr.close
end
PdfStamper.stamp('C:/clean-it-template.pdf', { 'LastName' => "Test Last Name", 'FirstName' => "Test First Name" }) do |pdf_io|
pdf_content = StringIO.new
pdf_content << pdf_io.read
send_data pdf_content.string, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
end
This is the full code in my controller class
require 'pdf_stamper'
class FormPagesController < ApplicationController
def pdftest
PdfStamper.stamp('C:/clean-it-template.pdf', { 'LastName' => "Test Last Name", 'FirstName' => "Test First Name" }) do |pdf_io|
pdf_content = StringIO.new
pdf_content << pdf_io.read
send_data pdf_content.string, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
end
end
end
This is the full code for the pdf_stamper class I am using
require 'builder'
require 'open3'
class PdfStamper
def self.stamp(input_pdf, fields)
stdin, stdout, stderr = Open3.popen3("pdftk #{input_pdf} fill_form - output - flatten")
stdin << generate_xfdf(fields, File.basename(input_pdf))
stdin.close
yield stdout
stdout.close
stderr.close
end
def self.generate_xfdf(fields, filename)
xml = Builder::XmlMarkup.new
xml.instruct!
xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
xml.f :href => filename
xml.fields {
fields.each do |field, value|
xml.field(:name => field) {
if value.is_a? Array
value.each {|item| xml.value(item.to_s) }
else
xml.value(value.to_s)
end
}
end
}
}
xml.target!
#file = File.new("C:/debug.xml", "w+")
#file.write(xml_data)
#file.close
end
end
UPDATE #1:
I ran the web app on Ubuntu and I still get the same errors. After digging around on the web I changed the code in my controller to this:
def pdftest
PdfStamper.stamp('/home/nikolaos/clean-it-template.pdf', { 'LastName' => "Test Last Name", 'FirstName' => "Test First Name" }) do |pdf_io|
pdf_content = StringIO.new("", 'wb')
pdf_content << pdf_io.read
send_data pdf_content.string, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
end
end
I changed StringIO to be in binary write mode and it works in Ubuntu! The PDF opens correctly with all the fields filled in. I opened the same file on Windows using Acrobat and no problems, BUT if I run the web app on Windows, it still produces damaged PDF files.
Does anyone have any solutions on how to get this working in Windows? I am guessing it has something to do with the way Windows and Linux interpret newlines or something similar to that?
After some more searching through the Ruby documentation I managed to solve my problem. Now my app is able to produce valid PDF files on Windows. Here is my solution for anyone that is experiencing the same problem.
The solution was to use IO instead of StringIO in the controller.
My FormPages controller code
require 'pdf_stamper'
class FormPagesController < ApplicationController
def pdftest
PdfStamper.stamp('C:/clean-it-template.pdf', { 'LastName' => "Bukas", 'FirstName' => "Nikolaos" }) do |pdf_io|
pdf_content = IO.new(pdf_io.to_i, "r+b")
pdf_content.binmode
send_data pdf_content.read, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
end
end
end
The pdf_stamper class in charge of filling and generating the PDF using PDFtk
require 'builder'
require 'open3'
class PdfStamper
def self.stamp(input_pdf, fields)
Open3.popen3("pdftk #{input_pdf} fill_form - output -") do |stdin, stdout, stderr|
stdin << generate_xfdf(fields, File.basename(input_pdf))
stdin.close
yield stdout
stdout.close
stderr.close
end
end
def self.generate_xfdf(fields, filename)
xml = Builder::XmlMarkup.new
xml.instruct!
xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
xml.f :href => filename
xml.fields {
fields.each do |field, value|
xml.field(:name => field) {
if value.is_a? Array
value.each {|item| xml.value(item.to_s) }
else
xml.value(value.to_s)
end
}
end
}
}
xml.target!
end
end

How to speed up loading of Marshal objects in Ruby/Rails

I have a mongoid model/class in my Rails application. It looks like this:
class Operation
include Mongoid::Document
include Mongoid::Timestamps
extend Mongoid::MarshallableField
marshallable_field :message
def load_message
message
end
end
message contains an array of several thousands of elements, so it has been converted into a byte stream with Marshal.
I need to be able to load messagefast, but currently it takes approx. 1,4 seconds to load, e.g. with the load_message method as displayed above.
How could I speed things up?
For your reference, here is my configuration:
## app/lib/mongoid/marshallable_field.rb
module Mongoid
module MarshallableField
def marshallable_field(field_name, params = {})
set_method_name = "#{field_name}=".to_sym
get_method_name = "#{field_name}".to_sym
attr_name = "__#{field_name}_marshallable_path".to_sym
send :define_method, set_method_name do |obj|
if Rails.env == "development" || Rails.env == "test"
path = File.expand_path(Rails.public_path + "/../file_storage/#{Time.now.to_i}-#{id}.class_dump")
elsif Rails.env == "production"
path = "/home/ri/prod/current/file_storage/#{Time.now.to_i}-#{id}.class_dump"
end
f = File.new(path, "w")
Marshal.dump(obj, f)
f.close
update_attribute(attr_name, path)
path
end
send :define_method, get_method_name do
if self[attr_name] != nil
file = File.open(self[attr_name], "r")
begin
Marshal.load(file)
rescue ArgumentError => e
Rails.logger.error "Error unmarshalling a field #{attr_name}: #{e}"
nil
end
else
Rails.logger.error "self[attr_name] is nil"
nil
end
end
end
end
end

Ruby/Rails: Prepend, append code to all methods

I wrote a small benchmarking Class for testing my code doing development. At the moment I have to add the Class to the beginning and end of every method. Is it posible to prepend, append on the fly, so that I don't have to clutter my code?
class ApplicationController
before_filter :init_perf
after_filter :write_perf_results_to_log!
def init_perf
#perf ||= Perf.new
end
def write_perf_results_to_log!
#perf.results
end
end
class Products < ApplicationsController
def foo
#perf.log(__methond__.to_s)
caculation = 5 *4
#perf.write!
end
def bar
#perf.log(__methond__.to_s)
caculation = 1 / 5
#perf.write!
end
end
This is the Perf class. It is located in the services folder.
class Perf
def initialize
#results = []
end
def log(note)
#start = Time.now
#note = note
end
def write!
if #results.find {|h| h[:note] == #note } # Update :sec method exists in results
#results.select { |h| h["note"] == #note; h[":sec"] = (Time.now - #start).round(3) }
else # Add new Hash to results
#results << { :note => #note, :sec => (Time.now - #start).round(3) }
end
end
def results
content = "
PERFORMANCE STATISTICS!
"
#results.each do |r|
content += r[:note] + " " + r[:sec].to_s + "
"
end
content += "
"
Rails.logger.info content
end
end
In general computing terms what you want to do is called code instrumentation. There are several ways to accomplish this, however here's one (crude) example using some metaprogramming:
First define a new method that we will use for injecting our instrumentation code:
class ApplicationController
def self.instrument_methods(*methods)
methods.each { |m|
# Rename original method
self.send(:alias_method, "#{m}_orig", m)
# Redefine old method with instrumentation code added
define_method m do
puts "Perf log #{m}"
self.send "#{m}_orig"
puts "Perf write"
end
}
end
end
How to use it:
class Product < ApplicationController
def foo
puts "Foo"
end
def bar
puts "Bar"
end
# This has to be called last, once the original methods are defined
instrument_methods :foo, :bar
end
Then:
p = Product.new
p.foo
p.bar
Will output:
Perf log foo
Foo
Perf write
Perf log bar
Bar
Perf write
Here are some other ways to instrument ruby code and measure performance:
http://ruby-prof.rubyforge.org/
http://www.igvita.com/2009/06/13/profiling-ruby-with-googles-perftools/
There is better solution.
class ApplicationController
def self.inherited(klass)
def klass.method_added(name)
return if #_not_new
#_not_new = true
original = "original #{name}"
alias_method original, name
define_method(name) do |*args, &block|
puts "==> called #{name} with args: #{args.inspect}"
result = send original, *args, &block
puts "<== result is #{result}"
result
end
#_not_new = false
end
end
end
class Product < ApplicationController
def meth(a1, a2)
a1 + a2
end
end
product = Product.new
puts product.meth(2,3)
And the result:
==> called meth with args: [2, 3]
<== result is 5
5
The source & explanation are here: http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming. I recommend to spend not a big money to get this course.
I'm the author of aspector gem. Thanks to dimuch for mentioning it.
I've come up with a solution using aspector. Here are the high level steps:
Create an aspect as a subclass of Aspector::Base
Inside the aspect, define advices (before/after/around are the primary types of advices)
Apply the aspect on target class (or module/object)
The full code can be found in this gist. Please feel free to let me know if you have questions or the solution doesn't do what you intend to.
class PerfAspect < Aspector::Base
around options[:action_methods] do |proxy|
#perf ||= Perf.new
proxy.call
#perf.results
end
around options[:other_methods], :method_arg => true do |method, proxy, *args, &block|
#perf.log(method)
result = proxy.call *args, &block
#perf.write!
result
end
end
action_methods = [:action]
other_methods = Products.instance_methods(false) - action_methods
PerfAspect.apply(Products, :action_methods => action_methods, :other_methods => other_methods)
Guess aspector gem can help. It's not well documented but has useful examples.

Resources