How do I generate website thumbnails without 3rd party tools? - ruby-on-rails

Are there any gems out there for Rails 3.2.1 that generate website thumbnails? I see a lot of 3rd party solutions but I don't like the fact that they aren't hosted on my server. It's really important the app I'm building is as stable as possible and I think this is not a good solution in the long run.
My ruby knowledge is fairly good, I think enough to use a gem and implement it, but definitely not good enough to write something like this from scratch if no gems exist.
Thanks!

You could try dragonfly or carrierwave

Well, here's the first thing that came up on Rubygems: thumbnailer. It uses Amazon and costs a small fee per image it generates, so you probably don't want this...
But there's also thumbnailer-ruby which looks like it works completely on the local machine. Haven't tested it out, though. It appears that this doesn't actually do what you want. Nevermind.
Now another gem called snapurl looks pretty fancy. Once again, I haven't tried it out yet. I'll do that now.
EDIT: Won't run for me; keeps failing with an error.

https://url2png.com/ has worked great so far

aBrowshot has a gem available.

There's no need to use a third party service for this.
You can do something like this in your model:
class MySexyModel < ActiveRecord::Base
... stuff
# Generate the thumbnail on validate so we can return errors on failure
validate :generate_thumbnail_from_url
# Cleanup temp files when we are done
after_save :cleanup_temp_thumbnail
# Generate a thumbnail from the remote URL
def generate_thumbnail_from_url
# Skip thumbnail generation if:
# a) there are already other validation errors
# b) an image was manually specified
# c) an image is already stored and the URL hasn't changed
skip_generate = self.errors.any? || (self.image_changed? ||
(self.image_stored? && !self.url_changed?))
# p "*** generating thumbnail: #{!skip_generate}"
return if skip_generate
# Generate and assign an image or set a validation error
begin
tempfile = temp_thumbnail_path
cmd = "wkhtmltoimage --quality 95 \"#{self.url}\" \"#{tempfile}\""
# p "*** grabbing thumbnail: #{cmd}"
system(cmd) # sometimes returns false even if image was saved
self.image = File.new(tempfile) # will throw if not saved
rescue => e
# p "*** thumbnail error: #{e}"
self.errors.add(:base, "Cannot generate thumbnail. Is your URL valid?")
ensure
end
end
# Return the absolute path to the temporary thumbnail file
def temp_thumbnail_path
File.expand_path("#{self.url.parameterize.slice(0, 20)}.jpg", Dragonfly.app.datastore.root_path)
end
# Cleanup the temporary thumbnail image
def cleanup_temp_thumbnail
File.delete(temp_thumbnail_path) rescue 0
end
end
The original post is on this blog: http://sourcey.com

Related

Reupload images on Amazon S3 carrierwave

I have images uploaded to amazon s3 bucket. When I tried to recreate_versions!, It gives me a nil body exception.
I think this is due to changes in previous uploader settings in our code. However, when I do pr.image.url, it still gives me the original image, so what I tried is below:
begin
User.all.each do |pr|
if pr.user.present?
pr.remote_avatar_url = pr.avatar.url
pr.save!
end
end
rescue
end
But it throws an error:
ActiveRecord::RecordInvalid: Validation failed: Avatar trying to
download a file which is not served over HTTP
Which I know is carrierwave exception. What I'm trying to do is, I want to reupload all the images (because pr.avatar.url give me the original image), but I don't know how to do it. Any help will be greatly appreciated.
You are correct in attempting to store the remote URL in an attribute called remote_avatar_url.
CarrierWave throws the Validation failed: ATTRIBUTE trying to download a file which is not served over HTTP exception when attempting to save an invalid URL to the model. More specifically, CarrierWave::Uploader::Download raises a CarrierWave::DownloadError when the downloaded file "scheme" attribute does not match the regex /^https/ (meaning the URL does not start with "https"). You can view this logic here. (In particular, see lines 31 and 69.)
I'm not sure if this is the problem, but you might try checking the pr.avatar.url to see whether it begins with the https prefix before assigning it to the remote_avatar_url.
I hope this was at least somewhat helpful.
To re-upload the image, you need to download the image, if your carrierwave attr is remote_avatar, then maybe you can do something like:
begin
User.all.each do |pr|
if pr.user.present?
pr.remote_avatar = File.open(pr.avatar.url, 'rb')
pr.save!
end
end
rescue
end

Saving image created with RMagick using Rails and CarrierWave

In my Rails app I have a module that takes several images, and using RMagick "stitches" them together into a new single image. I'm able to successfully create the final image, but I'm having trouble saving this new image as an attachment to a model (using CarrierWave). The method that does the stitching looks like this:
def generate_collage(params)
final_image = ImageList.new
# ... code that puts together the composite image ...
return final_image.append(true).to_blob { |attrs| attrs.format = 'JPEG' }
end
I've got my User model with an uploader mounted:
class User < ActiveRecord::Base
mount_uploader :image, UserImageUploader
end
In the CarrierWave documentation under the ActiveRecord section, they show how to assign a new image, but they assume the file already exists somewhere. In my case it doesn't yet exist on the filesystem, and I'm outputting a blob... is there any way to go from that blob to generating an image upload for CarrierWave?
I suppose I'm trying to avoid saving this image temporarily into "#{Rails.root}/tmp/" and then reading it from there... it seems like I could cut out this step and send directly to CarrierWave somehow, but I don't know how! Is it possible?
I'm working on something similar right now. This should be possible, but an easy workaround is to save it to a temp file:
temp_file = Tempfile.new([ 'temp', '.png' ])
image.write(temp_file.path)
user = User.new
user.avatar = temp_file
user.save
temp_file.close
temp_file.unlink
I'm hoping to try to improve it to remove the file system dependency completely, by following the advice in one of these answers: How to handle a file_as_string (generated by Prawn) so that it is accepted by Carrierwave?

Paperclip 4, Amazon S3 & rspec: how to stub file upload?

I'm writing tests with rspec and am a bit struggling with Paperclip 4. At the moment I'm using webmock to stub requests but image processing is slowing tests down.
Everything I read suggest to use stubs like so:
Profile.any_instance.stub(:save_attached_files).and_return(true)
It doesn't work since :save_attached_files disappeared with Paperclip 4 as far as I can tell.
What's the proper way to do it now ?
Thanks
Add this to your rails_helper.rb in your config block:
RSpec.configure do |config|
# Stub saving of files to S3
config.before(:each) do
allow_any_instance_of(Paperclip::Attachment).to receive(:save).and_return(true)
end
end
It doesn't exactly answer my question, but I found a way to run faster specs thanks to this dev blog, so I'm posting it if it can help someone else.
Just add this piece of code at the beginning of your spec file or in a helper. My tests are running 3x faster now.
# We stub some Paperclip methods - so it won't call shell slow commands
# This allows us to speedup paperclip tests 3-5x times.
module Paperclip
def self.run cmd, params = "", expected_outcodes = 0
cmd == 'convert' ? nil : super
end
end
class Paperclip::Attachment
def post_process
end
end
Paperclip > 3.5.2
For newer versions of Paperclip, use the following:
module Paperclip
def self.run cmd, arguments = "", interpolation_values = {}, local_options = {}
cmd == 'convert' ? nil : super
end
end
class Paperclip::Attachment
def post_process
end
end
I had a heckuva time dealing with this. I didn't want to test Paperclip per se -- rather, I needed a file to exist on my model so I can test a very sensitive custom class.
None of the other answers worked for me (Rails 5, Rspec 3.5) so I gave up on stubbing AWS or using the Paperclip::Shoulda::Matchers. Nothing worked and it ate up an entire day! Finally I gave up on Paperclip altogether and went this route:
list = build(:list)
allow(list).to receive_message_chain("file.url") { "#{Rails.root}/spec/fixtures/guess_fullname.csv" }
importer = Importer.new(list)
expect(importer.set_key_mapping).to eq({nil=>:email_address, :company=>:company, :fullname=>:full_name})
Where my models/list.rb has has_attached_file :file and Importer is a class I wrote that takes the file, which was already uploaded to S3 by paperclip before the class is initialized, and processes it. The file.url would otherwise be the S3 URL, so I give it the path in my repo to a testing csv file. set_key_mapping is a method in my class that I can use to verify the processing part worked.
Hope this saves somebody a few hours...
What about adding AWS.stub! in your spec/spec_helper.rb? Give that a try.

What's the proper way to copy a carrierwave file from one record to another?

I need to copy a file from one carrier wave object to another. They are different tables and different types of uploaders.
I started with:
user.avatar = image.content
(where user and image are model instances, avatar and content are the carrierwave mounted uploaders) which worked sometimes. It seems to work all the time locally, with a file storage, but intermittent when using fog and s3.
In a mailing list post I found this code:
user.avatar = image.content.file
that again worked sometimes.
My working solution so far is:
require "open-uri"
begin
user.avatar = open(image.url)
rescue Errno::ENOENT => e
begin
user.avatar = open(image.path)
rescue Errno::ENOENT => e
# Ok, whatever.
end
end
which is not only ugly, but fails to pass the extension validation because the opening of a remote file doesn't maintain the extension (jpg, png, etc.).
Perhaps one way you can do it is to set a remote image URL as per the Carrierwave gem documentation?
user.remote_avatar_url = image.url
From solutions discussed here I created simple CopyCarrierwaveFile gem to do this
usage is something like this:
original_resource = User.last
new_resource = User.new
CopyCarrierwaveFile::CopyFileService.new(original_resource, new_resource, :avatar).set_file
new_resource.save
nev_resource.avatar.url # https://...image.jpg
Here's a (albeit hacky) solution to that doesn't require an HTTP request to fetch the image:
module UploadCopier
def self.copy(old, new)
new.instance_variable_set('#_mounters', nil)
old.class.uploaders.each do |column, uploader|
new.send("#{column}=", old.send(column))
end
end
end
old_user = User.last
new_user = User.new
UploadCopier.copy(old_user, new_user)
new_user.save
I needed to copy a reference from one model to another model and I was successfully able to do so by doing the following:
my_new_model.update_column('attachment', my_other_model.attributes["attachment"]);
In this scenario, I did not care to actually make a copy of the file, nor did I care that 2 records were now linked to the same file (my system never deletes or modifies files after uploaded).
This may be useful to anyone who wants to just copy the reference to a file from one model to another model using the same uploader.
You can do this by copying files.
store_path is a carrierwave method from Uploader class. It returns uploaded file's folder relative path.
this clone file method should be called after model record is saved.
If record not saved, store_path may return wrong path if you specify store_dir with model id in uploader.
def clone_carrierwave_file(column_name)
origin_files = Dir[File.join(Rails.root, 'public', original_record.send(column_name).store_path, '*')]
return if origin_files.blank?
new_file_folder = File.join(Rails.root, 'public', send(column_name).store_path)
FileUtils.mkdir new_file_folder if !Dir.exist? new_file_folder
FileUtils.cp(origin_files, new_file_folder)
end
Hope it works.
I just wanted to copy an avatar reference from one object to another, and what worked for me was:
objectB.avatar.retrieve_from_store!(objectA.avatar.identifier)
objectB.save

Why isn't paperclip post processing working?

Hello I've been looking around at all the various tutorials out there for Paperclip post processing but somehow I can not get the 'Make' method to invoke.
Take a look at line 36 here... http://pastie.org/private/epfgcxywhyh4wpmozypg
It uploads normally without any errors or warnings but I can never see the puts statement in the make method which tells me that this isn't being invoked.
EDIT
I can run this in the model without a problem and I get True,
def class_exists?(class_name)
klass = Paperclip.const_get(class_name)
return klass.is_a?(Class)
rescue NameError
return false
end
Any ideas?
Two days ago I was facing the same problem. Here what I did to make it work:
Go to the command prompt and type: "which convert" command. This is ImageMagick command so if it says /usr/bin/convert then try adding
Paperclip.options[:command_path] = "/usr/bin"
in your config/environments/development.rb. Remove /convert from what you get over there.
then change name of your file file_contents.rb to paperclip_postprocess.rb and put it into directory: RAILS_ROOT/config/initializers/paperclip_postprocess.rb
You can cross check if your attachment is being processed or not by adding following lines in your model:
before_post_process :before_post_process
after_post_process :after_post_process
def before_post_process
puts "===========Before processing attachment==========="
end
def after_post_process
puts "-----------After processign attachment------------"
end
Take a look here
It worked for me at least.
I noticed this line in the Paperclip README:
NOTE: Because processors operate by turning the original attachment into the styles, no processors will be run if there are no styles defined.
And looking at your paste, you define everything but a :style argument, so maybe that's the problem?

Resources