Easy Upload via URL with Paperclip
by Trevor Turk
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 '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
{ :multipart => true } do |f| %>
Upload a photo:
...or provide a URL:
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 :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