Error while inserting image in table using Prawn - pdf

I am working with Prawn to generate a pdf, I have to insert an image in a cell of a table.
My code is like this:
image = "path to file"
subject = [["FORMATIVE I "," SUMATIVE ","GRAPH"],
[formative_1,sumative, {:image => image}]
]
table subject
But, I get an error which says:
prawn/table/cell.rb:127:in `make': Content type not recognized: nil (ArgumentError)
How can I resolve this? Any help is greatly appreciated.
Cheers!

In the current version of Prawn 0.12.0 it is not possible to embed images in a Prawn::Table, but this feature seems to be under way, see here. At the moment you have to write your own table, something like
data = [[{:image => "red.png"},{:text => "Red"}],
[{:image => "blue.png"},{:text => "Blue"}]]
data.each_with_index do |row,i|
row.each_with_index do |cell,j|
bounding_box [x_pos,y_pos], :width => cell_width, :height => cell_height do
image(cell[:image], ...) if cell[:image].present?
text_box(cell[:text], ...) if cell[:text].present?
end
x_pos = (x_pos + cell_width)
end
y_pos = (y_pos - cell_height)
end

Prawn in version 0.12.0 doesn't give the possibility to insert image in a cell. Look at this for further information. It works on the next version 1.0.0.rc1. Just change your version. You can also use the tricky way, but I advise you not to do so.
The manual is available here.
The commit and explanation for that feature from the author. Here

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

How to attach images in mailer from active storage association in Rails

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 %>">

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
)
)

Local Jump Error No block given (yield) error on find_each

I'm trying to step through a list of records that are retrieved with find_each.
I patterned my controller code on the answer in this stack overflow post, but I'm still getting a "No Block Given (Yield)" error.
I'm just getting started in Ruby and Rails and I haven't yet found a full fledged explanation (lots of basic examples though) of blocks and yield that gives me what I need.
My code looks like this:
def select_save
#class = params[:class]
#student_id = params[:id]
#class.each do |id|
old_subject = Subject.find(id)
new_subject = old_subject.dup
new_subject.student_id = #student_id
new_subject.save
Assignment.find_each.where(:subject_id => id) do |assignments|
assignments.each do |a|
new_assignment = a.dup
new_assignment.subject_id = new_subject.id
new_assignment.save
end
end
end
respond_to do |format|
format.html { redirect_to #student, :notice => 'Subject and assignments created.' }
end
end
and the error points to the line with find_each.
I know I need a block to yield to, but how exactly that would look in this particular case escapes me.
Thanks for any suggestions.
You're passing a block to where and no block to find_each. You can't do that. You need to reverse find_each and where on this line, the order is important as the block is passed to the last method invoked:
Assignment.find_each.where(:subject_id => id) do |assignments|
It should read:
Assignment.where(:subject_id => id).find_each do |assignments|
Your next problem is you're trying to iterate over assignments, which is a single Assignment. find_each is already doing the iteration for you, passing one assignment into the block at a time. The block should read:
Assignment.where(:subject_id => id).find_each do |assignment|
new_assignment = assignment.dup
new_assignment.subject_id = new_subject.id
new_assignment.save
end
I'm going to make the assumption that your Subject has many Assignments, since you have a subject_id inside your Assignment class. If this is the case, the final and most correct way to write your loop would be:
old_subject.assignments.each do |assignment|
new_assignment = assignment.dup
new_assignment.subject_id = new_subject.id
new_assignment.save
end

How do I export PSDs as PNGs with py-appscript?

I wrote a script to export PSDs as PNGs with rb-appscript. That's fine and dandy, but I can't seem to pull it off in py-appscript.
Here's the ruby code:
#!/usr/bin/env ruby
require 'rubygems'
require 'optparse'
require 'appscript'
ps = Appscript.app('Adobe Photoshop CS5.app')
finder = Appscript.app("Finder.app")
path = "/Users/nbaker/Desktop/"
ps.activate
ps.open(MacTypes::Alias.path("#{path}guy.psd"))
layerSets = ps.current_document.layer_sets.get
# iterate through all layers and hide them
ps.current_document.layers.get.each do |layer|
layer.visible.set false
end
layerSets.each do |layerSet|
layerSet.visible.set false
end
# iterate through all layerSets, make them visible, and create a PNG for them
layerSets.each do |layerSet|
name = layerSet.name.get
layerSet.visible.set true
ps.current_document.get.export(:in => "#{path}#{name}.png", :as => :save_for_web,
:with_options => {:web_format => :PNG, :png_eight => false})
layerSet.visible.set false
end
And here's the apparently nonequivalent python code:
from appscript import *
from mactypes import *
# fire up photoshop
ps = app("Adobe Photoshop CS5.app")
ps.activate()
# open the file for editing
path = "/Users/nbaker/Desktop/"
f = Alias(path + "guy.psd")
ps.open(f)
layerSets = ps.current_document.layer_sets()
# iterate through all layers and hide them
for layer in ps.current_document.layers():
layer.visible.set(False)
#... and layerSets
for layerSet in layerSets:
layerSet.visible.set(False)
# iterate through all layerSets, make them visible, and create a PNG for them
for layerSet in layerSets:
name = layerSet.name()
layerSet.Visible = True
ps.current_document.get().export(in_=Alias(path + name + ".png"), as_=k.save_for_web,
with_options={"web_format":k.PNG, "png_eight":False})
The only part of the python script that doesn't work is the saving. I've gotten all sorts of errors trying different export options and stuff:
Connection is invalid... General Photoshop error occurred. This functionality may not be available in this version of Photoshop... Can't make some data into the expected type...
I can live with just the ruby script, but all of our other scripts are in python, so it would be nice to be able to pull this off in python. Thanks in advance internet.
Bert,
I think I have a solution for you – albeit several months later. Note that I'm a Python noob, so this is by no means elegant.
When I tried out your script, it kept crashing for me too. However by removing the "Alias" in the export call, it seems to be OK:
from appscript import *
from mactypes import *
# fire up photoshop
ps = app("Adobe Photoshop CS5.1.app")
ps.activate()
# open the file for editing
path = "/Users/ian/Desktop/"
f = Alias(path + "Screen Shot 2011-10-13 at 11.48.51 AM.psd")
ps.open(f)
layerSets = ps.current_document.layer_sets()
# no need to iterate
ps.current_document.layers.visible.set(False)
ps.current_document.layer_sets.visible.set(False)
# iterate through all layerSets, make them visible, and create a PNG for them
for l in layerSets:
name = l.name()
# print l.name()
l.visible.set(True)
# make its layers visible too
l.layers.visible.set(True)
# f = open(path + name + ".png", "w").close()
ps.current_document.get().export(in_=(path + name + ".png"), as_=k.save_for_web,
with_options={"web_format":k.PNG, "png_eight":False})
I noticed, too, that you iterate through all the layers to toggle their visibility. You can actually just issue them all a command at once – my understanding is that it's faster, since it doesn't have to keep issuing Apple Events.
The logic of my script isn't correct (with regards to turning layers on and off), but you get the idea.
Ian