3
votes

I have been searching through this site but can't seem to find the best way to structure folders/files for image and file uploads. I am deploying a Ruby on Rails website using:

  • CarrierWave (uploads)
  • Capistrano (deployment)
  • Amazon S3 (storage)
  • Capistrano (deployment)

The site allows users to setup profiles where they will upload images. I'd like to use a folder structure that scales well to thousands (or tens of thousands) of users uploading multiple images.

Currently I'm using the default CarrierWave directory structure:

def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

But I'm thinking it is better to perhaps include the user_id in the folder name? Is it better to move this folder outside of the public/uploads folder? Shouldn't I used different folders for the dev, production and testing environments? I'd like to override the store_dir in the best way for a site with thousands of users uploading around 15 files per profile (stored as full size, profile and thumbnail sizes).

1
Please understand I have searched on this a bunch and hope that I'm respecting the rules on this site--I rarely need to ask pure coding questions because of the awesome answers already on this site. Any good suggestion for folder/file structure or tips for deployment will be accepted as an answer. - FireDragon

1 Answers

8
votes

The normal folder structure should support hat very well, the main thing is to avoid collisions and make sure that association you use for images across your site is consistent. For these reasons you would not want to just store all of your images in one folder obviously.

But I'm thinking it is better to perhaps include the user_id in the folder name?

I would not recommend this, it may cause more problems than its worth. Using the models class, and id as you are doing should be plenty sufficient and also the uploader may have problems mapping the image for example when a user who is not the uploader tries to view the image.

Is it better to move this folder outside of the public/uploads folder?

Since you are using Amazon S3 your images are not actually stored in public/uploads of your project if that is where you are referring to. That should only be the temporary files that Carrierwave uses to while uploading/resizing the files. If you are worried about space/security you may want to take a look at this and adapt it to your needs if necessary.

Shouldn't I used different folders for the dev, production and testing environments?

You can use different folders if you like:

def store_dir
  "#{Rails.env}/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

Or use different buckets on Amazon S3 (recommended):

In config/initializers/fog.rb something like:

CarrierWave.configure do |config|
  config.storage = :fog
  config.fog_credentials = {
    :provider              => <provider>,
    :aws_access_key_id     => <id>,
    :aws_secret_access_key => <key>
  }
  if Rails.env == "production"
    config.fog_directory = 'production'
  elsif Rails.env == "development"
    config.fog_directory = 'development'
  elsif ...
    config.fog_directory = '...'
  end
end

Or you could do something similar in the separate environment files.

I'd like to override the store_dir in the best way for a site with thousands of users uploading around 15 files per profile (stored as full size, profile and thumbnail sizes).

This storage dir structure should work just fine, as far as resizing you should check out the guide.

Update:

For the file name I would definitely recommend changing it to a random string so that you can avoid any potential naming collisions within the folder itself. It is not unlikely that someone could upload me.jpg for 2 different files. That being said this is how I do it.

Within your_uploader.rb

add a filename method that will randomize the current files name.

 def filename
   random_token = Digest::SHA2.hexdigest("#{Time.now.utc}--#{model.id.to_s}").first(20)
   ivar = "@#{mounted_as}_secure_token"
   token = model.instance_variable_get(ivar)
   token ||= model.instance_variable_set(ivar, random_token)
   "#{token}.jpg" if original_filename
  end

This particular arrangement may be a bit overkill but it proves sufficient for me.

Hope this helps!