Parsing an Email Attachment w Paperclip - Possible w/o a tempfile? - ruby-on-rails

My rails 3 app on heroku receives incoming emails. I want to be able to accept attachments but can't get rails to process the attachments without erroring.
The ideal would be to pass the attachment provided by ActionMailer.
message_all = Mail.new(params[:message])
message_all.attachments.each do |a|
attachments.each do |a|
.attachments.build(
:attachment => a
)
end
end
It errors with: NoMethodError (undefined methodrewind' for #)`
Where attachments is a model, with attachment is paperclip
Ideas y? Is there a different way to pass the attachment = a , to paperclip?
I tried another approach, creating a tempfile:
tempfile = File.new("#{Rails.root.to_s}/tmp/#{a.filename}", "w+")
tempfile << a.body
tempfile.puts
attachments.build(
:attachment => File.open(tempfile.path) )
The problem with the tempfile is files without extentions "blah" instead of "blah.png" are breaking paperclip which is why I want to avoid the tempfile. and creating Identity errors, imagemagick doesn't know what they are w/o the ext.
hugely appreciate any advice on this.

The problem with the methods that you are using is that they don't contain all of the necessary information for paperclip like the content type and the original filename. I wrote a blog post about this a while back and how you could fake the format and use an email attachment as a paperclip attachment.
The bottom line was to do this:
file = StringIO.new(attachment)
file.class.class_eval { attr_accessor :original_filename, :content_type }
file.original_filename = attachment.filename
file.content_type = attachment.mime_type

Related

Paperclip dynamic url?

I have a Rails ActiveModel Product with a Paperclip image attachment column that needs to get it's image.url from 2 sources. One is an old S3 bucket/CloudFront, other is our new S3 bucket/CloudFront. They have completely different credentials.
If the instance Product.image_file_name is containing "old:" I want the URL to be something like cloudfront_url/products/file_name, if it doesn't - it should use the new S3 bucket/CloudFront. Upload is going to happen only on the new S3 bucket, but it'll fallback on the old one if image_file_name is containing old: as I mentioned.
Currently I'm authorized only with the new S3 bucket, not with the old one.
I have read that I should do something like:
class Product
has_attached_file: :image, url: dynamic_url_method
def dynamic_url_method
.... do some logic based on image_file_name
return constructed_url
end
end
However when I do that, I get undefined local variable dynamic_url_method.
If I wrap it in a lambda as said in https://stackoverflow.com/a/10493048 I get Error "no implicit conversion of Proc into String".
Have you guys successfully gotten Paperclip to work with a dynamic URL? It would be a life-saver if you know how to do so.
Completely scrap the whole idea of dynamic URL parameter given to the Paperclip attachment. It breaks S3 image uploading cause Paperclip can't figure out which URL to use.
The solution is to introduce a new column in your schema called image_url.
The column will be updated on initialize/update in the ActiveModel and used in the web pages.
In code
class Product
has_attached_file: :image
after_create :update_image_url
after_update :update_image_url
def update_image_url
new_image_url = # some logic based on image_file_name that would either return the CloudFront URL or save the URL from image.url which is generated by Paperclip
# Paperclip does not update image_file_name_changed? so we can't say
# after_create or after_update if: image_file_name_changed? instead
# we have to manually check that image_url and new_image_url are different
update(image_url: new_image_url) if image_url != new_image_url
end
end

bson_dump error when try to attach a file to mongoid paperclip field of a rails model

I have spent a lot of time trying to attach a file into a mongoid paperclip field. The examples that I have found, always do the same:
my_model_instance = MyModel.new
file = File.open(file_path)
my_model_instance.attachment = file
file.close
my_model_instance.save!
as you can see in How to set a file upload programmatically using Paperclip.
The model that I have done is:
class Logotype
include Mongoid::Document
include Mongoid::Paperclip
has_mongoid_attached_file :logo,
styles: {large: ['640x160'], small: ['300x300>']},
size: { in: 0..3.megabytes },
content_type: [ "image/jpg", "image/png", "image/bmp" ],
storage: :filesystem,
path: ':rails_root/public/resource/resources/:id/:style.:extension',
url: '/logos/resources/:id/:style.:extension'
validates_presence_of :logo
validates_uniqueness_of :logo
end
However I have always the same big error:
NoMethodError: undefined method `__bson_dump__' for /logos/resources/51b5bfaa69fd8a7941000005/original.jpg?1370865578:Paperclip::Attachment
...
Could someone help me? Thanks in advance!
PD: Sorry for my English level.
This is an old question but I just thought I'd share my solution in case someone else finds themselves here.
For me, this was happening in the Moped::BSON::Extensions::Hash module while trying to log the request for a save action. Your stack may be different, but basically the problem occurs when trying to dump data that Mongoid doesn't know how to handle. In my case it was a file upload, ActionDispatch::Http::UploadedFile, but for the OP it's a Paperclip::Attachment.
I solved it by just removing those key/value pairs from the request parameters, e.g.
# recursively remove UploadedFile from a Hash of request parameters
def remove_files(params)
p = proc do |_, v|
v.delete_if(&p) if v.respond_to? :delete_if
v.is_a?(ActionDispatch::Http::UploadedFile)
end
params.delete_if(&p)
end
There might be a better solution that logs the filename or something instead, but I found it easier just to get rid of it.

Using Rails Paperclip gem to preprocess file before saving

I'm using the Paperclip gem on a Rails 3.2 app where users can upload a specialized file type that contains an image and other information (let's call it a ".elf" file).
I've written all the code needed to extract an image from an elf file via a class called ElfObject, and this works well when tested in the app's console. What I want to do is extract the image and other data from the .elf file before Paperclip saves it to AWS S3, store the other data in the model, and then save just the image object as the Paperclip attachment on S3. Here's the relevant code in the model:
class Photo < ActiveRecord::Base
validates_attachment :attachment,
:presence => true
has_attached_file :attachment,
:storage => :s3,
[[S3 credentials removed]]
before_attachment_post_process :import_photo
attr_accessible :name, :attachment, :properties
def import_photo
if attachment_file_name =~ %r{^*\.elf$}
origfile = attachment.queued_for_write
elf = ElfObject.read(origfile)
properties = elf.get_properties
attachment = elf.image.write "image_for_#{attachment_file_name}.png"
save!
end
end
When I try to upload this way on the app, it raises the error ArgumentError (Invalid argument 'file'. Expected String, got Hash.) from the line elf = ElfObject.read(origfile). If I try something like elf = ElfObject.read(origfile.path), I get NoMethodError (undefined method `path' for #).
Clearly, I'm not fully understanding how to access the file from Paperclip prior to it being posted--any ideas on where I'm going wrong and how to fix it?
It seems like the problem is exactly what the error is saying... that origfile is a Hash, rather than a String.
If that is the case then attachment.queued_for_write returns a Hash, which means you need to find the key that holds the file path string.
origfile = attachment.queued_for_write
p origfile.inspect #print it out so you can figure out what key holds the path
Edited answer try this:
def import_photo
if attachment_file_name =~ %r{^*\.elf$}
origfile = attachment.queued_for_write[:original]
elf = ElfObject.read(origfile.path)
properties = elf.get_properties
attachment = elf.image.write "image_for_#{attachment_file_name}.png"
save!
end
end

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.

ActionMailer problem- what is the correct syntax for sending PDF attachments

What is the correct syntax for sending an email with actionmailer that includes some PDF file attachments? I am using Gmail for SMTP, with the TLS plugin. Here is what I have so far (and have tried variations on this too):
**lead_mailer.rb:**
def auto_response(lead)
recipients lead.email
subject "Information"
body :recipient => lead.first_name
from "me#mydomain.com"
attachment "application/pdf" do |a|
a.body = File.read(RAILS_ROOT + "/public/files/Datasheet.pdf")
end
attachment "application/pdf" do |a|
a.body = File.read(RAILS_ROOT + "/public/files/OtherSheet.pdf")
end
end
**lead_observer.rb:**
class LeadObserver < ActiveRecord::Observer
def after_save(lead)
mail = LeadMailer.create_auto_response(lead)
LeadMailer.deliver(mail)
end
end
The problem is that it sends the attachments, but they show up as "no name", even though upon opening them they appear correctly. However, the body of the email does not appear at all. I am sure I am doing something simple, wrong.
Okay I stepped away for a moment and came back and googled a little more and got the answer!
From the API:
Implicit template rendering is not performed if any attachments or parts have been added to the email. This means that you‘ll have to manually add each part to the email and set the content type of the email to multipart/alternative.
For the main mailer method I switched out body and added this explicit render. Note- I skipped the multipart/alternative and it worked, most likely because I am sending a plain text email.
part :body => render_message('auto_response', :recipient => lead.first_name)
For the attachment naming issue, this is what I did:
attachment "application/pdf" do |a|
a.body = File.read(RAILS_ROOT + "/public/files/OtherSheet.pdf")
a.filename = "Othersheet.pdf"
end

Resources