Rails Mailer multipart + inline images: no html alternative in Apple Mail, TB - ruby-on-rails-3

Using rails 3.0.10, we need to send mail messages with html and plain text alternative where the html part references images sent along as inline attachments.
The code:
def invite(secure_share)
#share = secure_share
attachments.inline['download.png'] = File.read "#{Rails.root}/public/images/download.png"
mail( :to => secure_share.recipient,
:from => 'dummy#example.com',
) do |format|
format.html { render :layout => 'cargo_secureshare_mailer' }
format.text
end
end
The templates are in place, the inline attachment works and we get a mail with the following structure:
Content-type "multipart/related"
Part "multipart/alternative"
Part "text/html"
Part "text/plain"
Part "image/png" (inline attachment)
Neither Apple Mail nor Thunderbird seem to like this, they will not show the HTML version but only a text version with two "normal" attachments. It seems like the multipart structure is wrong - shouldn't it have "multipart/alternative" as the content type and then "multipart/related" as a wrapper for the html with inline attachments?
Anyone a hint how to fix this?

Found here: http://www.shortround.net/2011/04/07/multipart-alternative-multipart-related-email-with-actionmailer-for-rails-3/
It almost works. i.e. it works for regular attchments but fails for inlined

Related

How to update the database when users download an ActiveStorage blob attachment?

Currently users can download an ActiveStorage blob in my app using the following link:
link_to 'download', rails_blob_path(pj.document.file, disposition: 'attachment')
However, I would like to update an attribute in the database for the associated model to register when the file was first downloaded. This field is called the downloaded_at field.
I have made the following attempt:
Changed the link_to > button_to as I'm updating the model.
Added the appropriate route
Added the following code in the database:
def download
#proofreading_job = ProofreadingJob.find(params[:id])
#proofreading_job.update(downloaded_at: Time.current) if current_user == #proofreading_job.proofreader.user
response.headers["Content-Type"] = #proofreading_job.document.file.content_type
response.headers["Content-Disposition"] = "attachment; #{#proofreading_job.document.file.filename.parameters}"
#proofreading_job.document.file.download do |chunk|
response.stream.write(chunk)
end
ensure
response.stream.close
end
However, this does not do anything except redirect to the #proofreading_job page which is not what I want.
Has anyone done this before and if so how can I accomplish this task.
I think you can also try using your action controller as a proxy, the concept is this:
download the file in your action
check if it is downloaded successfully and other validations
perform clean up operations (in your case the added code in your #3)
send the file back to user using the send_data/send_file rendering method
E.g. in your controller:
def download
file = open(params[:uri])
validate!
cleanup!
send_file file.path
end
Then in your view:
link_to 'download', your_controller_path
Above is just concept and I apologize for only providing pseudo code in advance.
In the end I just used some javascript to capture the click of the button as follows:
td = link_to rails_blob_path(pj.document.file, disposition: 'attachment'),
id: pj.document.id,
download: pj.document.file_name,
class: "btn btn-outline-secondary btn-sm btn-download" do
=pj.document.file_name
i.fa.fa-download.ml-3 aria-hidden="true"
coffee script:
$('.btn-download').on 'click', (e) ->
id = $(this).attr('id')
$.ajax {url: Routes.document_path(id), type: 'PUT'}
routes.rb
resources :documents, only: [:show, :update]
documents_controller.rb:
def update
document = Document.find(params[:id])
authorize([:proofreaders, document])
document.update(downloaded_at: Time.current) if document.downloaded_at.nil?
head :ok
end
This seems to work very well. It updates the database and the user gets the file downloaded to their computer.

Accept an image via Rails 5 API and Active Storage

I am trying to send images to my Rails app and then store them via Active Storage.
I tried Base64 and direct upload and researched for hours but nothing really works.
Can somebody point me to a good way?
My last attempt was to use Base64 like so:
def attach_preview
page = Page.first
content = JSON.parse(request.body.read)
decoded_data = Base64.decode64(content["file_content"].force_encoding("UTF-8"))
begin
file = Tempfile.new('test')
file.write decoded_data
#page.thumbnail = file
filename = "foooo"
page.thumbnail.attach(io: File.read(file), filename: filename)
if page.save
render :json => {:message => "Successfully uploaded the profile picture."}
else
render :json => {:message => "Failed to upload image"}
end
ensure
file.close
file.unlink
end
end
But this results in a "\xAB" from ASCII-8BIT to UTF-8 error.
Dont really care if its Base64 or something else, I just need a way :-)
This works, I use IO directly since ActiveStorage needs it anyway.
def attach_thumbnail
content = JSON.parse(request.body.read.force_encoding("UTF-8"))
decoded_data = Base64.decode64(content["file_content"])
io = StringIO.new
io.puts(decoded_data)
io.rewind
#page.thumbnail.attach(io: io, filename: 'base.png')
#page.save
render json: {
success: #page.thumbnail.attached?,
thumbnail_url: url_for(#page.thumbnail),
page: #page
}
end

Passing a CSV file to rspec as a param

I have the following function in my users_controller to upload a csv file from local disk and export the user details in it into my application. The code is working correctly in the application but I am unsure as to how about passing the csv in the test. My function is as follows:
def upload
if request.post?
if (params[:user].nil? || params[:user][:csv].blank?)
flash[:notice] = "Please provide a csv file to upload."; return
end
file = params[:user][:csv].read
CSV.parse(file, headers: true, header_converters: :symbol).each { |row| Cortex::User.create(row.to_hash) }
respond_to do |format|
format.html { redirect_to admin_engine.cortex_users_path }
format.json { head :no_content }
end
else
# Provide upload view
end
end
Here is my attempt at trying to cover this in the rspec tests.
it "should be a success" do
post "admin/users/upload", :user => #user, :user_csv => fixture_file_upload(Admin::Engine.root.join('spec/dummy/tenants_sample.csv')), :session_token => #auth_token
response.code.should eq("200")
end
When I check the LOC coverage with the coverage gem I can see that the test enters the first two if statements before exiting.
Anyone got any tips on how I could go about passing this file in so that it could then be read by the rest of the function.
Cheers
IMHO, testing CSV composition or parsing doesn't belong in your current test. Ultimately, all such a test will do is test either the CSV module or the validity of your CSV file--which is a brittle test and tells you very little about your application.
A better testing practice would be to perform a test of the model to ensure that:
A properly-formatted CSV is imported the way you expect.
Malformed CSV files are handled appropriately for your application.
The controller should just stub or mock the CSV parsing because it's not relevant to the response code test, and the logic really belongs in the model anyway. YMMV.

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.

How can I attach multiple files to an ActionMailer mail object? (Without it attaching several unwanted text files as well.)

I am successfully generating and sending an email with the following code.
class UserMailer < ActionMailer::Base
default :from => 'user#user.com',
:date => Time.now
def new_user(user)
mail_subject = ['WELCOME TO ACME, INC', 'USER ACTIVATION']
#user = user
mail.attachments['File One.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_one.pdf'))
mail.attachments['File Two.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_two.pdf'))
mail.attachments['File Three.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_three.pdf'))
mail.attachments['File Four.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_four'))
mail( :to => user.address.email,
:subject => mail_subject.join(' ~ ').upcase )
end
end
However, the email contains three text documents which are identical to the content of the email body. The view I'm using for the mailer is named new_user.text.erb.
I suspect that for each pdf document I'm attaching, a plain text document is generated as well, the first being the actual email document body and the remaining three are attached along with the pdf documents.
How may I attach these pdf documents without also attaching these (repeating) text documents? Has anyone else run into this?
Try to use attachments['..'] instead of mail.attachments['..], it works here this way, and no duplicates are observed.
I'm on Rails 3.0.7, mail 2.2.19:
The only other difference that I see, is that I've a hash with mime_type and content. But I think that it worked the other way as well, it just was assigning a mime-type which was not adequate.
attachments['event.ics'] = {:mime_type=>'text/calendar', :content => ics}
mail({
:to => email,
:subject => subject,
}) do |format|
format.text { render :inline => mail_template }
...
end
I'm on Rails version 5.2.4.5. Hope it will help:
My reference comes from here 2.3.1 Adding Attachments and Attachments.
If you want to attach multiple files.
Just call this Method repeatedly. And it will send the mail with two file.
attachments["YourFile.csv"] = {mime_type: 'text/csv', content: csv_data}
attachments["YourFile2.csv"] = {mime_type: 'text/csv', content: csv_data}
This is my example that generate CSV and PDF and mail them in same time for your reference:
headers = ['Name', 'Age', 'Party', 'Vote']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
#candidates.each do |people|
csv << [people.name, people.age, people.party, people.vote_logs_count]
end
end
attachments["Report.csv"] = {mime_type: 'text/csv', content: csv_data}
attachments["Report2.pdf"] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "pdf",:template => 'candidates/pdf.html.erb')
)
mail(
from: "SomeOne" + "#SomeHashDomain.mailgun.org",
to: 'Recipient#Domain.com',
subject: "CSV and PDF report"
)
Supplementary note:
I use WickedPdf Gem to generate PDF.