Paperclip saving the image outside the :rails_root - ruby-on-rails

Hi am using paperclip for image upload, problem is i want to access the image for two application deployed in the same server, for that i have to save the image in common folder outside of rails root. how can i do this?
Help me to resolve this problem.

https://github.com/thoughtbot/paperclip under this got to Understanding Storage. You can specify path to whatever folder you want.

You can change it by two ways:
1) config/application.rb or in any of the config/environments/*.rb files
module YourApp
class Application < Rails::Application
# Other code...
config.paperclip_defaults = {:storage => :fog, :fog_credentials => {:provider => "Local", :local_root => "#{Rails.root}/public"}, :fog_directory => "", :fog_host => "localhost"}
end
end
2) Rails initializer:
Paperclip::Attachment.default_options[:storage] = :fog
Paperclip::Attachment.default_options[:fog_credentials] = {:provider => "Local", :local_root => "#{Rails.root}/public"}
Paperclip::Attachment.default_options[:fog_directory] = ""
Paperclip::Attachment.default_options[:fog_host] = "http://localhost:3000"

Related

"Excon::Error::Forbidden" error when trying to save files to Google Cloud Storage with Fog and Carrierwave

I'm using Carrierwave and Fog to store images on the cloud. I was previously using Amazon S3 for the actual storage, which worked with no issues. But I switched over to Google Cloud Storage, and now I'm getting the following error whenever I try to save anything:
Excon::Error::Forbidden in GalleriesController#create
Expected(200) <=> Actual(403 Forbidden) excon.error.response :body => "InvalidSecurityThe
provided security credentials are not valid.Request
was not signed or contained a malformed signature"
:cookies => [ ] :headers => { "Alt-Svc" => "hq=\":443\"; ma=2592000;
quic=51303433; quic=51303432; quic=51303431; quic=51303339;
quic=51303335,quic=\":443\"; ma=2592000; v=\"43,42,39,38,35\""
"Content-Length" => "224" "Content-Type" => "application/xml;
charset=UTF-8" "Date" => "Tue, 01 May 2018 22:03:23 GMT" "Server" =>
"UploadServer" "Vary" => "Origin" "X-GUploader-UploadID" =>
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
} :host => "[directory].storage.googleapis.com" :local_address
=> "xxx.xxx.x.xxx" :local_port => xxxxx :path => "/uploads%2Fimage.png" :port => 443 :reason_phrase => "Forbidden"
:remote_ip => "xxx.xxx.x.xx" :status => 403 :status_line => "HTTP/1.1
403 Forbidden\r\n"
initializers/carrierwave.rb
CarrierWave.configure do |config|
config.fog_provider = 'fog/google'
config.fog_credentials = {
provider: 'Google',
google_storage_access_key_id: 'GOOGxxxxxxxxxxx',
google_storage_secret_access_key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
config.fog_directory = 'xxxxxxxxxxx'
#config.fog_public = false
#config.fog_attributes = { cache_control: "public, max-age=#{365.day.to_i}" }
end
Uploader
class PhotoFileUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog
def fix_exif_rotation
manipulate! do |img|
img.tap(&:auto_orient)
end
end
process :fix_exif_rotation
process :resize_to_fit => [800, 56000]
version :thumb do
process resize_to_fit: [300, 56000]
end
end
Gemfile
gem "fog-google"
gem "google-api-client", "> 0.8.5", "< 0.9"
gem "mime-types"
It seems like there's a problem with the key_id or secret_key, but I just copied and pasted both from the Interoperability section on the Google Cloud Storage Settings page. And I have no idea how to test if they're valid. My request is from localhost, if that matters.
I've found a few similar errors on SO, but they're all related to Amazon, and they don't seem to apply to what I'm doing.
Anyone have any ideas for how I can debug this?
You should use a valid directory name in for_directory.
Replace config.fog_directory = '[directory]'
with config.fog_directory = 'name_of_fog_folder'.
Hope this will help.
Looking at your error, it seems the host is the reason your call is forbidden. AFAIK, the host should be written as such within your configuration. I believe that the error message is not explicit enough.
Hope this helps.
You need to use a name that exists, instead of inserting a random name. Try re-installing some of the files that you need to run the program, as I once was doing the same thing but the file had been updated so it didn't work.
You could try using the google-cloud-storage Ruby library to debug your authentication. Just write a simple script that uploads and downloads a file. There are examples in this guide.
If you want to use google-cloud-storage in a new Rails application, you can do so with Active Storage.

Extra object in Google Cloud Storage when using Fog

I am following the fog.io/storage example for creating a directory and then uploading a file. Everything works great when I push my file into Google Cloud Storage except there is always a "binary/octet-stream" file named exactly as the deepest file I create.
My code is very similar to the AWS example in that I create a directory and from that new directory, I create a file. The directory structure is created properly and the file is uploaded properly but there is always an extra, 0-byte file. My code looks like:
job_number = 100
connection = Fog::Storage.new({
:provider => 'Google',
:google_storage_access_key_id => YOUR_GCE_ACCESS_KEY_ID,
:google_storage_secret_access_key => YOUR_GCE_SECRET_ACCESS_KEY
})
directory = connection.directories.create(
:key => "test-project/uploads/#{job_number}",
:public => false
)
file = directory.files.create(
:key => 'file.pdf',
:content_type => 'application/pdf',
:body => File.open("/path/to/my/file.pdf"),
:public => false
)
The directory structure is perfect (gs://test-project/uploads/100 folder exists) and the file.pdf file exists in that directory as well (gs://test-project/uploads/100/file.pdf).
The problem is that after the:
directory = connection.directories.create(
:key => "test-project/uploads/#{job_number}",
:public => false
)
command runs, there is a file at gs://test-project/uploads/100 as well as a directory gs://test-project/uploads/100/. When I walk through the code, the connection.directories.create(...) command is definitely creating the extra file but I cannot figure out why.
I have also tried to add a trailing slash to the key value for the connection.directories.create(...) command but that actually creates a different directory structure problem that is worse than this (this isn't bad, just annoying).
Has anyone seen this or know how to correctly have the directory structure created through Fog?
Instead of creating the directory right up to the file, just create/get the base directory/bucket and then save the file with the rest of the directory structure. So it would look like this:
job_number = 100
connection = Fog::Storage.new({
:provider => 'Google',
:google_storage_access_key_id => YOUR_GCE_ACCESS_KEY_ID,
:google_storage_secret_access_key => YOUR_GCE_SECRET_ACCESS_KEY
})
directory = connection.directories.create(
:key => "test-project",
:public => false
)
file = directory.files.create(
:key => 'uploads/#{job_number}/file.pdf',
:content_type => 'application/pdf',
:body => File.open("/path/to/my/file.pdf"),
:public => false
)

Rails MIME::Types.type_for(photoshop_1354320001.psd) - MIME Type not found?

I'm using Rails 3, Paperclip(3.3.0), aws-sdk (1.7.1).
My paperclip attachments are being stored securely on S3.
attachment.rb
has_attached_file :attachment,
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:s3_protocol => 'https',
:s3_permissions => :private, # Sets the file, not the folder as private in S3
:use_timestamp => false,
:default_style => :original, # NEEDS to be original or download_url method below wont work
:default_url => '/images/:attachment/default_:style.png',
:path => "/:rails_env/private/s/:s_id/uuploaded_files/:basename.:extension"
In order to download the files I generate a secure URL like so:
def authenticated_url(style = nil, expires_in = 1.hour)
mime_type = MIME::Types.type_for(self.attachment_file_name)[0]
attachment.s3_object(style).url_for(:read, :secure => true, :response_content_type => mime_type.to_s, :expires => expires_in).to_s
end
The problem is for PSDs: This is return empty:
Rails MIME::Types.type_for('photoshop_1354320001.psd')
In the code it looks like:
mime_type = MIME::Types.type_for(self.attachment_file_name)[0]
It works for other files fine but not PSDs. Any idea why and how to resolve?
Thanks
Sure. MIME::Types lets you specify custom types.
Stick this into an initializer
# Not quite sure what the appropriate MIMEtype for PSDs are,
# but this is the gist of it.
# .PSB is a larger version of .PSD supporting up to 300000x300000 px
psd_mime_type = MIME::Type.new('image/x-photoshop') do |t|
t.extensions = %w(psd psb)
t.encoding = '8bit'
end
MIME::Types.add psd_mime_type
Now MIME::Types.type_for "test.psd" should give you "image/x-photoshop".

Amazon S3 and Spree setup

Cant find a way for S3 to work with spree. There seem to exist few gems for that but dont seem to work for me.
Running rails 3.1.1 with spree 0.70.3.
I am running rails 3.0.10 and spree 0.60 and was able to get spree to use s3 storage over writing to the public folder of the app by doing the following The process should be alike.
add aws-s3 gem to your Gemfile
gem 'aws-s3'
bundle installed and after doing that I created a yaml file in the config directory called s3.yml and it should look something like this.
development: &DEFAULTS
bucket: "YOUR_BUCKET"
access_key_id: "YOUR_ACCESS_KEY"
secret_access_key: "YOUR_ACCESS_SECRET"
test:
<<: *DEFAULTS
bucket: "YOUR_BUCKET"
production:
<<: *DEFAULTS
bucket: "YOUR_BUCKET"
You can specify individual credentials per environment if you like but since mine are all using the same S3 accont I opted to set defaults.
after that you are going to have to override the image model or make a decorator for your which tells paperclip to use S3 and to have it parse the yaml file created for credentials.
the area you want want to override would be this
has_attached_file :attachment,
:styles => {:mini => '48x48>', :small => '200x100>', :product => '240x240>', :large => '600x600>'},
:default_style => :small,
:storage => :s3,
:s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
:url => "/assets/products/:id/:style/:basename.:extension",
:path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
you can change these properties as needed but whats important is that you specify :storage and :s3_credentials.
In the current version of Spree, you can set these values in the admin tools. But if you prefer to maintain it in code but without overriding the Image model, you can set these values in config/initializers/spree.rb. Make sure not to edit them via the admin portal.
S3_CONFIG = YAML.load_file("#{Rails.root}/config/s3.yml")[Rails.env]
Spree.config do |config|
config.attachment_styles = ActiveSupport::JSON.encode({
"mini" => "100x100>",
"small" => "200x200>",
"medium" => "400x600>",
"product" => "400x600>",
"large" => "600x600>",
"xl" => "800x800>",
"xxl" => "1200x1200>",
})
#AWS S3
config.use_s3 = true
config.s3_bucket = S3_CONFIG['bucket']
config.s3_access_key = S3_CONFIG['access_key_id']
config.s3_secret = S3_CONFIG['secret_access_key']
config.attachment_url = 'products/:id/:style/:basename.:extension'
config.attachment_path = 'products/:id/:style/:basename.:extension'
end
You can also try the BitNami Spree AMIs at http://bitnami.org/stack/spree. Regards.

How can I set paperclip's storage mechanism based on the current Rails environment?

I have a rails application that has multiple models with paperclip attachments that are all uploaded to S3. This app also has a large test suite that is run quite often. The downside with this is that a ton of files are uploaded to our S3 account on every test run, making the test suite run slowly. It also slows down development a bit, and requires you to have an internet connection in order to work on the code.
Is there a reasonable way to set the paperclip storage mechanism based on the Rails environment? Ideally, our test and development environments would use the local filesystem storage, and the production environment would use S3 storage.
I'd also like to extract this logic into a shared module of some kind, since we have several models that will need this behavior. I'd like to avoid a solution like this inside of every model:
### We don't want to do this in our models...
if Rails.env.production?
has_attached_file :image, :styles => {...},
:path => "images/:uuid_partition/:uuid/:style.:extension",
:storage => :s3,
:url => ':s3_authenticated_url', # generates an expiring url
:s3_credentials => File.join(Rails.root, 'config', 's3.yml'),
:s3_permissions => 'private',
:s3_protocol => 'https'
else
has_attached_file :image, :styles => {...},
:storage => :filesystem
# Default :path and :url should be used for dev/test envs.
end
Update: The sticky part is that the attachment's :path and :url options need to differ depending on which storage system is being used.
Any advice or suggestions would be greatly appreciated! :-)
I like Barry's suggestion better and there's nothing keeping you from setting the variable to a hash, that can then be merged with the paperclip options.
In config/environments/development.rb and test.rb set something like
PAPERCLIP_STORAGE_OPTIONS = {}
And in config/environments/production.rb
PAPERCLIP_STORAGE_OPTIONS = {:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:path => "/:style/:filename"}
Finally in your paperclip model:
has_attached_file :image, {
:styles => {:thumb => '50x50#', :original => '800x800>'}
}.merge(PAPERCLIP_STORAGE_OPTIONS)
Update: A similar approach was recently implemented in Paperclip for Rails 3.x apps. Environment specific settings can now be set with config.paperclip_defaults = {:storage => :s3, ...}.
You can set global default configuration data in the environment-specific configuration files. For example, in config/environments/production.rb:
Paperclip::Attachment.default_options.merge!({
:storage => :s3,
:bucket => 'wheresmahbucket',
:s3_credentials => {
:access_key_id => ENV['S3_ACCESS_KEY_ID'],
:secret_access_key => ENV['S3_SECRET_ACCESS_KEY']
}
})
After playing around with it for a while, I came up with a module that does what I want.
Inside app/models/shared/attachment_helper.rb:
module Shared
module AttachmentHelper
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def has_attachment(name, options = {})
# generates a string containing the singular model name and the pluralized attachment name.
# Examples: "user_avatars" or "asset_uploads" or "message_previews"
attachment_owner = self.table_name.singularize
attachment_folder = "#{attachment_owner}_#{name.to_s.pluralize}"
# we want to create a path for the upload that looks like:
# message_previews/00/11/22/001122deadbeef/thumbnail.png
attachment_path = "#{attachment_folder}/:uuid_partition/:uuid/:style.:extension"
if Rails.env.production?
options[:path] ||= attachment_path
options[:storage] ||= :s3
options[:url] ||= ':s3_authenticated_url'
options[:s3_credentials] ||= File.join(Rails.root, 'config', 's3.yml')
options[:s3_permissions] ||= 'private'
options[:s3_protocol] ||= 'https'
else
# For local Dev/Test envs, use the default filesystem, but separate the environments
# into different folders, so you can delete test files without breaking dev files.
options[:path] ||= ":rails_root/public/system/attachments/#{Rails.env}/#{attachment_path}"
options[:url] ||= "/system/attachments/#{Rails.env}/#{attachment_path}"
end
# pass things off to paperclip.
has_attached_file name, options
end
end
end
end
(Note: I'm using some custom paperclip interpolations above, like :uuid_partition, :uuid and :s3_authenticated_url. You'll need to modify things as needed for your particular application)
Now, for every model that has paperclip attachments, you just have to include this shared module, and call the has_attachment method (instead of paperclip's has_attached_file)
An example model file: app/models/user.rb:
class User < ActiveRecord::Base
include Shared::AttachmentHelper
has_attachment :avatar, :styles => { :thumbnail => "100x100>" }
end
With this in place, you'll have files saved to the following locations, depending on your environment:
Development:
RAILS_ROOT + public/attachments/development/user_avatars/aa/bb/cc/aabbccddeeff/thumbnail.jpg
Test:
RAILS_ROOT + public/attachments/test/user_avatars/aa/bb/cc/aabbccddeeff/thumbnail.jpg
Production:
https://s3.amazonaws.com/your-bucket-name/user_avatars/aa/bb/cc/aabbccddeeff/thumbnail.jpg
This does exactly what I'm looking for, hopefully it'll prove useful to someone else too. :)
-John
How about this:
Defaults are established in application.rb. The default storage of :filesystem is used, but the configuration for s3 is initialized
Production.rb enables :s3 storage and changes the default path
Application.rb
config.paperclip_defaults =
{
:hash_secret => "LongSecretString",
:s3_protocol => "https",
:s3_credentials => "#{Rails.root}/config/aws_config.yml",
:styles => {
:original => "1024x1024>",
:large => "600x600>",
:medium => "300x300>",
:thumb => "100x100>"
}
}
Development.rb (uncomment this to try with s3 in development mode)
# config.paperclip_defaults.merge!({
# :storage => :s3,
# :bucket => "mydevelopmentbucket",
# :path => ":hash.:extension"
# })
Production.rb:
config.paperclip_defaults.merge!({
:storage => :s3,
:bucket => "myproductionbucket",
:path => ":hash.:extension"
})
In your model:
has_attached_file :avatar
Couldn't you just set an environment variable in production/test/development.rb?
PAPERCLIP_STORAGE_MECHANISM = :s3
Then:
has_attached_file :image, :styles => {...},
:storage => PAPERCLIP_STORAGE_MECHANISM,
# ...etc...
My solution is same with #runesoerensen answer:
I create a module PaperclipStorageOption in config/initializers/paperclip_storage_option.rb
The code is very simple:
module PaperclipStorageOption
module ClassMethods
def options
Rails.env.production? ? production_options : default_options
end
private
def production_options
{
storage: :dropbox,
dropbox_credentials: Rails.root.join("config/dropbox.yml")
}
end
def default_options
{}
end
end
extend ClassMethods
end
and use it in our model
has_attached_file :avatar, { :styles => { :medium => "1200x800>" } }.merge(PaperclipStorageOption.options)
Just it, hope this help
Use the :rails_env interpolation when you define the attachment path:
has_attached_file :attachment, :path => ":rails_root/storage/:rails_env/attachments/:id/:style/:basename.:extension"

Resources