Easy Upload via URL with Paperclip
I’ve been using Paperclip to handle file uploads lately, and I wanted to be able to accept file “uploads” with a URL. We already knew how to accomplish this with attachment_fu, and getting it working in Paperclip wasn’t too difficult.
This example shows a Photo model that has an Image attachment.
The technique we’re using requires adding a *_remote_url (string) column for your attachment, which is used to store the original URL. So, in this case, we need to add a column named image_remote_url the photos table.
# db/migrate/20081210200032_add_image_remote_url_to_photos.rb
class AddImageRemoteUrlToPhotos < ActiveRecord::Migration
def self.up
add_column :photos, :image_remote_url, :string
end
def self.down
remove_column :photos, :image_remote_url
end
end
Nothing special is required for the controller...
# app/controllers/photos_controller.rb
class PhotosController < ApplicationController
def create
@photo = Photo.new(params[:photo])
if @photo.save
redirect_to photos_path
else
render :action => 'new'
end
end
end
In the form, we a add a text_field called :image_url, so people can upload a file or provide a URL...
# app/views/photos/new.html.erb
<%= error_messages_for :photo %>
<% form_for :photo, :html => { :multipart => true } do |f| %>
Upload a photo: <%= f.file_field :image %>
...or provide a URL: <%= f.text_field :image_url %>
<%= f.submit 'Submit' %>
<% end %>
The meaty stuff is in the Photo model. We need to require open-uri, add an attr_accessor :image_url, and do the normal has_attached_file stuff. Then, we add a before_validation callback to download the file in the image_url attribute (if provided) and save the original URL as image_remote_url. Finally, we do a validates_presence_of :image_remote_url, which allows us to rescue from the many exceptions that can be raised when attempting to download the file.
# app/models/photo.rb
require 'open-uri'
class Photo < ActiveRecord::Base
attr_accessor :image_url
has_attached_file :image # etc...
before_validation :download_remote_image, :if => :image_url_provided?
validates_presence_of :image_remote_url, :if => :image_url_provided?, :message => 'is invalid or inaccessible'
private
def image_url_provided?
!self.image_url.blank?
end
def download_remote_image
self.image = do_download_remote_image
self.image_remote_url = image_url
end
def do_download_remote_image
io = open(URI.parse(image_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
Everything will work as normal, including the creation of thumbnails, etc. Plus, since we're doing all of the hard stuff in the model, "uploading" a file via URL works from within script/console as well:
$ script/console
Loading development environment (Rails 2.2.2)
>> Photo.new(:image_url => 'http://www.google.com/intl/en_ALL/images/logo.gif')
=> #
Sweet.
Update: The example code has been updated to reflect thew suggestions left in the comments. The original_filename method is now defined on the fly. Thanks for the feedback! I've also split out a new method called do_download_remote_image, which can be used for stubbing out in tests with mocha:
Photo.any_instance.expects(:do_download_remote_image).returns(File.open("#{Rails.root}/testhttp://s3.amazonaws.com/almosteffortless/rails.png"))
This looks incredibly useful. One issue, though, is that I don’t think you need the image_url_provided? method. Rails gives you that method via image_remote_url? (with the question mark). In fact, you should be able to use the :allow_blank option for the validates_presence_of call.
Great and useful post.
Thanks for the comment, Tammer. Maybe I’m confused about your suggestion, but I don’t think it will quite work that way. We need to know if the user provided a URL, which we do by checking for a value in image_url. (Rails doesn’t seem to provide me with an image_url? method, perhaps because there’s no db column for the image_url attribute.) If they provided a URL, we try to download the file and verify that the download was successful by checking image_remote_url. Perhaps renaming image_url_provided? to image_url? would be better…?
Wow. It’s grate to know that uploading images via url is achivable. I haven’t heard of that and Moreover thank you very much for showing how to do it with parerclip. I will surely incorporate this feature in my next rails app. :-)
I confirm Tammer’s comment.
You can replace :image_url_provided? by :image_url? wich do exactly the same (!blank?).
Florian, I appreciate the suggestion, but I’m getting an undefined method error when I try to use image_url? without defining it myself. I’m guessing that technique you and Tammer mention only works with database-backed attributes.
Nice.
I’ve used rio[1] also.
[1]http://rio.rubyforge.org/
Hi Trevor !
A little hello from a (the only one ?) previous French coworker ! Oh yea and happy new year !!!
Go back to serious stuff. Thanks a lot for your article. However, I’ve got one little comment: the StringIO class does not have a “original_filename=” method (example from your download_remote_image method). The solution is to use the code from the attachment_fu version (open the StringIO class and add dynamically the method). But perhaps, I missed something.
I think you should fork paperclip from github and enhance the Upfile class to handle StringIO and more generally remote files. Just an idea (almost effort less ;-).
a+
Didier
Thanks !
This worked great :)
Hey Didier – nice to hear from you! It looks like the Paperclip plugin added the original_filename= method in a recent version. I can’t see what commit right now because github is down, but update to the latest version and you should see it in there. Thanks for the heads-up in any case!
Didier is right.
You should include the original_filename to IO Class from http://almosteffortless.com/2008/10/24/easy-upload-via-url-with-attachment_fu/
Pierre, this appears to be working fine for me. Please update your Paperclip plugin and let me know if you still have a problem. I believe this is the commit that added original_filename to the StringIO class:
http://github.com/thoughtbot/paperclip/commit/d92be0d894545a447728186e5f7141f32adcb883
You guys must have been right after all. I was experiencing some problems with URL uploads. I’m not sure why it’s necessary, but I’ve update the article to define the original_filename method on the fly, and now it’s working fine. Thanks for the heads up!
i liked your post so much and inspired by this effort i made a small modification in paperclip using this idea and sent a pull request to include this as a feature
you can read about this in my post here
http://bionuc-tech.blogspot.com/2009/03/uploading-images-from-url-using.html
[...] wasn’t very difficult. There’s some advance stuff you can do with Paperclip, like upload via a URL or upload your files to Amazon’s S3 or define post processing operations on your [...]
Was ist das?
Could this be any more awesome?! Thanks! Added to our production app!
Using paperclip for download image with a specific image url.. after following your steps..
I m getting this err whts the meaning of this man, what should i need to patch this gone.. ?
User model does not have required column ‘user_file_name’
Thanx in advance..
@naresh, make sure you have a working “normal” paperclip model before you try to do the “upload via url” additions. Follow the instructions provided in the paperclip readme and you should be good to go.
Thanks for a terrific post. This really saved me a ton of effort with an automated rake task pulling products in from an old store into Spree.
That was unbelievably effective.
Brilliant!