Saving files using Paperclip without upload
Asked Answered
B

2

34

I had a quick question. Is it possible to save a file without actually uploading it through a form?

For example, let's say I'm looking at attachments from emails, and I want to save them using a paperclip. How do I do this? Do I manually have to call a save_file(or something similar) somewhere?

Any help would be much appreciated!

Babysitter answered 3/11, 2009 at 11:29 Comment(0)
W
49

I have a rake task that loads images (client logos) from a directory directly onto parperclip. You can probably adapt it to your needs.

This is my simplified Client model:

class Client < ActiveRecord::Base
  LOGO_STYLES = {
    :original => ['1024x768>', :jpg],
    :medium   => ['256x192#', :jpg],
    :small    => ['128x96#', :jpg]
  }

  has_attached_file :logo,
    :styles => Client::LOGO_STYLES,
    :url => "/clients/logo/:id.jpg?style=:style"
  attr_protected :logo_file_name, :logo_content_type, :logo_size

Then on my rake task I do this:

# the logos are in a folder with path logos_dir
Dir.glob(File.join(logos_dir,'*')).each do |logo_path|
  if File.basename(logo_path)[0]!= '.' and !File.directory? logo_path

    client_code = File.basename(logo_path, '.*') #filename without extension
    client = Client.find_by_code(client_code) #you could use the ids, too
    raise "could not find client for client_code #{client_code}" if client.nil?

    File.open(logo_path) do |f|
      client.logo = f # just assign the logo attribute to a file
      client.save
    end #file gets closed automatically here
  end
end

Regards!

Whitehot answered 3/11, 2009 at 16:24 Comment(2)
Using File.new(path) leads to unwanted situations. Paperclip never closes the File.new instance and this might lead to errors such as "Too many open files" when processing lots of attachments. The correct code should be f = File.new(logo_path) client.logo = f f.closeRockies
Very good comment. I didn't hit that problem because I used this on a very small task with a small number of files. I've updated my solution - I prefer using File.open <block> whenever possible instead of manually closing.Whitehot
T
11

The file saved in Paperclip doesn't have to be uploaded directly through a form.

I'm using Paperclip in a project to save files from URLs from webcrawler results. I'm not sure how you'd get email attachments (are they on the local file system of the server? Is your app an email app like GMail?) but as long as you can get a file stream (via something like open(URI.parse(crawl_result)) in my case...) you can attach that file to your model field that's marked has_attached_file.

This blog post about Easy Upload via URL with Paperclip helped me figure this out.

Since it now appears the original blog post is no longer available - here's the gist of it pulled from wayback machine:

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 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">
Tulle answered 3/11, 2009 at 12:4 Comment(3)
@Tulle Thanks for this comment. Looks like the link no longer works?Asclepius
This is pretty memory intensive for downloading large files. Secondly, a StringIO is returned for files less than 10KB and if paperclip does content type validation this will fail because Strings don't have content types.Aniela
Excellent. Thanks. This comment helped me "Don't get it mixed up with attr_accessible! Difference between attr_accessor and attr_accessible #3136920"Unconformable

© 2022 - 2024 — McMap. All rights reserved.