cloudinary / cloudinary/cloudinary_gem
Allow Cloudinary Active Storage service to reuse preloaded blobs via options (avoid extra DB queries)
- Dominant language
- Ruby
- Stars
- 420
- Forks
- 285
- PR merge metrics
- No merged PRs in 30d
Description
## Feature request for Cloudinary Ruby SDK
Please enhance the Cloudinary Active Storage service so that it can reuse a preloaded `ActiveStorage::Blob` instead of always querying the database.
I’m specifically proposing:
* Threading an optional `:blob` through the `options` hash into the internal helper `#find_blob_or_use_key`.
* Updating `#find_blob_or_use_key` to accept an optional `blob:` parameter and use it when present.
* Focusing primarily on `#url` while leaving `#delete` and `#exist?` unchanged for now.
This keeps public APIs backwards compatible and only adds an opt-in optimization path.
---
## Explain your use case
In my Rails app, `ActiveStorage::Blob` records are often already loaded in memory via `includes` / `preload`/`with_attached_*`.
When I call `blob.url`, Rails ends up delegating to the configured service’s `#url` method and passes along an `options` hash. I’d like to be able to include the already-loaded `blob` in that options hash so the Cloudinary service doesn’t need to look it up again by key.
For my specific workload, this primarily matters for generating URLs (`#url`), where blobs are very frequently preloaded. For deletions, blobs are less likely to be preloaded, so I’m less concerned about optimizing `#delete` right now.
---
## Describe the problem you’re trying to solve
Currently:
* `CloudinaryService#find_blob_or_use_key` is a **private** helper used internally by:
* `#url`
* `#delete`
* `#exist?`
* These methods pass a key to `find_blob_or_use_key`, which then always does a DB lookup (unless the argument is already an `ActiveStorage::BlobKey`).
* When calling `blob.url`, Rails goes through `service.url(key, options)`, and the options hash is forwarded — but there is no support in the Cloudinary service for using something like `options[:blob]` to skip the DB query.
As a result:
* Even if I already have the `ActiveStorage::Blob` instance, the Cloudinary service will still call `ActiveStorage::Blob.find_by(key: key)`.
* This can create unnecessary queries and N+1 patterns in attachment-heavy views or background jobs.
* There’s no way to opt in to a “I already have the blob, please reuse it” behavior.
`#delete` is more problematic to change because `Blob#delete` only passes the `key` to the service and doesn’t pass an options hash at all. In my case, blobs are less likely to be preloaded at deletion time, so I’m okay with leaving `#delete` as-is initially.
---
## Do you have a proposed solution?
Yes.
### 1. Pass `blob` through the `options` hash for `#url`
Because `blob.url` already forwards an `options` hash to the service, the most compatible way is to use that:
* Allow callers of `service.url` (including `blob.url`) to pass `blob` via `options[:blob]`.
* Inside `CloudinaryService#url`, extract `blob` from `options` and pass it to `find_blob_or_use_key`.
Conceptual example:
```ruby
def url(key, filename: nil, content_type: '', **options)
blob = options[:blob]
key = find_blob_or_use_key(key, blob:)
# existing logic using key...
end
```
### 2. Update `find_blob_or_use_key` to accept an optional `blob:` argument
Then, update the private helper so that it reuses a provided blob if present, and falls back to the current behavior otherwise. For example:
```ruby
def find_blob_or_use_key(key, blob: nil)
if key.is_a?(ActiveStorage::BlobKey)
key
else
begin
blob ||= ActiveStorage::Blob.find_by(key: key)
blob ? ActiveStorage::BlobKey.new(blob.attributes.as_json) : key
rescue ActiveRecord::StatementInvalid => e
# Return the original key if an error occurs
key
end
end
end
```
### Why this is backwards compatible
* Existing callers that do **not** pass a `:blob` option behave exactly as before:
* `find_blob_or_use_key` still does `ActiveStorage::Blob.find_by(key: key)` and returns the same result type.
* It still returns the key unchanged if it’s already an `ActiveStorage::BlobKey`.
* It still rescues `ActiveRecord::StatementInvalid` and returns the original key.
* The optimization is **opt-in**:
* Only when an upstream caller sets `options[:blob]` (for example, `blob.url(blob: blob)` or via a wrapper) will the Cloudinary service reuse the preloaded blob and skip the DB query.
This enables applications that already manage preloaded blobs to avoid extra database queries when generating URLs (and, optionally, when checking existence), without breaking or changing the behavior for any existing users of the SDK.
Contributor guide
Assessment
This issue has not been assessed yet.