Ryan Arneson
Ryan Arneson

Reputation: 1323

Paperclip S3 uploads with S3 credentials stored in the model

I've got a Site model which allows for a background_image upload to S3 via Paperclip. Where it gets weird is that each Site has it's own S3 credentials store in it's model. My question is this, how can I reference the site's own attributes when uploading via Paperclip?

class Site < ActiveRecord::Base
  attr_accessible :background_image, 
                  :s3_access_key_id,
                  :s3_secret_access_key,
                  :s3_username

  has_attached_file :background_image,
                    storage: :s3,
                    s3_credentials: {
                      access_key_id: @s3_access_key_id.to_s,
                      secret_access_key: @s3_secret_access_key.to_s
                    },
                    bucket: "my-app",
                    s3_permissions: "public-read",
                    path: "/home/#{@s3_username}/background_images/:id/:filename"
end

Unfortunately with this setup I just get The AWS Access Key Id you provided does not exist in our records. I assume it's just getting blank values because it works fine when I hard-code the values from the database.

Upvotes: 2

Views: 899

Answers (1)

gregates
gregates

Reputation: 6714

Your problem here is that has_attached_file is a being called on the Site class when site.rb is first read, not on the site instance. Fortunately, paperclip supports dynamic configuration in several places, wherein you can retrieve the Site instance at runtime. Try this instead:

has_attached_file :background_image,
  s3_credentials: lambda { |attachment| attachment.instance.s3_keys },
  path: lambda { |attachment| "/home/#{attachment.instance.s3_username}/background_images/:id/filename" }


def s3_keys
  {
    access_key_id: self.s3_access_key_id.to_s,
    secret_access_key: self.s3_secret_access_key.to_s
  }
end

Those lambdas will get evaluated at runtime, with the Paperclip::Attachment object passed in as a param. You can retrieve the Site instance with Paperclip::Attachment#instance. So then you can call methods on the instance to get its specific keys.

You can also use a more specific/advanced trick for getting the s3_username into the path. You can just teach paperclip more path name interpolations. Like you could add a file at config/initializers/paperclip.rb with:

Paperclip.interpolates :s3_username do |attachment, style|
  attachment.instance.s3_username
end

and then your Site model could look like

has_attached_file :background_image,
  s3_credentials: lambda { |attachment| attachment.instance.s3_keys },
  path: "/home/:s3_username/background_images/:id/:filename"


def s3_keys
  {
    access_key_id: self.s3_access_key_id.to_s,
    secret_access_key: self.s3_secret_access_key.to_s
  }
end

Upvotes: 3

Related Questions