I am uploading an image that I am making many versions of, and I want to be able to put each version in a different folder. For example:
class ItunesArtworkUploader < CarrierWave::Uploader::Base
DIMENSIONS = [1024, 1024]
{
:iphone_small_29x29 => { size: [29, 29], filename: "icon-small.png" },
:iphone_57x57 => { size: [57, 57], filename: "icon.png" }
}
# resize
def filename
"iTunesArtwork.png"
end
end
The original ItunesArtwork.png is being put into uploads/foo/itunes_artwork/iTunesArtwork.png. Now for each version I want them to be in their own folders underneath "foo" like this:
uploads/foo/itunes_artwork/iTunesArtwork.png
uploads/foo/itunes_artwork/icon-small.png
uploads/foo/itunes_artwork/icon.png
I've tried
:iphone_small_29x29 => { size: [29, 29], filename: "/icon-small/icon-small.png"},
but that gives me this as a resulting filepath:
uploads/foo/itunes_artwork/icon-small/icon-small.png
Any suggestions would be great, thanks!
Related
Im trying to to create a hash with one key per each type of extension on a directory. To every key I would like to add two values: number of times that extension is repeated and total size of all the files with that extension.
Something similar to this:
{".md" => {"ext_reps" => 6, "ext_size_sum" => 2350}, ".txt" => {"ext_reps" => 3, "ext_size_sum" => 1300}}
But I´m stuck on this step:
hash = Hash.new{|hsh,key| hsh[key] = {}}
ext_reps = 0
ext_size_sum = 0
Dir.glob("/home/computer/Desktop/**/*.*").each do |file|
hash[File.extname(file)].store "ext_reps", ext_reps
hash[File.extname(file)].store "ext_size_sum", ext_size_sum
end
p hash
With this result:
{".md" => {"ext_reps" => 0, "ext_size_sum" => 0}, ".txt" => {"ext_reps" => 0, "ext_size_sum" => 0}}
And I can't finde the way to increment ext_reps and ext_siz_sum
Thanks
Suppose the file name extensions and files sizes drawn are as follows.
files = [{ ext: 'a', size: 10 },
{ ext: 'b', size: 20 },
{ ext: 'a', size: 30 },
{ ext: 'c', size: 40 },
{ ext: 'b', size: 50 },
{ ext: 'a', size: 60 }]
You can use Hash#group_by and Hash#transform_values.
files.group_by { |h| h[:ext] }.
transform_values do |arr|
{ "ext_reps"=>arr.size, "ext_size_sum"=>arr.sum { |h| h[:size] } }
end
#=> {"a"=>{"ext_reps"=>3, "ext_size_sum"=>100},
# "b"=>{"ext_reps"=>2, "ext_size_sum"=>70},
# "c"=>{"ext_reps"=>1, "ext_size_sum"=>40}}
Note that the first calculation is as follows.
files.group_by { |h| h[:ext] }
#=> {"a"=>[{:ext=>"a", :size=>10}, {:ext=>"a", :size=>30},
# {:ext=>"a", :size=>60}],
# "b"=>[{:ext=>"b", :size=>20}, {:ext=>"b", :size=>50}],
# "c"=>[{:ext=>"c", :size=>40}]}
Another way is use the forms of Hash#update (aka Hash#merge!) and Hash#merge that employ a block to compute the values of keys that are present in both hashes being merged. (Ruby does not consult that block when a key-value pair with key k is being merged into the hash being built (h) when h does not have a key k.)
See the docs for an explanation of the three parameters of the block that returns the values of common keys of hashes being merged.
files.each_with_object({}) do |g,h|
h.update(g[:ext]=>{"ext_reps"=>1, "ext_size_sum"=>g[:size]}) do |_k,o,n|
o.merge(n) { |_kk, oo, nn| oo + nn }
end
end
#=> {"a"=>{"ext_reps"=>3, "ext_size_sum"=>100},
# "b"=>{"ext_reps"=>2, "ext_size_sum"=>70},
# "c"=>{"ext_reps"=>1, "ext_size_sum"=>40}}
I've chosen names for the common keys of the "outer" and "inner" hashes (_k and _kk, respectively) that begin with an underscore to signal to the reader that they are not used in the block calculation. This is common practive.
Note that this approach avoids the creation of a temporary hash similar to that created by group_by and therefore tends to use less memory than the first approach.
Here is a solution inspired by the answers given by Cary Swoveland and BenFenner
hash = {}
Dir.glob("/home/computer/Desktop/**/*.*").each do |file|
(hash[File.extname(file)] ||= []) << file.size
end
hash.transform_values! { |sizes| { "ext_reps" => sizes.count, "ext_size_sum" => sizes.sum } }
With each_with_object and nested Hash.new
files = [{ ext: 'a', size: 10 },
{ ext: 'b', size: 20 },
{ ext: 'a', size: 30 },
{ ext: 'c', size: 40 },
{ ext: 'b', size: 50 },
{ ext: 'a', size: 60 }]
files.each_with_object(Hash.new(Hash.new(0))) do |el, hash|
h = hash[el[:ext]]
hash[el[:ext]] =
{ "ext_reps" => h["ext_reps"] + 1, "ext_size_sum" => h["ext_size_sum"] + el[:size] }
end
#=> {"a"=>{"ext_reps"=>3, "ext_size_sum"=>100},
# "b"=>{"ext_reps"=>2, "ext_size_sum"=>70},
# "c"=>{"ext_reps"=>1, "ext_size_sum"=>40}}
It's not the most "Ruby-like" solution, but going along with your provided example this is probably what you'd ultimately end up with as a solution. Your main problem was that you were never incrementing the ext_reps value, nor were you ever accumulating the ext_size_sum value.
hash = {}
Dir.glob('/home/computer/Desktop/**/*.*').each do |file|
file_extension = File.extname(file)
if hash[file_extension].nil?
# This is the first time this file extension has been seen, so initialize things for it.
hash[file_extension] = {}
hash[file_extension]['ext_reps'] = 0
hash[file_extension]['ext_size_sum'] = 0
end
# Increment/accumulate values.
hash[file_extension]['ext_reps'] += 1
hash[file_extension]['ext_size_sum'] += file.size
end
This is pretty much a reiteration of Cary's and others' answers without temporary variables. (Which is more Ruby-like IMHO.)
Dir.glob("*.*")
.group_by { |f| File.extname(f) }
.transform_values do |files|
{
"count" => files.count,
"size" => files.sum { |f| File.size(f) }
}
end
=> {".app"=>{"count"=>1, "size"=>96},
".sh-builder"=>{"count"=>1, "size"=>192},
".sh-names"=>{"count"=>1, "size"=>288},
".json"=>{"count"=>2, "size"=>5362},
".rb"=>{"count"=>1, "size"=>132}}
I have uploaders for different types of images. Each has the same set of derivatives (large, medium, thumbnail), but different resolutions. But they also share some configuration. For example, each uploader converts the original to jpeg, changes quality and strips metadata.
class BannerUploader < Shrine
Attacher.derivatives do |original|
magick = ImageProcessing::MiniMagick.source(original)
.convert('jpg')
.saver(quality: 85)
.strip
{
large: magick.resize_to_limit!(1600, 400),
medium: magick.resize_to_limit!(800, 200),
thumbnail: magick.resize_to_limit!(400, 100)
}
end
end
This one has the same processing rules:
class PhotoUploader < Shrine
Attacher.derivatives do |original|
magick = ImageProcessing::MiniMagick.source(original)
.convert('jpg')
.saver(quality: 85)
.strip
{
large: magick.resize_to_limit!(1200, 1200),
medium: magick.resize_to_limit!(600, 600),
thumbnail: magick.resize_to_limit!(300, 300)
}
end
end
Is it possible to extract and share some of the configuration (like .convert('jpg').saver(quality: 85).strip) between those uploaders? Something similar to validations inheritance or a helper.
There isn't anything for sharing processing logic out-of-the-box, but you can create a service object, for example:
class BannerUploader < Shrine
Attacher.derivatives do |original|
Thumbnails.call(original, {
large: [1600, 400],
medium: [800, 200],
thumbnail: [400, 100],
})
end
end
class PhotoUploader < Shrine
Attacher.derivatives do |original|
Thumbnails.call(original, {
large: [1200, 1200],
medium: [600, 600],
thumbnail: [300, 300],
})
end
end
class Thumbnails
def self.call(original, sizes)
magick = ImageProcessing::MiniMagick.source(original)
.convert('jpg')
.saver(quality: 85)
.strip
thumbnails = {}
sizes.each do |name, (width, height)|
thumbnails[name] = magick.resize_to_limit!(width, height)
end
thumbnails
end
end
Rails 5.2.0 (as API)
/config/application.rb
config.active_storage.variant_processor = :vips
Problem:
/serializers/api/v1/user/current_user_serializer.rb
class Api::V1::User::CurrentUserSerializer < Api::V1::User::BaseSerializer
include Rails.application.routes.url_helpers
attributes(
[...]
:avatar
:created_at
)
def avatar
if object.avatar.attachment
avatar = {
image: url_for( object.avatar ), # This one works
thumb: url_for( object.avatar.variant(resize_to_fit: [800, 800]) ), # EXCEPTION
thumb_test: url_for( object.avatar.variant(resize: '800x800') ) # Returns image of size: 640x800 (expected 800x800)
}
end
end
end
I get the following exception:
exception: "<MiniMagick::Error: `mogrify -resize-to-fit [800, 800] /tmp/mini_magick20180625-19749-rghjbg.jpg` failed with error: mogrify.im6: unrecognized option `-resize-to-fit' # error/mogrify.c/MogrifyImageCommand/5519. >"
EDIT
Thanks #George Claghorn
I now created my own variant based on this post:
https://prograils.com/posts/rails-5-2-active-storage-new-approach-to-file-uploads
lib/active_storage_variants.rb
class ActiveStorageVariants
class << self
def resize_to_fill(width:, height:, blob:, gravity: 'Center')
blob.analyze unless blob.analyzed?
cols = blob.metadata[:width].to_f
rows = blob.metadata[:height].to_f
if width != cols || height != rows
scale_x = width / cols
scale_y = height / rows
if scale_x >= scale_y
cols = (scale_x * (cols + 0.5)).round
resize = cols.to_s
else
rows = (scale_y * (rows + 0.5)).round
resize = "x#{rows}"
end
end
{
resize: resize,
gravity: gravity,
background: 'rgba(255,255,255,0.0)',
extent: cols != width || rows != height ? "#{width}x#{height}" : ''
}.merge(optimize_hash(blob))
end
end
end
/models/concerns/users/active_storage_variants.rb
require 'active_storage_variants' # /lib/active_storage_variants.rb
module Users::ActiveStorageVariants
def avatar_thumbnail
variation = ActiveStorage::Variation.new(
ActiveStorageVariants.resize_to_fill(
width: 300, height: 300, blob: avatar.blob
)
)
ActiveStorage::Variant.new(avatar.blob, variation)
end
end
/models/user.rb
class User < ApplicationRecord
...
## Concerns
include Users::ActiveStorageVariants
...
end
To call it:
user.avatar_thumbnail
resize_to_fit: [800, 800] is an ImageProcessing transformation. Rails 5.2 doesn’t use ImageProcessing and thus doesn’t support libvips; it uses MiniMagick directly instead.
Rails 6 will switch to ImageProcessing and add libvips support. To use libvips now, prior to the release of Rails 6, bundle the master branch of the rails/rails repository on GitHub:
# Gemfile
gem "rails", github: "rails/rails"
Found a solution here
Replacing this,
<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %>
with,
<%= image_tag user.avatar.variant(resize: "100 x100") %>
Worked for me. Even though the office document does the former way.
I am creating a pdf report in order to show some data using the "squid" gem. This would allow me to display charts in my pdf.
The only issue i found is that when the chart does not fit at the bottom of the page then it looks rendered partially which does not look good at all. Any idea how can i fix this?
Here is the code i am using to render the charts
require 'squid'
class SurveyPdf < Prawn::Document
def initialize(survey, view)
super()
font "#{Rails.root}/app/assets/fonts/roboto-condensed.ttf"
#survey = survey
#view = view
questions
end
def questions
#survey.questions.each do |question|
text "#{question.title}", size: 20
text "Answers #{question.answers.size}", size: 15
if ["single", "select"].include? question.question_type.prefix
if question.answers.choice_counter.any?
chart choices: question.answers.choice_counter
end
end
if question.question_type.prefix == "image"
if question.answers.image_counter.any?
chart images: question.answers.image_counter
end
end
if question.question_type.prefix == "multiple"
if question.answers.multiple_choice_counter.any?
chart choices: question.answers.multiple_choice_counter
end
end
if question.question_type.prefix == "raiting"
move_down 5
if question.answers.any?
text_box "Average rating", size: 12, width: 120, :at => [0, cursor - 2]
text_box "#{average_rating(question.answers.rating_average)}", size: 12, width: 120, :at => [4 * 30, cursor - 2]
else
text_box "Average rating", size: 12, width: 120, :at => [0, cursor - 2]
text_box "0", size: 12, width: 120, :at => [4 * 30, cursor - 2]
end
end
end
end
end
For a similar issue I used the prawn-grouping gem
It pre-renders whatever you place in a group block to test whether it fits on the current page. If not, it skips to the next page and renders.
In your case you would do something like:
def questions
#survey.questions.each do |question|
group :too_tall => lambda { start_new_page } do |g|
g.text "#{question.title}", size: 20
g.text "Answers #{question.answers.size}", size: 15
if ["single", "select"].include? question.question_type.prefix
if question.answers.choice_counter.any?
g.chart choices: question.answers.choice_counter
end
end
if question.question_type.prefix == "image"
if question.answers.image_counter.any?
g.chart images: question.answers.image_counter
end
end
if question.question_type.prefix == "multiple"
if question.answers.multiple_choice_counter.any?
g.chart choices: question.answers.multiple_choice_counter
end
end
if question.question_type.prefix == "raiting"
move_down 5
if question.answers.any?
g.text_box "Average rating", size: 12, width: 120, :at => [0, cursor - 2]
g.text_box "#{average_rating(question.answers.rating_average)}", size: 12, width: 120, :at => [4 * 30, cursor - 2]
else
g.text_box "Average rating", size: 12, width: 120, :at => [0, cursor - 2]
g.text_box "0", size: 12, width: 120, :at => [4 * 30, cursor - 2]
end
end
end
end
end
disclaimer: I've never used squid so the only piece I'm not sure of is g.chart let me know if you have issues there and I will try to figure it out.
Update for squid
The prawn-grouping gem doesn't know about the squid methods (like chart). So we can extract the logic from the prawn-grouping gem and add it directly in your survey_pdf.rb. Copy lines 7-63 from this file, and remove prawn-grouping gem from your app.
if you are curious why this works...
Squid uses the Prawn::Document.extensions method to force Prawn::Document to inherit the squid methods. You can see that in the squid gem code here on line 37.
For prawn-grouping to work it creates a new Prawn::Document as part of the group method. You can see that here on line 55. The problem was that the Prawn::Document instantiated via the prawn-grouping gem wasn't inheriting the squid methods... but your SurveyPdf instance of Prawn::Document does inherit the squid methods, so by adding the grouping logic into your SurveyPdf class, now the Prawn::Document instantiated in your group method will work.
To answer the question in your comment as to determining page size I will run through a few useful methods too long for a comment:
d = Prawn::Document.new
d.y #full page height
d.margin_box.bottom #actually top since prawn starts at the bottom
d.margin_box.absolute_bottom #actual top with margins included
d.margin_box.top #usable page height
d.margin_box.absolute_top #same as #y
d.cursor #where you are vertically on the page
So you can use some basic math to determine fit:
#this is all chart does excepting chart calls #draw
#which we don't want to do until we determine if it fits
c = Squid::Graph.new(d, choices: question.answers.choice_counter)
#determine if the chart will fit vertically
#if not start a new page and move to the top
unless d.cursor + c.height < d.margin_box.top
d.start_new_page
d.move_cursor_to(0)
end
#draw the chart onto the appropriate page
c.draw
Hope this helps in some way
Using Paperclip for file upload in my Rails app and I need to convert images into separate PDFs before uploading to Amazon S3 servers. I know I can use Prawn for the image to PDF conversion and I can intercept the file using the answer to this stack overflow question
In the model:
has_attached_file :file
before_file_post_process :convert_images
...
def convert_images
if file_content_type == 'image/png' || file_content_type == 'image/jpeg'
original_file = file.queued_for_write[:original]
filename = original_file.path.to_s
pdf = Prawn::Document.new
pdf.image open(filename), :scale => 1.0, position: :center
file = pdf.render
end
end
However I'm unable to actually convert the image that is stored on S3. Any ideas what I'm missing?
Edit: Adding a save! call results in validations failing that weren't doing so before.
Was able to figure it out.
changed:
before_file_post_process :convert_images
to:
before_save :convert_images
and changed my convert_images method to:
def convert_images
if file_content_type == 'image/png' || file_content_type == 'image/jpeg'
file_path = file.queued_for_write[:original].path.to_s
temp_file_name = file_file_name.split('.')[0] + '.pdf'
pdf = Prawn::Document.new(:page_size => "LETTER", :page_layout => :portrait)
pdf.image File.open("#{file_path}"), :fit => [612, 792], position: :center
pdf.render_file temp_file_name
file_content_type = 'application/pdf'
self.file = File.open(temp_file_name)
File.delete(temp_file_name)
end
end