使用Paperclip保存文件但不上传

34
我有一个简短的问题。是否可能在不通过表单上传文件的情况下保存文件?
例如,假设我正在查看电子邮件附件,并且我想使用图钉保存它们。如何做到这一点?我需要手动调用save_file(或类似的函数)吗?
非常感谢您的帮助!
2个回答

49

我有一个 Rake 任务,可以将图片(客户标志)直接从一个目录加载到 Paperclip 中。你可能可以根据自己的需要进行调整。

这是我简化后的 Client 模型:

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

然后在我的Rake任务中我这样做:

# 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

敬礼!


4
使用File.new(path)可能导致意外情况。Paperclip不会关闭File.new实例,这可能会导致处理大量附件时出现“打开文件过多”的错误。正确的代码应该是:f = File.new(logo_path) client.logo = f f.close - Dan Gurgui
2
非常好的评论。我没有遇到这个问题,因为我在一个非常小的任务中使用了它,只有少量的文件。我已经更新了我的解决方案 - 我更喜欢尽可能使用File.open <block>而不是手动关闭。 - kikito

11
Paperclip保存的文件不必通过表单直接上传。我在项目中使用Paperclip从网络爬虫结果的URL中保存文件。如果您可以获取文件流(例如,在我的情况下通过open(URI.parse(crawl_result))),则可以将该文件附加到标记为has_attached_file的模型字段上。对于电子邮件附件(它们是否在服务器的本地文件系统上?您的应用程序是否像GMail一样是电子邮件应用程序?)我不确定如何获得,但只要能够获取文件流,就可以将其附加到模型字段上。可以参考这篇关于使用Paperclip通过URL进行简单上传的博客文章,其中包含示例代码。需要注意的是,此技术需要添加一个*_remote_url(字符串)列,用于存储原始URL。因此,在本例中,我们需要在照片表中添加名为image_remote_url的列。
# 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

控制器没有什么特别的要求...

# 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
在表单中,我们添加了一个名为:image_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 %>

重点在于Photo模型中。我们需要require open-uri,添加一个attr_accessor :image_url,并进行正常的has_attached_file操作。然后,我们添加一个before_validation回调函数,在image_url属性中下载文件(如果提供了)并将原始URL保存为image_remote_url。最后,我们执行validates_presence_of :image_remote_url,这允许我们从尝试下载文件时可能引发的许多异常中进行救援。

# 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

一切都会像往常一样运作,包括缩略图的创建等等。而且,由于我们在模型中执行了所有艰难的工作,因此通过URL“上传”文件也可以从脚本/控制台中进行:

$ 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">

@Nate 谢谢你的评论。看起来链接已经失效了? - Russell Silva
这对于下载大文件来说是非常占用内存的。其次,对于小于10KB的文件,返回StringIO,如果paperclip进行内容类型验证,则会失败,因为字符串没有内容类型。 - maletor
太好了,谢谢。这个评论帮了我很多,“不要把它和attr_accessible混淆!attr_accessor和attr_accessible之间的区别https://dev59.com/43A75IYBdhLWcg3wubqn”。 - Perry Horwich
非常感谢。这真的帮了我很多。 - Krishna Rani Sahoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接