Overriding uploader version with an image from a remote url - carrierwave

How can we override version method to extract an image from a URL instead of processing.
Basically, How can we do something like this:
version :normal do
if model.is_facebook_image?
#help needed
#get image from facebook graph api: http://graph.facebook.com/model.fb_id/picture?type=normal
else
process :scale => [100, 133]
end
end

Related

Why won't Carrierwave and MiniMagick images composite correctly?

I am using carrierwave (0.9.0) with fog (1.18.0), mini_magick (3.6.0), and Rackspace CloudFiles. I want to composite images.
I have two models: Products and Emails. In the products uploader I am trying to create a process that loops through all the emails and composites the product image on top of each email.background_image. I also want to control the name of this image so that is:
"#{email.name}_#{product.product_number}.jpg"
I have included my process and method below. Test result in a file being uploaded to cloudfiles named "email_123.png" and the original image. The name doesn't currently include the email's name and images are not compositing, I just receive the original back. Anything obvious I am missing here? Thanks!
version :email do
process :email_composite
def email_composite
Email.all.each do |email|
email_image = email.secondary.url
email_name = email.name
merge(email_name, email_image)
end
end
def merge(email_name, email_image)
manipulate! do |img|
#product = Product.find(model.id)
email_image = ::MiniMagick::Image.open(email_image)
img = email_image.composite(img, "jpg") do |c|
c.gravity "Center"
end
# img = yield(img) if block_given?
img = img.name "#{email_name}_#{#product.product_number}.png"
img
end
end
end
UPDATED
First I wanted to thank you for your thorough answer, the amount of effort involved is obvious and appreciated.
I had a few more questions when trying to implement this solution.
Inside the #compose method you have:
email_image = ::MiniMagick::Image.open(email_image)
Should this be:
email_image = ::MiniMagick::Image.open(email_image_url)
I assume this from seeing the define_method call included below ( which was awesome by the way - I didn't know you could do that):
define_method(:email_image_url) { email.secondary.url }
After getting Undefined method "versions_for" I switched the #versions_for method as I will always want all email to have a secondary version.
versions_for before
def self.versions_for emails
reset_versions!
email.find_each { |e| version_for_email(e) }
end
versions_for after changes
def self.versions_for
reset_versions!
Email.all.find_each { |e| version_for_email(e) }
end
This change allowed me get rid of the error.
The end result of all of this is that everything appears as if it works, I can upload imagery without error, but now I have no resulting image for the emails versions. I still get my other versions like thumb etc. Any idea what could cause this? Again thanks for all the help you have provided thus far.
Calling manipulate! multiple times within the same version applies the changes in its block to the same image; what you want is to create many versions, one corresponding to each Email. This is tricky because CarrierWave really wants you to have a small set of statically defined versions in your code; it actually builds a new, anonymous Uploader class to handle each version.
We can trick it to build versions dynamically, but it's pretty ugly. Also, we have to be careful to not keep references to stale uploader classes around, or we'll accumulate classes endlessly and eventually run out of memory!
# in ProductUploader:
# CarrierWave stores its version classes in two places:
#
# 1. In a #versions hash, stored within the class; and
# 2. As a constant in your uploader, with a name based on its #object_id.
#
# We have to clean out both of them to be sure old versions get garbage
# collected!
def self.reset_versions!
versions.keys.select { |k| k =~ /^email_/ }.each do |k|
u = versions.delete(k)[:uploader]
remove_const("Uploader#{u.object_id}".gsub('-', '_'))
end
end
def self.version_name_for email
"email_#{email.name}".to_sym
end
# Dynamically generate the +version+ that corresponds to the image composed
# with the image from +email+.
def self.version_for_email email
version(version_name_for(email)) do
process :compose
# Use +define_method+ here so that +email+ in the block refers to the
# argument "email" from the outer scope.
define_method(:email_image_url) { email.secondary.url }
# Use the same trick to override the filename for this version.
define_method(:filename) { "#{email_name}_#{model.product_number}.png" }
# Compose +email_image+ on top of the product image.
def compose
manipulate! do |img|
email_image = ::MiniMagick::Image.open(email_image_url)
# Do the actual composition.
img = email_image.composite(img, "jpg") do |c|
c.gravity "Center"
end
img
end
end
end
end
# Clear out any preexisting versions and generate new ones based on the
# contents of +emails+.
def self.versions_for emails
reset_versions!
email.find_each { |e| version_for_email(e) }
end
# After you call Product.versions_for(emails), you can use this to fetch
# the version for a specific email:
def version_for_email email
versions[self.class.version_name_for(email)]
end
To use this, be sure to call Product.versions_for(Email.all) (possibly in a before_filter), Then, you can access the version for a specific email with #p.product.version_for(email):
# in your controller:
def action
# Recreate a +version+ for each email.
# If you don't want to overlay *every* email's image, you can also pass a
# subset here.
ProductUploader.versions_for(Email.all)
# Upload a file and its versions to Cloud Files...
#p = Product.find(params[:id])
#p.product = File.open(Rails.root.join('clouds.jpg'), 'r')
#p.save!
end
In your views, you can use url or any other helpers as usual:
# in a view:
<% Email.all.find_each do |email| %>
<%= image_tag #p.product.version_for_email(email).url %>
<% end %>

CarrierWave: detect if an image has already been uploaded

A model is seeded with a remote url for an image, meaning the db entry it is not created in Rails. Then the first time it is fetched from the DB in Rails I want to detect that the image has not been uploaded, assign the remote_seed_url for the image url and save! to trigger the CarrierWave upload. I want to do this only once, obviously, but the code below sometimes uploads the same image more than once.
class Item
include Mongoid::Document
include Sunspot::Mongoid2
field :image, type: String
field :remote_seed_url, type: String #URL for image
mount_uploader :image, ImageUploader
end
Then in a controller
def show
# item = Item.find(params[:id])
# if CarrierWave has already uploded then do nothing
if !#item.image?
#item.image = #item.remote_seed_url # next save will trigger the upload?
#item.save!
end
respond_to do ...
end
The value of #item.image is usually "new" or "old" and #item.image? sometimes returns false when I can see that it has already uploaded the image. This causes the above code to upload multiple times.
Is the controller code correct to get the image uploaded only once?
Is there some way I might have messed things up and caused #item.image? to return false when it should be true? Maybe image aging?
After uploader is mounted on a field, when you call that field, you get an uploader object. If you want to check if there is a file, you should call "file" on that object:
[1] pry(main)> item.image.class
=> ImageUploader
[2] pry(main)> item.image.file.class
=> CarrierWave::SanitizedFile
[3] pry(main)> item.image.file.nil?
=> false

Carrierwave on the fly resize

I'm using carrierwave and I have this problem:
Suppose once the project has been delivered you need to add a section where the images in the system need to be displayed with a different size. I don' t want to regenerate the new dimension for each one of the images already in the system. I want to be able to generate (and cache it) whenever a view demands. Something like: " /> . If the new size 500x150 already exists, then returns the cached url, else generate it and return the cached url
I like pretty much Carrierwave but unfortunately doesn't have any on the fly resize feature out of the box. Everyone says it should be pretty simple add this feature but I found almost nothing. The only thing which goes pretty close is this uploader https://gist.github.com/DAddYE/1541912
I had to modify it to make it work so here is my version
class ImageUploader < FileUploader
include CarrierWave::RMagick
#version :thumb do
# process :resize_to_fill => [100,100]
#end
#
#version :thumb_square do
# process :resize_to_fill => [100,100]
#end
#
#version :full do
# process :resize_to_fit => [550, 550]
#end
def re_size(string_size)
if self.file.nil?
return self
end
begun_at = Time.now
string_size.gsub!(/#/, '!')
uploader = Class.new(self.class)
uploader.versions.clear
uploader.version_names = [string_size]
img = uploader.new(model, mounted_as)
img.retrieve_from_store!(self.file.identifier)
cached = File.join(CarrierWave.root, img.url)
unless File.exist?(cached)
img.cache!(self)
img.send(:original_filename=, self.file.original_filename)
size = string_size.split(/x|!/).map(&:to_i)
resizer = case string_size
when /[!]/ then :resize_to_fit
# add more like when />/ then ...
else :resize_to_fill
end
img.send(resizer, *size)
FileUtils.mv(img.file.file, cached)
#img.store!
end
img
end
def extension_white_list
%w[jpg jpeg gif png]
end
def filename
Digest::MD5.hexdigest(original_filename) << File.extname(original_filename) if original_filename
end
def cache_dir
"#{Rails.root}/tmp/uploads"
end
def default_url
'/general/no-image.png'
end
end
The problem with this version is that apparently when calling re_size("100x100").url, the url gets generated and returned before the actual resized image is created resulting in a page with broken links which displays good on any subsequent refresh.
Anyone achieved better results willing to share? :)
Please don't tell me to switch to Dragonfly. I'm using Carrierwave and i really like it. Also it seamlessly integrates with RailsAdmin which is part of my projects too.
Why don't you just generate a different version of the image, such as a thumbnail? In your image_uploader.rb
# Create different versions of your uploaded files:
include CarrierWave::RMagick
version :thumb do
process :resize_to_limit => [100, 100]
end
Then in your view just call
<%= image_tag nameofimage.image_url(:thumb).to_s %>
You could create multiple versions of our original image without resizing the original image. The processing is done by RMagick which you'll need to install.
RMagick requires you to have ImageMagick, so you'll need to install that as well. These can be a little tricky to get installed and working but well worth it. Plus the stackoverflow community has provided a lot of help with this issue.
Error installing Rmagick on Mountain Lion
rmagick gem install "Can't find Magick-config"

Rails Carrierwave acessing parent model from within a version

I'm trying to create a version on my carrierwave uploader that checks to see whether it's parent model has some data on how to resize and crop the image, if not do a default resize to fill. I've been trying to reference the model as demonstrated here: https://github.com/carrierwaveuploader/carrierwave
If I run the version code like this:
version :title do
if model.dimensions_hash["title"]
process :image_crop => [model.dimensions_hash["title"], 960, 384]
else
process :resize_to_fill => [960, 384]
end
end
I get this error:
NameError: undefined local variable or method `model' for #<Class:0x007f9eae7cfed0>
from /Users/RyanKing/Sites/test/app/uploaders/page_image_uploader.rb:45:in `block in <class:PageImageUploader>'
Line 45 being process :image_crop => [model.dimensions_hash["title"], 960, 384
If the if statement returns true why does the line 45 return an error? Am I referencing the model incorrectly?
I found a similar problem here but wasn't able to adapt it to my situation. Passing a parameter to the uploader / accessing a model's attribute from within the uploader / letting the user pick the thumbnail size
Well you are right model method wont be available because version is a
class method and whereas model is instance method of uploader
but there are way to get them
if you check the link that I have attached in the mail the all that is define inside block are class_eval so taking that into account you can modify your code like this
version :title do
process :image_crop => [960, 384],:if => :has_title?
process :resize_to_fill => [960, 384] ,:if => :has_not_title?
def has_title?
model.dimensions_hash["title"].present?
end
def has_not_title?
not(has_title?)
end
end
Hope this help

How can I access the raw request in rails and re-instantiate the request later in rails?

I want to access the request object before a file upload gets converted into a tempfile and add it to a queue to be processed later, mainly because IO objects can't be marshaled or serialized. How can I do this?
I'll also need to "redo" the request at later point. At what point in the rails request lifecycle would I hook into to do this?
Code example, in routes.rb:
post 'incoming_email' => Proc.new { |env|
RequestCache.create!(
data: env['rack.input'].read,
content_type: env['CONTENT_TYPE'],
content_length: env['CONTENT_LENGTH']
)
[200, {'Content-Type' => 'text/plain'}, ['OK']]
}
and in the model:
class RequestCache < ActiveRecord::Base
attr_accessible :data, :content_length, :content_type
def params
Rack::Request.new({
'rack.input' => StringIO.new(data),
'CONTENT_LENGTH' => content_length,
'CONTENT_TYPE' => content_type
}).POST
end
end
I don't know how feasible this is, but I would use rack middleware.
Well, just to do this you don't do something complicated like that.
If you want to do upload in the background process, you could simply use delayed_paperclip. This gem use delayed_job and process upload, resize, send to s3 (if you want) in the background process.

Resources