9
votes

I have an external service that creates files and stores them into S3 (that my Rails app has access too).

I want to be able to use Paperclip in order to store that S3 object, as well as a thumbnail of it. I haven't been able to find documentation on how to use Paperclip with files that are already on S3. So, my code should look like:

page = Page.find(3)
page.s3_url = s3_url # I have the s3 route. And I would need to generate a thumbnail too.
page.save

So, in other words:

How can I tell PaperClip that my attachment is already in S3?

EDIT:

Here is what I have so far:

I know the file_name, the content_length and the content_type of the file that is already uploaded in S3. So, since the association is a Page has_attached_file :screenshot

This is what I do:

@page = Page.find(3)
@page.update_attributes(screenshot_content_type: screenshot_content_type, screenshot_file_size: screenshot_file_size, screenshot_update_at: screenshot_updated_at, screenshot_file_name: screenshot_file_name)

So, now I can do:

@page.screenshot and I see the paperclip object. However, when I do:

@page.screenshot.url => The url is not the one that I originally stored the image.

2

2 Answers

7
votes

I've managed to solve this problem with paperclip interpolations:

  1. Define in your model a field, witch would store path to the uploaded files
  2. Load the paperclip attachment info to DB through property update
  3. With interpolations specify a paperclip attachment :path, so it first looks for uploaded file, and then for default
  4. Profit!

This will do the trick, because of how S3 storage composes urls:

path: This is the key under the bucket in which the file will be stored. The URL will be constructed from the bucket and the path. This is what you will want to interpolate. Keys should be unique, like filenames, and despite the fact that S3 (strictly speaking) does not support directories, you can still use a / to separate parts of your file name.

Here are more details with code:

Model

class MyModel
    has_attached_file :file, path: ":existent_file_or_default", storage: :s3
end

Paperclip interpolations

Put this under config/initializers

Paperclip.interpolates :existent_file_or_default do |attachment, style|
  attachment.instance.existent_file_path ||
    attachment.interpolator.interpolate(":class/:attachment/:id_partition/:style/:filename", attachment, style)
end

Attach existent items

MyModel.create({
  existent_file_path: "http://your-aws-region.amazonaws.com/your-bucket/path/to/existent/file.jpeg",
  file_file_name: "some_pretty_file_name.jpeg",
  file_content_type: "image/jpeg",
  file_file_size: 123456
})
-1
votes

S3

Paperclip S3 integration is actually relatively simple - you'll be best looking at this Railscast on how to use Paperclip, and the afore-linked documentation to give you an idea on how Paperclip works; then you can just connect it to S3

--

In short, Paperclip basically takes a file object & saves the relevant data to your db. The storage of the file is dependent on the service you associate with Paperclip

What I'm trying to say is the two elements (data allocation & file storage) are two different elements of the gem, and if you can get Paperclip to handle the inbound files, you're half-way there:


Paperclip

Default implementation:

#app/models/page.rb
Class Page < ActiveRecord::Base
   has_attached_file :photo
end

This will allow you to save an "attachment" to your Page database:

#app/controllers/pages_controller.rb
Class PagesController < ApplicationController
   def new
       @page = Page.new
   end

   def create
       @page = Page.new(page_params)
   end

   private

   def page_params
      params.require(:page).permit(:title, :other, :information, :photo)
   end
end

#app/views/pages/new.html.erb
<%= form_for @page do |f| %>
   <%= f.file_field :photo %>
   <%= f.submit %>
<% end %>

This will handle Paperclip uploads directly to your own app (storing the files in the public/system directory)

In order to get it to work with S3, you just have to add the S3 credentials to the model (taken from the Paperclip documentation):

#app/models/page.rb
Class Page < ActiveRecord::Base
   has_attached_file :photo,
      :storage => :s3,
      :s3_credentials => Proc.new{|a| a.instance.s3_credentials }

   def s3_credentials
      {:bucket => "xxx", :access_key_id => "xxx", :secret_access_key => "xxx"}
   end
end

Path

If you'd like to call the "S3" filepath for your file - you'll have to do this:

#app/controllers/pages_controller.rb
Class PagesController < ApplicationController
   def show
      @page = Page.find 3
      @page.photo.url #-> yields S3 path ;)
   end
end