Why won't Carrierwave and MiniMagick images composite correctly? - carrierwave

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

Related

How do I ask Paperclip if I'm uploading a new file?

I want to do some processing on an image the first time it's uploaded. The model can be updated without updating the image and I want to avoid extra processing in those situations. How can I write a check to only process the image if the image is new, like being replaced?
Something like:
def update
#model = Model.find(params[:id])
#model.process_image if #model.image_is_different_from_existing?
...
end
There's one way if the photos you are uploading have unique name, Let's say paperclip attribute name is image, paperclip creates a column named image_file_name, before saving you can check whether the image file name present in your database matches with the name of image file you are uploading or not. I've implemented something like this, below is the snippet
product_images = product.product_attachments.pluck(:photo_file_name)
unless product_images.include?("name_of_the_image_i_am_uploading")
product.product_attachments.create!(:photo => File.open("/home/rajdeepb/Photo/#{data_array[1]}")) if all_images.include?("/home/rajdeepb/Photo/#{name_of_the_image_i_am_uploading}")
end
THis might not be the perfect solution but it may be helpful to you
I've written a solution that looks for relevant params in the update or create method.
Something like this:
user.rb
def attachments
%w(banner footer logo)
end
users_controller.rb
def new_resources?
attaching = []
#user.attachments.each do |a|
attaching << a if params[:user][a].present?
end
return true unless attaching.empty?
end
def attaching
[]
end
def update
...
if #user.request_necessary? && new_resources?
action_response << "Server updated: new #{attaching} uploaded."
redirect_to users_path, notice: action_response.join(' ')

How to save a raw_data photo using paperclip

I'm using jpegcam to allow a user to take a webcam photo to set as their profile photo. This library ends up posting the raw data to the sever which I get in my rails controller like so:
def ajax_photo_upload
# Rails.logger.info request.raw_post
#user = User.find(current_user.id)
#user.picture = File.new(request.raw_post)
This does not work and paperclip/rails fails when you try to save request.raw_post.
Errno::ENOENT (No such file or directory - ????JFIF???
I've seen solutions that make a temporary file but I'd be curious to know if there is a way to get Paperclip to automatically save the request.raw_post w/o having to make a tempfile. Any elegant ideas or solutions out there?
UGLY SOLUTION (Requires a temp file)
class ApiV1::UsersController < ApiV1::APIController
def create
File.open(upload_path, 'w:ASCII-8BIT') do |f|
f.write request.raw_post
end
current_user.photo = File.open(upload_path)
end
private
def upload_path # is used in upload and create
file_name = 'temp.jpg'
File.join(::Rails.root.to_s, 'public', 'temp', file_name)
end
end
This is ugly as it requires a temporary file to be saved on the server. Tips on how to make this happen w/o the temporary file needing to be saved? Can StringIO be used?
The problem with my previous solution was that the temp file was already closed and therefore could not be used by Paperclip anymore. The solution below works for me. It's IMO the cleanest way and (as per documentation) ensures your tempfiles are deleted after use.
Add the following method to your User model:
def set_picture(data)
temp_file = Tempfile.new(['temp', '.jpg'], :encoding => 'ascii-8bit')
begin
temp_file.write(data)
self.picture = temp_file # assumes has_attached_file :picture
ensure
temp_file.close
temp_file.unlink
end
end
Controller:
current_user.set_picture(request.raw_post)
current_user.save
Don't forget to add require 'tempfile' at the top of your User model file.

Adding tags to images in RefineryCMS

I'm trying to add tags to the Image model in RefineryCMS (trying on 1.0.8 and 2.0.4), have added attr_accessible :tag_list, required acts-as-taggable and setup the views, but the problem is that the tags only save when editing/updating a previously uploaded image - not when uploading for first time, even though it uses the same form...
Any ideas?
It happens on every version of rails and Refinery I have tried...
The tags are going through in the post when looking at logs, just not saving...
I had a similar issue and eventually find the cause of the extra-attributes (in your case the :tag_list) not being saved on new image upload.
If you look at ::Refinery::ImageController you'll see that the create action actyally create the image with :
unless params[:image].present? and params[:image][:image].is_a?(Array)
#images << (#image = ::Refinery::Image.create(params[:image]))
else
params[:image][:image].each do |image|
#images << (#image = ::Refinery::Image.create(:image => image))
end
end
params[:image][:image] is an Array when multiple multiple file uploed is enabled (by default it is). But then the action only use take the array values when creating the images, ignoring the other params.
I quickly write the below work-around that allow to save the other params on multiple image upload :
unless params[:image].present? and params[:image][:image].is_a?(Array)
#images << (#image = ::Refinery::Image.create(params[:image]))
else
images_params = params[:image].dup
images_params.delete(:image)
params[:image][:image].each do |image|
#images << (#image = ::Refinery::Image.create({:image => image}.merge(images_params)))
end
end
It's probably not the most elegant solution bu it does the trick.
To use it in your app, you'll have to create a decorator for the ::Refinery::ImageController to copy and edit the create action in it. (see 'Extending a Controller' in Refinery's Guides)

how can I upload and parse an Excel file in Rails?

I want to be able to upload an Excel file that contains contact information. I then went to be able to parse it and create records for my Contact model.
My application is a Rails application.
I am using the paperclip gem on heroku, I've been able to parse vim cards into the Contact model, and am looking for something similar, but will go through all lines of the Excel file.
Gems that simplify the task and sample code to parse would be helpful!
Spreadsheet is the best Excel parser that I have found so far. It offers quite a lot of functionality.
You say you use Paperclip for attachments which is good. However, if you store the attachments in S3 (which I assume since you use Heroku) the syntax for passing the file to spreadsheet is a little different but not difficult.
Here is an example of the pure syntax that can be used and not placed in any classes or modules since I don't know how you intend to start the parsing of contacts.
# load the gem
require 'spreadsheet'
# In this example the model MyFile has_attached_file :attachment
#workbook = Spreadsheet.open(MyFile.first.attachment.to_file)
# Get the first worksheet in the Excel file
#worksheet = #workbook.worksheet(0)
# It can be a little tricky looping through the rows since the variable
# #worksheet.rows often seem to be empty, but this will work:
0.upto #worksheet.last_row_index do |index|
# .row(index) will return the row which is a subclass of Array
row = #worksheet.row(index)
#contact = Contact.new
#row[0] is the first cell in the current row, row[1] is the second cell, etc...
#contact.first_name = row[0]
#contact.last_name = row[1]
#contact.save
end
I had a similar requirement in one of my Rails 2.1.0 application. I solved it in the following manner:
In the 'lib' folder I wrote a module like this:
require 'spreadsheet'
module DataReader
def read_bata(path_to_file)
begin
sheet = book.worksheet 0
sheet.each 2 do |row|
unless row[0].blank?
# Create model and save it to DB
...
end
end
rescue Exception => e
puts e
end
end
end
Had a model Upload:
class Upload < AR::Base
has_attached_file :doc,
:url => "datafiles/:id",
:path => ":rails_root/uploads/:id/:style/:basename.:extension"
# validations, if any
end
Generated an UploadsController which would handle the file upload and save it to appropriate location. I used Paperclip for file upload.
class UploadsController < AC
include DataReader
def new
#upload = Upload.new
end
def create
#upload = Upload.new(params[:upload])
#upload.save
file_path = "uploads/#{#upload.id}/original/#{#upload.doc_file_name}"
#upload.read = DataReader.read_data(file_path)
# respond_to block
end
end
Read about 'spreadsheet' library here and here. You can make appropriate improvements and make the technique work in Rails 3. Hope this helps.
I made a gem to achieve this easily. I called it Parxer and...
It's built on to of roo gem.
It allows you to parse xls, xlsx and csv files.
Has a DSL to handle:
Column mapping.
File, row, and column/cell validation.
Column/cell formatting.

How do I download a remote image from another site to a file_column in Ruby on Rails?

first question, hopefully I don't mess it up :)
A bit of a Ruby on Rails newbie (also Ruby newbie) and have stumbled upon a problem with the intended behavior of the application.
I have a file_column :image in model picture that belongs to model product, which can have many pictures.
The file_column works just fine when used as I think it's meant to be used and that's for uploading image using <%= file_column_field "picture", "image" %> etc. That part works just fine.
The problem comes with the intention of having a text field where user can enter a css -selector for an image tag on their site (they've registered the site and the path to the page where the image should be). I haven't been able to figure out how to properly download the image from that other site "under the hood".
Using these two methods both result in Do not know how to handle a string with value 'GIF89ad..... followed by loads of "binary".
Method 1:
url = URI.parse(picture_www.external_url)
Net::HTTP.start(url.host, url.port) {|http|
resp = http.get(url.path)
picture_www.image = resp.body unless resp.nil?
}
Method 2:
res = open(picture_www.external_url)
picture_www.image = res.read unless res.nil?
The external_url contains the correct url and the download goes ok, so the problem seems to be in the way I'm trying to assign the image to the file_column field. Naturally the problem could be the way I'm downloading the image, I have no idea TBH where the problem actually lies... :)
Anyone able to help me please?
Update:
Trying to use a tempfile "causes undefined method 'original_filename' for" etc
Net::HTTP.start(url.host, url.port) {|http|
resp = http.get(url.path)
tempfile = Tempfile.new('test.jpg')
File.open(tempfile.path, 'wb') do |f|
f.write resp.body
end
picture_www.image = tempfile unless resp.nil?
}
Update2:
Debugging shows me that an uploaded file has attributes #content_type ("image/jpeg" for instance) and #original_path (file name without path) under #_dc_obj and #tmpfile when the tempfile I created does not. Setting these properly would perhaps make this work? How do I set those properly? And if setting those values properly, would the file downloading be done "properly"? After ofcourse re-structuring the code once I get a working solution.
Update3:
From Minver's answer I got the solution for "original_filename" issue and this code seems to work:
io = open(picture_www.external_url)
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
picture_www.image = io
No idea though, if this is the "proper" way to do this or not, but this is what I'll be using for now unless some "clearly the right way to do it" solution appears :)
-Pkauko
The UrlUpload method by Joe Martinez is a good solution but the code is missing a key method. If you over-ride the method_missing, you should always also over-ride the respond_to? method as well. In this case it is especially important since some software uses respond_to? when deciding whether to do a multipart-post.
For example, the Faraday gem does this:
def has_multipart?(body)
body.values.each do |v|
if v.respond_to?(:content_type)
return true
elsif v.respond_to?(:values)
return true if has_multipart?(v)
end
end
false
end
So, if you are going to use the UrlUpload code above, I suggest you add the following method:
def respond_to?(symbol)
attachment_data.respond_to?(symbol) || super
end
Then Faraday and other related gems will be able to use an instance of this class to generate a proper multipart-post.
I don't know but maybe this is what you are looking for. When you save the image you provide a css_selector and gets a image file in return.
This is the view:
<%= form_for(#image) do |f| %>
<div class="field">
<%= f.label :css_selector %><br />
<%= f.text_field :css_selector %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
and this is the model:
class Picture < ActiveRecord::Base
require 'open-uri' # Required to download the photo
require 'mechanize' # Good gem to parse html pages
belongs_to :product
# Define the css_selector (not required as a filed in the database)
attr_accessor :css_selector
# Before we save the image, we download the photo if image has a css_selector value
before_save :download_remote_photo, :if => :css_selector_provided?
private
# Check if the attribute is provided
def css_selector_provided?
!self.css_selector.blank?
end
# This method opens the page where the photo is
# and grab the url to the image using a css-selector
def fetch_photo_url
agent = Mechanize::new
page = agent.get(HERE_IS_THE_URL_TO_THE_PAGE_YOU_WANNA_SCRAPE)
doc = Nokogiri::HTML(page.body)
image_element = doc.at_css(self.css_selector) # Get the image on that page using the css selector
image_url = image_element[:src]
end
def download_remote_photo
self.image = do_download_remote_photo(fetch_photo_url)
end
def do_download_remote_photo(photo_url)
io = open(URI.parse(URI.escape(photo_url)))
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...)
end
end
Haven't tested the code but I hope you get the idea!
Here ya go
require 'open-uri'
class UrlUpload
EXTENSIONS = {
"image/jpeg" => ["jpg", "jpeg", "jpe"],
"image/gif" => ["gif"],
"image/png" => ["png"]
}
attr_reader :original_filename, :attachment_data
def initialize(url)
#attachment_data = open(url)
#original_filename = determine_filename
end
# Pass things like size, content_type, path on to the downloaded file
def method_missing(symbol, *args)
if self.attachment_data.respond_to? symbol
self.attachment_data.send symbol, *args
else
super
end
end
private
def determine_filename
# Grab the path - even though it could be a script and not an actual file
path = self.attachment_data.base_uri.path
# Get the filename from the path, make it lowercase to handle those
# crazy Win32 servers with all-caps extensions
filename = File.basename(path).downcase
# If the file extension doesn't match the content type, add it to the end, changing any existing .'s to _
filename = [filename.gsub(/\./, "_"), EXTENSIONS[self.content_type].first].join(".") unless EXTENSIONS[self.content_type].any? {|ext| filename.ends_with?("." + ext) }
# Return the result
filename
end
end
# Make it always write to tempfiles, never StringIO
OpenURI::Buffer.module_eval {
remove_const :StringMax
const_set :StringMax, 0
}

Resources