How to generate a pdf to save it in activestorage with wicked_pdf in rails 5? - ruby-on-rails-5

With rails 5.2.4, I am trying to generate a pdf in the background (within a job) and then attach it to a model as:
pdf_contents = ApplicationController.render(
pdf: "name",
template: 'mytemplate.html.erb',
layout: 'pdf_layout.html',
disposition: 'attachment'
)
#user.attach(io: StringIO.new(pdf_contents), filename: "file.pdf", content_type: "application/pdf")
but I am getting :
WARN: ActionView::Template::Error: private method `format' called for nil:NilClass
using
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'

Below solution works for my case.
# frozen_string_literal: true
class OrderPdfJob < ApplicationJob
queue_as :default
def perform(order_id)
order = Order.find(order_id)
# include helpers
action_controller = ActionController::Base.new
action_controller.view_context_class.include(ActionView::Helpers, ApplicationHelper)
pdf = WickedPdf.new.pdf_from_string(
action_controller.render_to_string(
"order_pdf/summary", layout: 'pdf', locals: { order: order }
)
)
# in your order model should have 'has_one_attached :summary'
order.summary.attach(
io: StringIO.new(pdf),
filename: "#{order.id}.pdf",
content_type: 'application/pdf'
)
order.save!
end
end

Related

Accessing object elements in sidekiq task for wicked_pdf

I'm trying to add a custom name to the file based on the object it's called for in WickedPDF. This worked fine as a controller action, but once it's in a Sidekiq worker, it fails saying no method name for the Issueable (polymorphic) object, which is returned as a string (and that is likely the cause of all my issues right there, but I've no idea how to get around it).
the line in question:
save_path = Rails.root.join('public', "#{#issueable.name}_checklist.pdf")
The precise error in context from the Sidekiq console output:
2019-01-15T02:36:46.194Z 10080 TID-oxwns2nmk GenerateChecklistWorker JID-cefb15a101c80e1c8727b646 INFO: start
"***************[\"/Users/jathayde/.rbenv/versions/2.5.2/bin/wkhtmltopdf\", \"-q\", \"--dpi\", \"144\", \"--margin-top\", \"30\", \"--margin-bottom\", \"20\", \"--margin-left\", \"10\", \"--margin-right\", \"10\", \"--header-spacing\", \"5\", \"--footer-spacing\", \"0\", \"file:////var/folders/1b/9267f2s126q7c_8dztswqps40000gn/T/wicked_pdf20190114-10080-40sij4.html\", \"/var/folders/1b/9267f2s126q7c_8dztswqps40000gn/T/wicked_pdf_generated_file20190114-10080-kekkfo.pdf\"]***************"
2019-01-15T02:36:58.263Z 10080 TID-oxwns2nmk GenerateChecklistWorker JID-cefb15a101c80e1c8727b646 INFO: fail: 12.069 sec
2019-01-15T02:36:58.268Z 10080 TID-oxwns2nmk WARN: {"context":"Job raised exception","job":{"class":"GenerateChecklistWorker","args":["#<Lodge:0x00007fcdb8cc86b0>",["#<Issue:0x00007fcdc267ea70>","#<Issue:0x00007fcdc26c0a88>"],"#<Issue::ActiveRecord_AssociationRelation:0x00007fcdc22e1c28>"],"retry":false,"queue":"default","jid":"cefb15a101c80e1c8727b646","created_at":1547519806.182058,"enqueued_at":1547519806.18223},"jobstr":"{\"class\":\"GenerateChecklistWorker\",\"args\":[\"#<Lodge:0x00007fcdb8cc86b0>\",[\"#<Issue:0x00007fcdc267ea70>\",\"#<Issue:0x00007fcdc26c0a88>\"],\"#<Issue::ActiveRecord_AssociationRelation:0x00007fcdc22e1c28>\"],\"retry\":false,\"queue\":\"default\",\"jid\":\"cefb15a101c80e1c8727b646\",\"created_at\":1547519806.182058,\"enqueued_at\":1547519806.18223}"}
2019-01-15T02:36:58.268Z 10080 TID-oxwns2nmk WARN: NoMethodError: undefined method `name' for "#<Lodge:0x00007fcdb8cc86b0>":String
2019-01-15T02:36:58.268Z 10080 TID-oxwns2nmk WARN: /Users/jathayde/Development/Meticulous/Products/patchvault/app/workers/generate_checklist_worker.rb:30:in `perform'
Here's the full generate_checklist_worker.rb:
class GenerateChecklistWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform(issueable, issues, event_issues)
#issueable = issueable
av = ActionView::Base.new()
av.view_paths = ActionController::Base.view_paths
av.class_eval do
include Rails.application.routes.url_helpers
include ApplicationHelper
end
pdf = av.render template: 'issues/checklist.pdf.erb',
locals: {:issueable => #issueable, :issues => #issues, :event_issues => #event_issues}
pdf = WickedPdf.new.pdf_from_string(
pdf,
disposition: 'attachment',
dpi: '144',
header: {html: {template: 'layouts/_checklist_header'}, spacing: 5 },
footer: {html: {template: 'layouts/_checklist_footer'}, spacing: 0 },
margin: {top: 30, bottom: 20, left: 10, right: 10},
locals: {:issueable => #issueable},
#cover: ApplicationController.new.render_to_string('issues/checklist_cover.pdf.erb'), locals: {:issueable => issueable}
)
save_path = Rails.root.join('public', "#{#issueable.name}_checklist.pdf")
File.open(save_path, 'wb') do |file|
file << pdf
end
end
end
Here's the method in the controller that calls this:
def checklist
#issues = #issueable.issues.kept.non_event_issues.ordered_issues
#event_issues = #issueable.issues.kept.event_issues.order(issue_number: :asc)
GenerateChecklistWorker.perform_async(#issueable, #issues, #event_issues)
redirect_to #issueable
end
You cannot serialize complex Ruby objects as Sidekiq arguments. You'll need to look up the thing (i.e. by calling find) in the job.
https://github.com/mperham/sidekiq/wiki/Best-Practices#1-make-your-job-parameters-small-and-simple

RAILS 5: Browse Server CKEditor error 'undefined method `path'' after upload a picture

I just make image upload in ckeditor including carrierwave and cloudinary
I've tried once and it works well, I can browse image on my laptop and upload to cloudinary via image uploader in ckeditor. but when I try to edit or create new article and try to upload a picture, image uploader doesn't work anymore. I don't know why, I didn't edit anything after successful make the image uploader. anyone can help me? thx anyway
error that comes out like this
here's my model/ckeditor/asset.rb
class Ckeditor::Asset < ActiveRecord::Base
include Ckeditor::Orm::ActiveRecord::AssetBase
delegate :url, :current_path, :content_type, to: :data
validates :data, presence: true
end
model/ckeditor/picture.rb
class Ckeditor::Picture < Ckeditor::Asset
mount_uploader :data, CkeditorPictureUploader, mount_on: :data_file_name
def url_content
url(:content)
end
end
model/ckeditor/attachment_file.rb
class Ckeditor::AttachmentFile < Ckeditor::Asset
mount_uploader :data, CkeditorAttachmentFileUploader, mount_on: :data_file_name
def url_thumb
#url_thumb ||= Ckeditor::Utils.filethumb(filename)
end
end
uploader/ckeditor/ckeditor_picture_uploader.rb
class CkeditorPictureUploader < CarrierWave::Uploader::Base
include Ckeditor::Backend::CarrierWave
include Cloudinary::CarrierWave
include CarrierWave::MiniMagick
version :thumb do
process resize_to_fill: [118, 100]
end
version :content do
process resize_to_limit: [800, 800]
end
def extension_white_list
Ckeditor.image_file_types
end
end
config/initializers/ckeditor.rb
Ckeditor.setup do |config|
# ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default), :mongo_mapper and
# :mongoid (bson_ext recommended) by default. Other ORMs may be
# available as additional gems.
require "ckeditor/orm/active_record"
config.picture_model { Ckeditor::Picture }
# config.attachment_file_model { Ckeditor::AttachmentFile }
Rails.application.config.assets.precompile += %w( ckeditor/filebrowser/images/gal_del.png )
config.cdn_url = "//cdn.ckeditor.com/4.5.6/standard/ckeditor.js"
config.js_config_url = "/assets/ckeditor/config.js"
end
EDIT
here's my ckeditor js
ckeditor/config.js
CKEDITOR.editorConfig = function( config )
{
config.enterMode = 2
config.filebrowserBrowseUrl = "/ckeditor/attachment_files";
config.filebrowserFlashBrowseUrl = "/ckeditor/attachment_files";
config.filebrowserFlashUploadUrl = "/ckeditor/attachment_files";
config.filebrowserImageBrowseLinkUrl = "/ckeditor/pictures";
config.filebrowserImageBrowseUrl = "/ckeditor/pictures";
config.filebrowserImageUploadUrl = "/ckeditor/pictures";
config.filebrowserUploadUrl = "/ckeditor/attachment_files";
config.toolbar_Pure = [
'/', {
name: 'basicstyles',
items: ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']
}, {
name: 'paragraph',
items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl']
}, {
name: 'links',
items: ['Link', 'Unlink']
}, '/', {
name: 'styles',
items: ['Styles', 'Format', 'Font', 'FontSize']
}, {
name: 'colors',
items: ['TextColor', 'BGColor']
}, {
name: 'insert',
items: ['Image', 'Table', 'HorizontalRule', 'PageBreak']
}
];
config.allowedContent = true;
config.toolbar = 'Pure';
return true;
}
[:extract_content_type, :set_size, :read_dimensions].each do |method|
define_method :"#{method}_with_cloudinary" do
send(:"#{method}_without_cloudinary") if self.file.is_a?(CarrierWave::SanitizedFile)
{}
end
alias_method_chain method, :cloudinary
end
check CKEditor Carrierwave Cloudinary

Can't test engine routes under Rails 3.2 + Rspec 2.13

In my engine 'Utilizer', I'm trying to test routes with Rspec 3.2.
Namespace are isolated
# lib/utilizer/engine.rb
module Utilizer
class Engine < ::Rails::Engine
isolate_namespace Utilizer
...
end
end
The engine is mounted to the dummy app:
# spec/dummy/config/routes.rb
Rails.application.routes.draw do
mount Utilizer::Engine => "/utilizer", as: 'utilizer'
end
To the spec_helper.rb I've added a couple of configures as below (from here):
# spec/spec_helper.rb
RSpec.configure do |config|
...
config.before(:each, type: :routing) { #routes = Utilizer::Engine.routes }
...
end
When I defined a route:
# config/routes.rb
Utilizer::Engine.routes.draw do
resources :auths, id: /\d+/, only: [:destroy]
end
Rake shows it properly for the dummy app:
$ spec/dummy > bundle exec rake routes
$ utilizer /utilizer Utilizer::Engine
$ Routes for Utilizer::Engine:
$ auth DELETE /auths/:id(.:format) utilizer/auths#destroy {:id=>/\d+/}
BUT both Rspec tests
# spec/routing/auths_routing_spec.rb
require 'spec_helper'
describe "Auths page routing" do
let!(:auth) { create(:utilizer_auth) } # factory is working properly by itself
describe "#destroy" do
let!(:action) { { controller: "utilizer/auths", action: "destroy", id: auth.id } }
specify { { delete: "/auths/#{ auth.id }" }.should route_to(action) }
specify { { delete: auth_path(auth) }.should route_to(action) }
end
end
fail with errors (for the first and second tests correspondingly):
No route matches "/auths/1"
No route matches "/utilizer/auths/1"
But, Holmes, why?
Since RSpec 2.14 you can use the following:
describe "Auths page routing" do
routes { Utilizer::Engine.routes }
# ...
end
Source: https://github.com/rspec/rspec-rails/pull/668
I've fount a solution in Exoth's comment at the end of this discussion on Github (thanks to Brandan).
In my spec_helper instead of
config.before(:each, type: :routing) { #routes = Utilizer::Engine.routes }
I use
config.before(:each, type: :routing) do
#routes = Utilizer::Engine.routes
assertion_instance.instance_variable_set(:#routes, Utilizer::Engine.routes)
end
and it works.

Paperclip and Phusion Passenger NoHandlerError

I followed this guide to get drag and drop file uploads through AJAX: http://dannemanne.com/posts/drag-n-drop_upload_that_works_with_ror_and_paperclip
Everything was working fine on my development environment with WebBrick but if I deploy to PhusionPassenger then I get:
Paperclip::AdapterRegistry::NoHandlerError (No handler found for #<PhusionPassenger::Utils::RewindableInput:0x000000041aef38 #io=#<PhusionPassen...
I'm using this in my controller:
before_filter :parse_raw_upload, :only => :bulk_submissions
def bulk_submissions
...
#submission = Submission.create!(url: "", file: #raw_file, description: "Please edit this description", work_type: "other", date_completed: DateTime.now.to_date)
...
end
private
def parse_raw_upload
if env['HTTP_X_FILE_UPLOAD'] == 'true'
#raw_file = env['rack.input']
#raw_file.class.class_eval { attr_accessor :original_filename, :content_type }
#raw_file.original_filename = env['HTTP_X_FILE_NAME']
#raw_file.content_type = env['HTTP_X_MIME_TYPE']
end
end
Looking at the request itself all the headers are set (X_MIME_TYPE, X_FILE_NAME) etc.
Any ideas?
Thanks in advance!
The example you're cribbing from expects the file stream to be a StringIO object, but Passenger is giving you a PhusionPassenger::Utils::RewindableInput object instead.
Fortunately, a RewindableInput is duckalike to StringIO for this case, so Paperclip's StringioAdapter can be used to wrap your upload stream.
Inside the if block in your parse_raw_upload, at the end, do:
if #raw_file.class.name == 'PhusionPassenger::Utils::RewindableInput'
#raw_file = Paperclip::StringioAdapter.new(#raw_file)
end

Paperclip- validate pdfs with content_type='application/octet-stream'

I was using paperclip for file upload. with validations as below:
validates_attachment_content_type :upload, :content_type=>['application/pdf'],
:if => Proc.new { |module_file| !module_file.upload_file_name.blank? },
:message => "must be in '.pdf' format"
But, my client complained today that he is not able to upload pdf. After investigating I come to know from request headers is that the file being submitted had content_type=application/octet-stream.
Allowing application/octet-stream will allow many type of files for upload.
Please suggest a solution to deal with this.
Seems like paperclip doesn't detect content type correctly. Here is how I was able to fix it using custom content-type detection and validation (code in model):
VALID_CONTENT_TYPES = ["application/zip", "application/x-zip", "application/x-zip-compressed", "application/pdf", "application/x-pdf"]
before_validation(:on => :create) do |file|
if file.media_content_type == 'application/octet-stream'
mime_type = MIME::Types.type_for(file.media_file_name)
file.media_content_type = mime_type.first.content_type if mime_type.first
end
end
validate :attachment_content_type
def attachment_content_type
errors.add(:media, "type is not allowed") unless VALID_CONTENT_TYPES.include?(self.media_content_type)
end
Based on the above, here's what I ended up with which is compatible with PaperClip 4.2 and Rails 4:
before_post_process on: :create do
if media_content_type == 'application/octet-stream'
mime_type = MIME::Types.type_for(media_file_name)
self.media_content_type = mime_type.first.to_s if mime_type.first
end
end
For paperclip 3.3 and Rails 3, I did this a bit differently
before_validation on: :create do
if media_content_type == 'application/octet-stream'
mime_type = MIME::Types.type_for(media_file_name)
self.media_content_type = mime_type.first if mime_type.first
end
end
validates_attachment :media, content_type: { content_type: VALID_CONTENT_TYPES }
By the way, i needed to do this because testing with Capybara and phantom js using attach_file did not generate the correct mime type for some files.