Attaching local or remote files to Paperclip and Milton Models in Rails (Mocking content_type and original_filename in a Tempfile)
I was working on a project today where I needed to import some data from MySpace accounts (yeah, MySpace), which included importing the users profile image. In the controller that did the importing I was using OpenURI to retrieve the image and then turn it into a Tempfile to be attached the the model, like so:
1 2 3 4 5 6 7 | def import #...snip tempfile = Tempfile.new( my_filename) tempfile.write open( image_url ).read @imported_user.images.create(:file => tempfile) #...snip end |
This doesn't work. It blows up missing one of two methods:
- #original_filename
- #content_type
If you inspect a normal file upload in Rails which has these methods, you'll find that is just a regular old Tempfile. But it has the methods! If you create a Tempfile manually, it won't have the methods. That's because Rails magics them on. I am not sure why they don't create a subclass like Rails::Tempfile that contains these methods and just use that. I guess its because OOP is retarded (sarcasm).
So, I wrote a little subclass that will take a file path, and quack like the magic'd rails Tempfiles you get from an upload so you can attach local files to models or even remote files.
This works pretty straightforward and I'm currently using it in production. It won't work on windows systems because the dependency on the 'file' binary. Also, some linux systems are missing this library by default, so make sure you yum|dpkg|apt|port or whatever to get it installed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | require 'open-uri' require 'digest/sha1' class RemoteFile < ::Tempfile def initialize(path, tmpdir = Dir::tmpdir) @original_filename = File.basename(path) @remote_path = path super Digest::SHA1.hexdigest(path), tmpdir fetch end def fetch string_io = OpenURI.send(:open, @remote_path) self.write string_io.read self.rewind self end def original_filename @original_filename end def content_type mime = `file --mime -br #{self.path}`.strip mime = mime.gsub(/^.*: */,"") mime = mime.gsub(/;.*$/,"") mime = mime.gsub(/,.*$/,"") mime end end |
Usage is pretty simple:
1 2 3 | remote_file = RemoteFile.new("http://www.google.com/intl/en_ALL/images/logo.gif") remote_file.original_filename #=> logo.gif remote_file.content_type #= image/gif |
Using it in your controller:
1 2 3 4 5 | def import #...snip @imported_user.images.create(:file => RemoteFile.new( url_to_image )) #...snip end |
May 6th, 2010 - 14:35
Thanks, you just saved me a great deal of hair-pulling. It’s working great with Paperclip.
June 6th, 2010 - 14:09
Thanks, needed this for importing images for products in Spree.
January 19th, 2011 - 04:32
I am sorry, but there is a solution which is far more easy than yours:
require ‘open-uri’
@imported_user.images.create(:file => open(url_to_image))
January 24th, 2011 - 17:59
Not sure it works exactly… using the ‘open’ method you would still be missing the ‘original_filename’ method…
February 12th, 2011 - 08:21
Thanks very much for this – I didn’t have a problem with #original_filename and #content_type being missing (they seem to be present for me even with manually created Tempfiles), but I was wrestling with forcing a nice filename* for the upload without rolling my own temp file solution; I hadn’t realised it was as simple as overriding the #original_filename method.
August 14th, 2011 - 17:11
Thanks for posting this Corey. Ran into a situation where I needed to fetch and process remote images from Facebook with Paperclip and this helped me out tremendously.
October 19th, 2012 - 09:47
what about just doing something like this
tempfile.singleton_class.class_eval {attr_accessor
riginal_filename, :content_type}
tempfile.original_filename, tempfile.content_type = file.original_filename, file.content_type