Attach active storage file to mailer - ruby-on-rails

I have tried everything and still can't get this to work.
I have two types of application in my system that are pre-qualified and sent to lenders,
1) one generates a pdf
2) second should use active storage attachments and attach them to an ActionMailer
First one is working the second is giving me the following error:
[ActionMailer::DeliveryJob] [905177a5-b0e9-46f4-ba9a-fc4630e873f9]
Error performing ActionMailer::DeliveryJob (Job ID:
905177a5-b0e9-46f4-ba9a-fc4630e873f9) from Async(mailers) in 140.14ms:
Errno::ENOENT (No such file or directory # rb_sysopen -
https://funderhunt.co/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZ1lIIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--fa91a15681c23d47d767169c7821601aa15ed2b3/Statuses.pages?disposition=attachment):
The link is correct tho:
My mailer code for this part looks like this:
q = 0
statement.files.each do |file|
q += 1
bank_statement = File.read(rails_blob_url(file, disposition: "attachment"))
attachments["statement_#{q}.pdf"] = { :mime_type => 'application/pdf', :content => bank_statement }
end
What is wrong? Can you please help. Thanks in advance.

If somebody lands here looking for a general solution:
modelname.attachments.each do |file|
attachments[file.blob.filename.to_s] = {
mime_type: file.blob.content_type,
content: file.blob.download
}
end

You should be able to do something like,
statement.files.each_with_index do |file, q|
attachments["statement_#{q + 1}.pdf"] = { mime_type: 'application/pdf', content: file.blob.download }
end
file.blob.download will return the content of the file, similar to File.read.

Related

Temp files disappears when CarrierWave processing

I created an ActiveJob to process my carrier waves uploads. However, when I upload more than one image, I get the following error for the second file:
Errno::ENOENT (No such file or directory # rb_sysopen - C:/Users/tdavi/AppData/Local/Temp/RackMultipart20180830-392-z2s2i.jpg)
Here's the code in my controller:
if #post.save
files = params[:post_attachments].map { |p|
{image: p['photo'][:image].tempfile.path, description: p['photo'][:decription]}
}
ProcessPhotosJob.perform_later(#post.id, files.to_json)
format.html { render :waiting }
end
And my ActiveJob
require 'json'
class ProcessPhotosJob < ApplicationJob
queue_as :default
def perform(post_id, photos_json)
post = Post.friendly.find(post_id)
photos = JSON.parse photos_json
photos.each do |p|
src_file = File.new(p['image'])
post.post_attachments.create!(:photo => src_file, :description => p[:description])
end
post.processed = true
post.save
end
end
When I upload only one file to upload, it works okay.
You should not pass Tempfile to the queued jobs.
First of all - TempFiles can be deleted automatically by Ruby (docs, explanation)
If you would like to upload file(s) and process them later (in a background), then I would suggest you check this question.

Email download gets missing file error

I have some code that runs every week using Whenever gem. It creates a file and sends a download link.
ApplicationMailer
def weekly_email
file = CSVData::Report.new(time).create_csv
#filename = File.basename(file.path)
mail(
:from => "from",
:to => "to",
:subject => "Data #{time.strftime("%m/%d/%Y")}"
)
end
The file is stored in downloads/. The issue is, it works in testing, it works sometimes, but sometimes it generate a Missing File error:
Error:
ActionController::MissingFile occurred in file_downloads#download:
Cannot read file downloads/data_9_17_2015.csv
actionpack (3.2.13) lib/action_controller/metal/data_streaming.rb:71:in `send_file'
Here is the download code:
def download
send_file "downloads/#{params[:filename]}.csv", type: "application/csv", x_sendfile: true
end
Here is a shortened version of the file creation:
def create_file
file_data = some_cool_data
file = create_file(File.join(Dir.pwd, "/downloads/#{#file_name}.csv"))
file.write(file_data)
file.close
file
end
def create_file(path)
FileUtils.mkdir_p(File.dirname(path))
File.new(path, "w+")
end
I dont think its an issue with the file reference (i.e. /dowloads vs downloads), because it works fine in testing and sometimes in production. It just seems that the file is getting deleted. What could be causing the missing file error?

winmail.dat attachment gets corrupted using ActionMailer in Rails app

I am using ActionMailer in a Ruby on Rails app to read emails (ruby 1.9.3, rails 3.2.13).
I have an email that has a winmail.dat file attached to it (ms-tnef) and I am using the tnef gem to extract its contents.
The problem is that when I read the attachment from the mail, it gets corrupted and tnef can not extract files from it.
$ tnef winmail.dat
ERROR: invalid checksum, input file may be corrupted
Extracting the winmail.dat attachment using any mail app, the extracted winmail.dat works fine with tnef and I got it's content.
Comparing the two files I noticed that:
- original file is bigger (76k against 72k)
- they differ on line breaks: Orginal file has the windows format (0D 0A) and the file saved by rails has the linux format (0A)
I wrote this test:
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
That fails with these messages:
WARNING: invalid checksum, input file may be corrupted
Invalid RTF CRC, input file may be corrupted
WARNING: invalid checksum, input file may be corrupted
Assertion failed: ((attr->lvl_type == LVL_MESSAGE) || (attr->lvl_type == LVL_ATTACHMENT)), function attr_read, file attr.c, line 240.
Errno::EPIPE: Broken pipe
anexo = message.attachments[0]
=> #<Mail::Part:2159872060, Multipart: false, Headers: <Content-Type: application/ms-tnef; name="winmail.dat">, <Content-Transfer-Encoding: quoted-printable>, <Content-Disposition: attachment; filename="winmail.dat">>
I tried to save it to disk as bynary, and read it again, but I got the same result
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
tmpfile_name = "#{::Rails.root}/tmp/#{anexo.filename}"
File.open(tmpfile_name, 'w+b', 0644) { |f| f.write anexo.body.decoded }
anexo = File.open(tmpfile_name)
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
How should I read the attachment?
The method anexo.body.decoded calls the decode method of the best suited encoding (Mail::Encodings) for the attachment, in your case quoted_printable.
Some of these encodings (7bit, 8bit and quoted_printable), perform a conversion, changing different types of line breaks to the platform specific line break.
the *quoted_printable" call .to_lf that corrupt the winmail.dat file
# Decode the string from Quoted-Printable. Cope with hard line breaks
# that were incorrectly encoded as hex instead of literal CRLF.
def self.decode(str)
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first.to_lf
end
mail/core_extensions/string.rb:
def to_lf
to_str.gsub(/\n|\r\n|\r/) { "\n" }
end
To solve it you have perform the same encoding without the last .to_lf.
To do that you can create a new encoding that does not corrupt your file and use it to encode you attachment.
create the file:
lib/encodings/tnef_encoding.rb
require 'mail/encodings/7bit'
module Mail
module Encodings
# Encoding to handle Microsoft TNEF format
# It's pretty similar to quoted_printable, except for the 'to_lf' (decode) and 'to_crlf' (encode)
class TnefEncoding < SevenBit
NAME='tnef'
PRIORITY = 2
def self.can_encode?(str)
EightBit.can_encode? str
end
def self.decode(str)
# **difference here** removed '.to_lf'
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
end
def self.encode(str)
# **difference here** removed '.to_crlf'
[str.to_lf].pack("M")
end
def self.cost(str)
# These bytes probably do not need encoding
c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
# Everything else turns into =XX where XX is a
# two digit hex number (taking 3 bytes)
total = (str.bytesize - c)*3 + c
total.to_f/str.bytesize
end
private
Encodings.register(NAME, self)
end
end
end
To use your custom encoding you must, first, register it:
Mail::Encodings.register('tnef', Mail::Encodings::TnefEncoding)
And then, set it as your preferred encoding for the attachment:
anexo.body.encoding('tnef')
Your test would, then, become:
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
tmpfile_name = "#{::Rails.root}/tmp/#{anexo.filename}"
Mail::Encodings.register('tnef', Mail::Encodings::TnefEncoding)
anexo.body.encoding('tnef')
File.open(tmpfile_name, 'w+b', 0644) { |f| f.write anexo.body.decoded }
anexo = File.open(tmpfile_name)
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
Hope it helps!

Test download of pdf with rspec and pdfkit

I am developing a rails 3.2 application with which users can download pdfs. I enjoy test driven development a lot using rspec and shoulda matchers, but I'm at a loss with this one.
I have the following code inside my controller:
def show_as_pdf
#client = Client.find(params[:client_id])
#invoice = #client.invoices.find(params[:id])
PDFKit.configure do |config|
config.default_options = {
:footer_font_size => "6",
:encoding => "UTF-8",
:margin_top=>"1in",
:margin_right=>"1in",
:margin_bottom=>"1in",
:margin_left=>"1in"
}
end
pdf = PDFKit.new(render_to_string "invoices/pdf", layout: false)
invoice_stylesheet_path = File.expand_path(File.dirname(__FILE__) + "/../assets/stylesheets/pdfs/invoices.css.scss")
bootstrap_path = File.expand_path(File.dirname(__FILE__) + "../../../vendor/assets/stylesheets/bootstrap.min.css")
pdf.stylesheets << invoice_stylesheet_path
pdf.stylesheets << bootstrap_path
send_data pdf.to_pdf, filename: "#{#invoice.created_at.strftime("%Y-%m-%d")}_#{#client.name.gsub(" ", "_")}_#{#client.company.gsub(" ", "_")}_#{#invoice.number.gsub(" ", "_")}", type: "application/pdf"
return true
end
This is fairly simple code, all it does is configure my PDFKit and download the generated pdf. Now I want to test the whole thing, including:
Assignment of instance variables (easy, of course, and that works)
The sending of data, i.e. the rendering of the pdf => And this is where I'm stuck
I have tried the following:
controller.should_receive(:send_data)
but that gives me
Failure/Error: controller.should_receive(:send_data)
(#<InvoicesController:0x007fd96fa3e580>).send_data(any args)
expected: 1 time
received: 0 times
Does anyone know of a way to test that the pdf is actually downloaded/sent? Also, what more things do you see that should be tested for good test coverage? E.g., testing for the data type, i.e. application/pdf, would be nice.
Thanks!
Not sure why you're getting that failure but you could instead test the response headers:
response_headers["Content-Type"].should == "application/pdf"
response_headers["Content-Disposition"].should == "attachment; filename=\"<invoice_name>.pdf\""
You asked for advice regarding better test coverage. I thought I'd recommend this: https://www.destroyallsoftware.com/screencasts. These screencasts have had a huge impact on my understanding of test-driven development -- highly recommended!
I recommend using the pdf-inspector gem for writing specs for PDF related Rails actions.
Here's an exemplary spec (which assumes the Rails #report action writes data about a Ticket model in the generated PDF):
describe 'GET /report.pdf' do
it 'returns downloadable PDF with the ticket' do
ticket = FactoryGirl.create :ticket
get report_path, format: :pdf
expect(response).to be_successful
analysis = PDF::Inspector::Text.analyze response.body
expect(analysis.strings).to include ticket.state
expect(analysis.strings).to include ticket.title
end
end

How do I test a file upload in rails for integration testing?

I do some integration testing like this :
def user.excel_import
fixture_excel = fixture_file_upload('goodsins.xls', 'text/xls')
post excel_import_goods_ins_goods_ins_path, :dump=> {:excel_file=>fixture_excel}, :html => { :multipart => "true" }
assert_response :redirect
assert_redirected_to goods_ins_path
end
But when I run the testing it is said that : goodsins.xls file does not exist.
FYI : I put the file in the folder that named fixtures.
Any idea? Thx u very much
The notes here: http://apidock.com/rails/ActionController/TestProcess/fixture_file_upload indicate that you need to include a slash before the path or file name.
try fixture_file_upload('/goodsins.xls', 'text/xls') and see if that helps.
fixture_file_upload Source:
# File actionpack/lib/action_controller/test_process.rb, line 523
def fixture_file_upload(path, mime_type = nil, binary = false)
if ActionController::TestCase.respond_to?(:fixture_path)
fixture_path = ActionController::TestCase.send(:fixture_path)
end
ActionController::TestUploadedFile.new("#{fixture_path}#{path}",
mime_type, binary)
end
Update from Question Owner:
Solution:
add include ActionDispatch::TestProcess to test_helper.rb

Resources