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}/testhttp://s3.amazonaws.com/almosteffortless/rails.png"))
 

20 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!

 naresh

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

 Trevor

@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.

Leave a comment

WP_Big_City