How to attach images in mailer from active storage association in Rails - ruby-on-rails-5

In rails 5.2, I have a model using has_many_attached :images. I would like to send out an email that includes all associated images as attachments.
My mailer method currently looks like:
def discrepancy_alert(asset_discrepancy_id, options={})
#asset_discrepancy = AssetDiscrepancy.find asset_discrepancy_id
#asset_discrepancy.images.each_with_index do |img,i|
attachments["img_#{ i }"] = File.read(img)
end
mail to: 'noone#gmail.com', subject: "email subject"
end
obviously, File.read does not work here because img is not a path, it is a blob. I have not been able to find any info on this in the docs
Question One:
Is there a rails method for attaching a blob like this?
I can use the following instead:
#asset_discrepancy.images.each_with_index do |img,i|
attachments["img_#{ i }"] = img.blob.download
end
Question Two:
the download method could use a log of RAM, is this usage ill advised?
It seems, with the addition of ActiveStorage, that rails mailers would have some new methods for interaction between the two....I have not seen anything in the docs. All the mailer attachments[] examples use paths to a local file.

in app/mailers/mailer.rb:
if #content.image.attached?
#filename = object.id.to_s + object.image.filename.extension_with_delimiter
if ActiveStorage::Blob.service.respond_to?(:path_for)
attachments.inline[#filename] = File.read(ActiveStorage::Blob.service.send(:path_for, object.image.key))
elsif ActiveStorage::Blob.service.respond_to?(:download)
attachments.inline[#filename] = object.image.download
end
end
in mailer view:
if #filename
image_tag(attachments[#filename].url)
else
image_tag(attachments['placeholder.png'].url)
end

This worked for me in production using Amazon S3.
in mailer view:
if #object.images
#object.images.each do |image|
path = "https://www.example.com" + Rails.application.routes.url_helpers.rails_blob_path(image, only_path: true)
<img src="<%=path%>">
end
end

Here is the working solution for active storage url in email template. I have seen the images visible in gmail. You can use "rails_blob_url". This works for file stored in aws s3.
mailer.rb
....
#image_url = Rails.application.routes.url_helpers.rails_blob_url(blob),
....
mailer view file
<img src="<%= #image_url %>">

Related

Use Shrine to upload video files and generate thumbnails

I want to use the Shrine gem to upload video files, transcode, & generate thumbnails from the video.
I am trying to convert Erik Dahlstrand's Shrine-Rails-example from photos to videos. I am having trouble creating the video uploader. I based this code on Video isn't of allowed type (allowed types: video/mp4), Shrine, Rails
require "streamio-ffmpeg"
class VideoUploader < Shrine
ALLOWED_TYPES = %w[video/mp4 video/quicktime video/x-msvideo video/mpeg]
plugin :processing
plugin :versions
plugin :determine_mime_type
plugin :cached_attachment_data
plugin :remove_attachment
plugin :add_metadata
plugin :validation_helpers
plugin :derivation_endpoint, prefix: "derivations/video"
add_metadata do |io, context|
movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) }
{ "duration" => movie.duration,
"bitrate" => movie.bitrate,
"resolution" => movie.resolution,
"frame_rate" => movie.frame_rate }
end
movie.screenshot("video_thumb_007.jpg", seek_time: 5, resolution: '320x240')
metadata_method :duration
Attacher.validate do
validate_max_size 100.megabyte, message: "is too large (max is 100 MB)"
validate_mime_type_inclusion ALLOWED_TYPES
end
end
I get this error:
/var/folders/mm/_j8x4k2176jcv31zvbc497_c0000gp
/T/shrine20190607-24438-4f3jz2.m4v: No such file or directory
and in fact, there is no file in that location. So where is the file stored while waiting for upload??
Also, using the demo to upload photos to AWS (production env), the objects are stored in the bucket in a folder called 'photos'. Shrine uses the table name to name the folder apparently. Is possible to create alternative & nested folder names?
Thank you - seems like an amazing gem! Trying to understand it better!
Thx
Further delving into the documentation, I was able to solve this problem with only one remaining question - is it possible to control which folders in the AWS bucket the original and thumbs are uploaded to? Thx
Solution:
require "streamio-ffmpeg"
require "tempfile"
class VideoUploader < ImageUploader
plugin :add_metadata
plugin :validation_helpers
plugin :processing
plugin :versions
plugin :delete_raw
add_metadata do |io, context|
movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) }
{ "duration" => movie.duration,
"bitrate" => movie.bitrate,
"resolution" => movie.resolution,
"frame_rate" => movie.frame_rate
}
end
process(:store) do |io, context|
versions = {original: io}
io.download do |original|
screenshot1 = Tempfile.new(["screenshot1", ".jpg"], binmode: true)
screenshot2 = Tempfile.new(["screenshot2", ".jpg"], binmode: true)
screenshot3 = Tempfile.new(["screenshot3", ".jpg"], binmode: true)
screenshot4 = Tempfile.new(["screenshot4", ".jpg"], binmode: true)
movie = FFMPEG::Movie.new(original.path)
movie.screenshot(screenshot1.path, seek_time: 5, resolution: '640x480')
movie.screenshot(screenshot2.path, seek_time: 10, resolution: '640x480')
movie.screenshot(screenshot3.path, seek_time: 15, resolution: '640x480')
movie.screenshot(screenshot4.path, seek_time: 20, resolution: '640x480')
[screenshot1, screenshot2, screenshot3, screenshot4].each(&:open) # refresh file descriptors
versions.merge!(screenshot1: screenshot1, screenshot2: screenshot2, screenshot3: screenshot3, screenshot4: screenshot4)
end
versions
end
Attacher.validate do
validate_max_size 100.megabyte, message: "is too large (max is 100 MB)"
validate_mime_type_inclusion ALLOWED_TYPES
end
end

create a (Prawn) PDF inside custom DelayedJob and upload it to S3?

Using: Rails 4.2, Prawn, Paperclip, DelayedJobs via ActiveJobs, Heroku.
I have a PDF that is very large and needs to be handled in the background. Inside a custom Job I want to create it, upload it to S3, and then email the user with a url when its ready. I facilitate this via a PdfUpload model.
Is there anything wrong with my approach/code? Im using File.open() as outlined in examples I found, but this seems to be the root of my error ( TypeError: no implicit conversion of FlightsWithGradesReport into String ).
class PdfUpload < ActiveRecord::Base
has_attached_file :report,
path: "schools/:school/pdf_reports/:id_:style.:extension"
end
/pages_controller.rb
def flights_with_grades_report
flash[:success] = "The report you requested is being generated. An email will be sent to '#{ current_user.email }' when it is ready."
GenerateFlightsWithGradesReportJob.perform_later(current_user.id, #rating.id)
redirect_to :back
authorize #rating, :reports?
end
/ the job
class GenerateFlightsWithGradesReportJob < ActiveJob::Base
queue_as :generate_pdf
def perform(recipient_user_id, rating_id)
rating = Rating.find(rating_id)
pdf = FlightsWithGradesReport.new( rating.id )
pdf_upload = PdfUpload.new
pdf_upload.report = File.open( pdf )
pdf_upload.report_processing = true
pdf_upload.report_file_name = "report.pdf"
pdf_upload.report_content_type = "application/pdf"
pdf_upload.save!
PdfMailer.pdf_ready(recipient_user_id, pdf_upload.id)
end
end
This results in an error:
TypeError: no implicit conversion of FlightsWithGradesReport into String
Changing this:
pdf_upload.report = File.open( pdf )
to this:
pdf_upload.report = StringIO.new(pdf.render)
fixed my problem.

Rails / Sitemap_Generator: Subdomain sitemaps

I'm trying to create a sitemap for my app which features subdomains using the sitemap_generator gem. However, I'm getting an error with my code:
the scheme http does not accept registry part: .foo.com (or bad hostname?)
My sitemap.rb:
SitemapGenerator::Sitemap.default_host = "http://www.foo.com"
SitemapGenerator::Sitemap.sitemaps_host = "http://s3.amazonaws.com/foo/"
SitemapGenerator::Sitemap.public_path = 'tmp/'
SitemapGenerator::Sitemap.sitemaps_path = 'sitemaps/'
SitemapGenerator::Sitemap.adapter = SitemapGenerator::WaveAdapter.new
SitemapGenerator::Sitemap.create do
add '/home'
end
Customer.find_each do |customer|
SitemapGenerator::Sitemap.default_host = "http://#{customer.user_name}.foo.com"
SitemapGenerator::Sitemap.sitemaps_path = "sitemaps/#{customer.user_name}"
SitemapGenerator::Sitemap.create do
add '/home'
end
end
What am I doing wrong?
I'm the author of this gem.
I am fairly certain that the problem is with one of the customer user name's containing a character which is not allowed in a URL. A simple test with simple names works e.g.:
%w(bill mary bob).each do |customer|
SitemapGenerator::Sitemap.default_host = "http://#{customer}.foo.com"
SitemapGenerator::Sitemap.sitemaps_path = "sitemaps/#{customer}"
SitemapGenerator::Sitemap.create do
add '/home'
end
end

Rails - How to test that ActionMailer sent a specific attachment?

In my ActionMailer::TestCase test, I'm expecting:
#expected.to = BuyadsproMailer.group_to(campaign.agency.users)
#expected.subject = "You submitted #{offer_log.total} worth of offers for #{offer_log.campaign.name} "
#expected.from = "BuyAds Pro <feedback#buyads.com>"
#expected.body = read_fixture('deliver_to_agency')
#expected.content_type = "multipart/mixed;\r\n boundary=\"something\""
#expected.attachments["#{offer_log.aws_key}.pdf"] = {
:mime_type => 'application/pdf',
:content => fake_pdf.body
}
and stub my mailer to get fake_pdf instead of a real PDF normally fetched from S3 so that I'm sure the bodies of the PDFs match.
However, I get this long error telling me that one email was expected but got a slightly different email:
<...Mime-Version: 1.0\r\nContent-Type: multipart/mixed\r\nContent-Transfer-Encoding: 7bit...> expected but was
<...Mime-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n boundary=\"--==_mimepart_50f06fa9c06e1_118dd3fd552035ae03352b\";\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit...>
I'm not matching the charset or part-boundary of the generated email.
How do I define or stub this aspect of my expected emails?
Here's an example that I copied from my rspec test of a specific attachment, hope that it helps (mail can be creating by calling your mailer method or peeking at the deliveries array after calling .deliver):
mail.attachments.should have(1).attachment
attachment = mail.attachments[0]
attachment.should be_a_kind_of(Mail::Part)
attachment.content_type.should be_start_with('application/ics;')
attachment.filename.should == 'event.ics'
I had something similar where I wanted to check an attached csv's content. I needed something like this because it looks like \r got inserted for newlines:
expect(mail.attachments.first.body.encoded.gsub(/\r/, '')).to(
eq(
<<~CSV
"Foo","Bar"
"1","2"
CSV
)
)

Alter URL using ActiveResource

I am using ActiveResource to manage accessing an external service.
The external service has an URL like:
http://api.cars.com/v1/cars/car_id/range/range_num?filter=filter1,filter2
Here's my Car class:
class Car < ActiveResource::Base
class << self
def element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
"#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}#{query_string(query_options)}"
end
def collection_path(prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
"#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}"
end end
self.site = "http://api.cars.com/"
self.prefix = "/v1/"
self.format = :json
end
When I set up my object to get a particular car in rails console:
> car = car.new
> car.get('1234')
I get a URL like this:
http://api.cars.com/v1/cars//1234.json
How do I get the URL to include the range and range_num elements?
Also, i don't want the .json extension on the end of the URL. I've attempted overriding the element_name and collection_name methods as described here: How to remove .xml and .json from url when using active resource but it doesn't seem to be working for me...
Thanks in advance for any ideas!
Get rid of the forward slash in the URL
"#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}#{query_string(query_options)}"
becomes:
"#{prefix(prefix_options)}#{collection_name}#{URI.parser.escape id.to_s}#{query_string(query_options)}"