Easy Upload via URL with Paperclip

Posted by Trevor in Ruby/Rails on December 11, 2008

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 %><br>
  ...or provide a URL: <%= f.text_field :image_url %><br>
  <%= 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')
=> #<Photo image_file_name: "logo.gif", image_remote_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}/test/files/rails.png"))
 

16 Comments

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.

 Trevor

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…?

 Nuwan Chaturanga

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?).

 Trevor

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/

 Didier Lafforgue

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

 Pierre

Thanks !
This worked great :)

 Trevor

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!

 Pierre

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/

 Trevor

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

 Trevor

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!

 BioNuc

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 [...]

 grarceSkypece

Was ist das?

Could this be any more awesome?! Thanks! Added to our production app!

Leave a comment

WP_Big_City