write stream to paperclip
Asked Answered
W

7

8

I want to store received email attachment with usage of paperclip. From email I get part.body and I have no idea how to put it to paperclip'ed model. For now I create temporary file and write port.body to it, store this file to paperclip, and delete file. Here is how I do it with temporary file:

    l_file = File.open(l_path, "w+b", 0644)
    l_file.write(part.body)
    oAsset = Asset.new(
        :email_id => email.id, 
        :asset => l_file, 
        :header => h, 
        :original_file_name => o, 
        :hash => h)
    oAsset.save
    l_file.close
    File.delete(l_path)

:asset is my 'has_attached_file' field. Is there a way to omit file creation and to do something like: :asset => part.body in Asset.new ?

Weekley answered 2/3, 2011 at 11:24 Comment(0)
G
24

This is how I would do it, assuming your using the mail gem to read the email. you'll need the whole email 'part', not just part.body

file = StringIO.new(part.body) #mimic a real upload file
  file.class.class_eval { attr_accessor :original_filename, :content_type } #add attr's that paperclip needs
  file.original_filename = part.filename #assign filename in way that paperclip likes
  file.content_type = part.mime_type # you could set this manually aswell if needed e.g 'application/pdf'

now just use the file object to save to the Paperclip association.

a = Asset.new 
a.asset = file
a.save!

Hope this helps.

Griskin answered 4/3, 2011 at 0:47 Comment(4)
Thanks! I had to change one line to make your solution work. Correct line is: file = StringIO.open(part.body.to_s)Weekley
Nice solution. I used this myself but I was concerned about monkey-patching StringIO. See my response below for what I did instead.Interlocution
This code patches the StringIO class and pollutes any new instances of StringIO. I recommend following gtd's example, because it is safer, by only modifying the one instance of StringIO. It is unfortunate that paperclip doesn't really support StringIO without these modifications.Ossification
Hoaa, thank you. I'm having this problem since yesterday and now it has been solved.Hyperbaric
I
10

Barlow's answer is good, but it is effectively monkey-patching the StringIO class. In my case I was working with Mechanize::Download#body_io and I didn't want to possibly pollute the class leading to unexpected bugs popping up far away in the app. So I define the methods on the instances metaclass like so:

original_filename = "whatever.pdf" # Set local variables for the closure below
content_type = "application/pdf"

file = StringIO.new(part.body)

metaclass = class << file; self; end
metaclass.class_eval do
  define_method(:original_filename) { original_filename }
  define_method(:content_type) { content_type }
end
Interlocution answered 25/8, 2012 at 18:28 Comment(0)
D
7

I like gtd's answer a lot, but it can be simpler.

file = StringIO.new(part.body)

class << file
  define_method(:original_filename) { "whatever.pdf" }
  define_method(:content_type) { "application/pdf" }
end

There's not really a need to extract the "metaclass" into a local variable, just append some class to the object.

Dravidian answered 22/5, 2013 at 17:13 Comment(1)
You can also just create a new class for this. class FileString < StringIO; attr_accessor :original_filename, :content_type; endWimberly
F
2

From ruby 1.9, you can use StringIO and define_singleton_method :

def attachment_from_string(string, original_filename, content_type)
  StringIO.new(string).tap do |file|
    file.define_singleton_method(:original_filename) { original_filename }
    file.define_singleton_method(:content_type) { content_type }
  end
end
Flowerpot answered 24/5, 2016 at 7:49 Comment(0)
P
0

This would have been better as a comment on David-Barlow's answer but I don't have enough reputation points yet...

But, as others mentioned I didn't love the monkey-patching. Instead, I just created a new class that inherited from StringIO, like so:

class TempFile < StringIO
  attr_accessor :original_filename, :content_type  
end
Penny answered 22/5, 2019 at 21:5 Comment(0)
S
0

For posterity, here is the best answer. Put the top part in vendor/paperclip/data_uri_adapter.rb and the bottom part in config/initializers/paperclip.rb.

https://github.com/thoughtbot/paperclip/blob/43eb9a36deb09ce5655028a1061578dbf0268a5d/lib/paperclip/io_adapters/data_uri_adapter.rb

This requires a data URI scheme stream, but these days that seems pretty common. Simply set your paperclip'd variable to a string with the stream data, and the code takes care of the rest.

Snigger answered 12/5, 2021 at 17:53 Comment(0)
C
-1

I used a similar technique to pull down images into paperclip

this should work, but is obvs untested:

io = part.body
def io.original_filename; part.original_file_name || 'unknown-file-name'; end

asset = Asset.new(:email=>email)
asset.asset = io

When we are assigning the IO directly to the paperclip instance, it needs to have a .original_file_name to it, so that's what we're doing in the second line.

Cornemuse answered 2/3, 2011 at 16:2 Comment(2)
Doesn't seem to work :( even if I give :asset_file_name explicitly there is no file being created.Weekley
OK, you'll need to get part.body into an IO stream. try reading it. good luck!Cornemuse

© 2022 - 2024 — McMap. All rights reserved.