How do you set a file to be uploaded to a specific folder when using Cloudinary and Active Storage?

1.8k Views Asked by At

I understand how this can be down when uploading directly to Cloudinary using the below syntax and passing the folder name as an argument.

Cloudinary::Uploader.upload("sample.jpg", :use_filename => true, :folder => "folder1/folder2")

However, I am using ActiveStorage, and when I upload a photo in the above manner it isn't attached to my Post model or associated in any way with my app.

I am using the following code to attach images

post.send(:images).attach io: StringIO.new(new_data), filename: blob.filename.to_s, 
          content_type: 'image'

It does not accept an argument to specify the folder. I have tried my best to read both ActiveStorage and Cloudinary docs in an attempt to find a way to make this work, however, I cannot seem to figure it out.

I have seen that setting a custom folder header may be a way to get this to work, but again cannot figure out how to set a custom header for the above code which takes place in the below job.

require 'tmpdir'
require 'fileutils'
require 'open-uri'
class ResizeImagesJob < ApplicationJob
  queue_as :default

  def perform(post)
    post.images.each do |image|
      blob = image.blob
      blob.open do |temp_file|
        path = temp_file.path
        pipeline = ImageProcessing::MiniMagick.source(path)
        .resize_to_limit(1200, 1200)
        .call(destination: path)
        new_data = File.binread(path)
        post.send(:images).attach io: StringIO.new(new_data), filename: blob.filename.to_s, 
                           content_type: 'image'
      end
      image.purge_later
    end
  end
end

What the above job is doing is waiting until after a post is created and then resizing and reattaching the photos to the post well deleting the originals. I am taking this approach to avoid integrity errors that take place resizing a post directly on Cloudinary after upload.

What I want to do is store the resized photos in a different folder. The reason for this is that I am using direct_upload and it is possible for users to upload photos without ever creating a post. Thus causing me to store unused, photos. This would provide an easy way to identify and deal with such images.

3

There are 3 best solutions below

0
On BEST ANSWER

For folder names based on Rails environment

You can dynamically set your folder on storage.yml:

cloudinary:
  service: Cloudinary
  folder: <%= Rails.env %>

And so Cloudinary will automatically create folders based on your Rails env:

image

This is a long due issue with Active Storage that seems to have been worked around by the Cloudinary team. Thanks for the amazing work ❤️

2
On

By default, Cloudinary Active Storage service uploads to the root of the Cloudinary account. If you would like to upload files to a different base folder then you can configure that in the storage.yml file. To do that, you would add the folder option and set it to the folder in which you would like the Cloudinary service to upload the resources to -

cloudinary_gallery:
  service: Cloudinary
  folder: my_gallery_images

If what you're looking for is not a base folder, but rather to dynamically change the folder for uploaded files per-upload, then, in short, that isn't supported. The Cloudinary Active Storage service is implemented similar to other Storage providers, such as Azure Storage, Google Cloud Storage or Amazon S3. As a Storage service, its main function is to store files on the integrated service, but Active Storage is not able to support many custom upload flows, such as dynamic changing of the Storage path (folder) per upload. This functionality is supported on Cloudinary itself, but due to the constraint with how standard Active Storage services are integrated, it means it's currently not supported through Active Storage.

However, the developers working on Rails/Active Storage have a planned update in the upcoming versions to support defining multiple Active Storage adapters for the same service. This would allow you to configure multiple Cloudinary configurations in the storage.yml file which can then be configured per attachment. There is a Pull request that was merged to support this - https://github.com/rails/rails/pull/34935.

Using the above changes, we can do something like this -

In storage.yml

cloudinary_profiles:
  service: Cloudinary
  folder: profiles

cloudinary_images:
  service: Cloudinary
  folder: images

Then you can do this (where different attachments can reference different adapters) -

class User < ApplicationRecord
  has_one_attached :profile, service: :cloudinary_profiles
  has_many_attached :images, service: :cloudinary_images
end

If you need a single attachment to upload to different folders (or apply different upload parameters/configurations) dynamically, then Active Storage, in general, won't be applicable for this use-case due to the constraints that Active Storage services have based on their standard implementation. You can use the Cloudinary Ruby SDK without Active Storage for a lot more fine-grained control based on your use-case.

0
On

I just tested this and it works. You can write some ruby code in your storage.yml file to tell Cloudinary to host your files in a different pre-defined folder, and different for production and development environments:

<% if Rails.env == "development" %>
  <% test = "test-dev" %>
<% else %>
  <% test = "test-prod" %>
<% end %>

cloudinary:
  service: Cloudinary
  folder: <%= test %>

I hope this helps!