Moving Paperclip S3 attachments on Heroku
I'm trying to do something very simple using Heroku, Paperclip and S3 - set one model's attachment to equal another's.
Here's a custom rake task I put together:
task :migrate => :environment do
@companies = Company.where("attachment_file_name IS NOT NULL")
@companies.each do |c|
if c.attachments.where("attachment_file_name = ?", c.attachment_file_name).blank?
# i.e. if there are no instances of Attachment that match c.attachment
a = Attachment.new( :company_id => c.id, :name => "Default" )
a.attachment = c.attachment
a.save
end
end
end
So, I'm trying to move Company.attachment to a new instance of the new Attachment model. On my local development server, it works beautifully.
Once pushed to Heroku, I'm getting the following error pointing to the line a.attachment = c.attachment
.
The specified key does not exist.
I try the operation manually for a company that has an attachment in the heroku console and I get:
TypeError: can't convert nil into String
/app/.bundle/gems/ruby/1.8/gems/paperclip-2.3.6/lib/paperclip/storage/s3.rb:131:in `extname'
/app/.bundle/gems/ruby/1.8/g开发者_如何学Goems/paperclip-2.3.6/lib/paperclip/storage/s3.rb:131:in `to_file'
/app/.bundle/gems/ruby/1.8/gems/paperclip-2.3.6/lib/paperclip/attachment.rb:81:in `assign'
/app/vendor/plugins/paperclip/lib/paperclip.rb:245:in `attachment='
Do you know what's going on here?
I just tried c.attachment = c.attachment
. Same error!!!
Looks like c.attachment_file_name
is coming up nil and paperclip doesn't know what to do with it. I'm not sure why it's nil but to get around it you can just check to see if it's nill and skip it if it is:
if c.attachment_file_name
if c.attachments.where("attachment_file_name = ?", c.attachment_file_name).blank?
# i.e. if there are no instances of Attachment that match c.attachment
a = Attachment.new( :company_id => c.id, :name => "Default" )
a.attachment = c.attachment
a.save
end
end
Have you considered modifying your paperclip model to accept a URL as an attachment? That way you can port over your attachements to a new model and not radically modify the paperclip s3 storage mechansim.
Add this to your new model:
before_validation :download_remote_attachment, :if => :attachment_url_provided?
...
attr_accessor :attachment_url
private
def attachment_url_provided?
!self.attachment_url.blank?
end
def download_remote_attachment
self.file = do_download_remote_image
end
def do_download_remote_attachment
io = open(attachment_url)
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue
end
Then to create a new attachment object, pass it the parameter :attachment_url and it will download it, re-process it, and store it as an attachment for the new model. The only downside to this is that the attachment will be stored twice on S3. Depending on your app requirements thought that might be a good thing
One more long shot in the dark here, from the "why does it work with development db and not production db" angle. Any chance attachment
is made available via a has_many :through
relationship? Have read this type of bizarre error can occur with mySQL if the join table has a primary key added to it. Will work with sqLite3 though. So, once you go to production, the error is seen. Just a thought.
精彩评论