Rails Paperclip使用动态存储桶名称与S3

8

我正在使用Paperclip将我的文档上传到Amazon S3。当我上传新文档时,我想自动创建一个与我的项目ID相对应的存储桶。

因此,在我的控制器中,我有以下代码:

 def new
    @pmdocument = Pmdocument.new
    @pmdocument.projectmilestone_id=params[:projectmilestone_id]

其中projectmilestone_id是我的项目的外键(用作我的存储桶名称)

我的模型如下:

class Pmdocument < ActiveRecord::Base
  belongs_to :projectmilestone
  attr_accessible :id, :name, :description, :projectmilestone_id, :pmdoc, :projectmilestone_attributes
  attr_protected :pmdoc_content_type, :pmdoc_size
  accepts_nested_attributes_for :projectmilestone, :allow_destroy => false
  has_attached_file :pmdoc,
    :storage => :s3,
    :bucket => self.projectmilestone_id.to_s,
    :s3_credentials => File.join(Rails.root, 'config', 's3.yml')

当我加载页面时,出现以下错误: undefined method `projectmilestone_id' for #
我检查了我的控制器,projectmilestone_id字段在那里正确加载。
我尝试将bucket行更改为:bucket => self.name,然后错误消失了。
模型工作正常,因为projectmilestone_id在数据库中正确存储。
我的猜测是可能与可访问属性有关,但它似乎也没问题。
到底是什么问题?非常感谢!
我真的不明白:
我决定不再更改我的bucket(这是一个坏主意,因为所有S3的名称都需要唯一),而是改变我的路径。
这是代码:
:path => proc { |attachment| "#{attachment.istance.projectname}/:attachment/:id/:basename.:extension" },

我的项目名称下的第一个文件夹没有被创建。如果我将projectname替换为name,甚至是description(pmdocuments的另一个字段),就可以实现创建文件夹,但是projectname无法创建文件夹。当然,我检查了projectname是否正确填充。原因在其他地方。

有什么线索吗?

1个回答

11
has_attached_file方法在类的上下文中执行(当文件被加载时),而不是在记录实例的上下文中,您可以在其中使用属性和其他实例方法。self.name确实有效,但它返回类的名称("Pmdocument"),而不是记录的名称。
但是,Paperclip很友好地允许您想要的内容。关于S3存储的文档中说:

如果您想在运行时确定其名称,则可以将bucket定义为Proc。Paperclip将以attachment作为唯一参数调用该Proc。

在您的情况下,应该像这样:
has_attached_file :pmdoc,
  :storage => :s3,
  :bucket => proc { |attachment| attachment.instance.projectmilestone_id.to_s },
  :s3_credentials => File.join(Rails.root, 'config', 's3.yml')

现在你将Proc传递给has_attached_file。块的内容在加载类时不会被评估,而是在需要时稍后评估。然后,Paperclip使用attachment作为参数调用该块,并使用返回的值作为存储桶名称。

编辑:

不幸的是,这个块在文件被分配时运行,而不是在记录保存时运行。因此,可能尚未设置所有属性(当您执行Pmdocument.new(params[:pmdocument])时,属性分配的顺序是不确定的)。我希望Paperclip能够以另一种方式工作,但同时我看到了两个选择:

您可以从控制器中的params中删除文件,并在其他所有内容准备就绪时再设置它:

pmdoc = params[:pmdocument].delete(:pmdoc)
@pmdocument = Pmdocument.new(params[:pmdocument])
@pmdocument.pmdoc = pmdoc

或者您可以通过使用before_post_process(请参见README中的事件部分)禁用Paperclip后处理,并在after_save回调中运行它来延迟处理。


非常感谢您提供的非常有用的解释。我是Rails和OO编程的新手,还没有完全掌握。我按照您上面所说的做了,但显然,我不能创建一个以数字为名称的bucket。因此,我尝试使用我的关系来返回项目名称:proc { |attachment| attachment.instance.projectmilestone.projectcapstone.project.name.to_s },但我又遇到了另一个错误:“未定义方法'projectcapstone' for nil:NilClass”。这很奇怪,因为我的类似乎不为空,因为它可以在不经过关系的情况下工作。这次出了什么问题?谢谢! - ndemoreau
块可能在您分配文件时执行(例如像 pmdocument.pmdoc = params[...]pmdocument.update_attributes(...) 这样的一行)。确保在此分配之前,您的对象具有有效的 projectcapstone - Michaël Witrant
事实上,配置选项实际上可以使用带有参数的lambda/proc(即附件)来保存我的生命。非常感谢!<3 - dimitarvp

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