Skip to content

Conversation

@ArnyminerZ
Copy link

@ArnyminerZ ArnyminerZ commented Jan 11, 2026

Closes #2060

Currently, the images loaded from Immich (I guess also Photoprism) are always stored in cache, no matter whether the server responded successfully or not.

This is a simple PR to make sure no errors (or empty images) are stored in cache.


I can't find where to suggest updates to the documentation, but this page must be updated to indicate that the asset.view is also required, as explained in the original issue.

Summary by CodeRabbit

  • Refactor
    • Improved photo thumbnail response handling efficiency.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

📝 Walkthrough

Walkthrough

The photos controller's thumbnail caching mechanism was refactored to replace a Rails.cache.fetch block with an explicit read-check pattern. The method now checks the cache first, and if empty, computes the thumbnail response, caches it on success, and then returns the response in all scenarios.

Changes

Cohort / File(s) Summary
Thumbnail caching refactor
app/controllers/api/v1/photos_controller.rb
Changed caching strategy from implicit Rails.cache.fetch to explicit read-check and conditional write. Ensures response is always returned regardless of cache state, improving reliability of thumbnail response handling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A cache that was confused, my dear,
Now reads and writes with crystal clear,
The photos bloom upon the map,
No more a frustrating gap!
With explicit checks, our bugs take flight 📸✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing thumbnails from being cached when errors occur in the response.
Linked Issues check ✅ Passed The code change implements conditional caching to avoid storing error responses, which directly addresses issue #2060's root cause of missing Immich photos in map view.
Out of Scope Changes check ✅ Passed The changes are focused on the thumbnail caching logic in the photos controller, which is directly scoped to the issue requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ArnyminerZ
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @app/controllers/api/v1/photos_controller.rb:
- Around line 23-24: Cache key for photo thumbnails omits params[:source],
causing collisions across sources; update both the cache read and write to
include the source (e.g., append params[:source] or a normalized default) so the
key uniquely identifies source + id (use the same composite key when reading and
writing, and guard/normalize params[:source] to avoid nil/unsafe values).
- Around line 26-30: The cached value must be a plain hash rather than the
HTTParty::Response object; change the Rails.cache.write call in
Photos::Thumbnail usage to store only the response fields, e.g. build a hash
like { body: response.body, code: response.code, success: response.success? }
keyed by "photo_thumbnail_#{params[:id]}", and ensure any code that reads from
cache (the same controller or any consumer) expects and uses that hash
(accessing .dig or hash keys instead of calling methods on an
HTTParty::Response).
🧹 Nitpick comments (2)
app/controllers/api/v1/photos_controller.rb (2)

28-30: Consider checking for empty response bodies in addition to success status.

The PR description mentions preventing "empty images" from being cached, but the code only checks response.success?. HTTP 200 responses can still have empty bodies. Consider adding an additional check for empty or invalid image data.

💡 Suggested enhancement to check for empty bodies
-    if response.success?
+    if response.success? && response.body.present?
       Rails.cache.write("photo_thumbnail_#{params[:id]}", response, expires_in: 1.day)
     end

28-30: Optional: Use modifier form for single-line conditional.

RuboCop suggests using modifier if syntax for improved readability when the body is a single line.

♻️ RuboCop-compliant alternative
-    if response.success?
-      Rails.cache.write("photo_thumbnail_#{params[:id]}", response, expires_in: 1.day)
-    end
+    Rails.cache.write("photo_thumbnail_#{params[:id]}", response, expires_in: 1.day) if response.success?

Based on static analysis hints.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 301c14d and aec9df0.

📒 Files selected for processing (1)
  • app/controllers/api/v1/photos_controller.rb
🧰 Additional context used
📓 Path-based instructions (6)
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rb: Always handle location data with appropriate precision and privacy considerations
Leverage PostGIS features for geographic calculations rather than Ruby-based solutions
Use the lonlat column in the points table for geographic calculations
Consider database indexes for geographic queries to improve performance
Never log or expose user location data inappropriately

Use bundle exec rubocop to enforce Ruby code formatting before commits

Files:

  • app/controllers/api/v1/photos_controller.rb
app/**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

app/**/*.rb: When implementing features that interact with stats, use the public_accessible? method to check if a stat can be publicly accessed
Support UUID-based access in API endpoints when appropriate for public sharing
Respect expiration settings and disable sharing when expired for public stat sharing
Only expose minimal necessary data in public sharing contexts

app/**/*.rb: Use PostGIS geometries for imports, exports, sharing, and trip analytics
Use Devise for authentication and Pundit for authorization

Files:

  • app/controllers/api/v1/photos_controller.rb
**/*.{rb,js,ts,tsx,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Use two-space indentation

Files:

  • app/controllers/api/v1/photos_controller.rb
app/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Use snake_case for filenames

Files:

  • app/controllers/api/v1/photos_controller.rb
**/*.{rb,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use CamelCase for class names

Files:

  • app/controllers/api/v1/photos_controller.rb
app/**/*.{rb,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Queue non-trivial work to Sidekiq workers instead of blocking requests

Files:

  • app/controllers/api/v1/photos_controller.rb
🧬 Code graph analysis (1)
app/controllers/api/v1/photos_controller.rb (2)
app/controllers/api_controller.rb (1)
  • current_api_user (35-37)
app/services/photos/thumbnail.rb (1)
  • call (12-17)
🪛 RuboCop (1.82.1)
app/controllers/api/v1/photos_controller.rb

[convention] 28-28: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.

(Style/IfUnlessModifier)

Comment on lines +23 to +24
cached = Rails.cache.read("photo_thumbnail_#{params[:id]}")
return cached if cached
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Cache key should include the source parameter.

The cache key "photo_thumbnail_#{params[:id]}" doesn't include the source parameter. If the same photo ID exists in both Immich and Photoprism, one source's thumbnail will overwrite the other's in the cache, causing incorrect thumbnails to be served.

🔧 Proposed fix to include source in cache key
-    cached = Rails.cache.read("photo_thumbnail_#{params[:id]}")
+    cached = Rails.cache.read("photo_thumbnail_#{source}_#{params[:id]}")
     return cached if cached
 
     response = Photos::Thumbnail.new(current_api_user, source, params[:id]).call
 
     if response.success?
-      Rails.cache.write("photo_thumbnail_#{params[:id]}", response, expires_in: 1.day)
+      Rails.cache.write("photo_thumbnail_#{source}_#{params[:id]}", response, expires_in: 1.day)
     end
🤖 Prompt for AI Agents
In @app/controllers/api/v1/photos_controller.rb around lines 23 - 24, Cache key
for photo thumbnails omits params[:source], causing collisions across sources;
update both the cache read and write to include the source (e.g., append
params[:source] or a normalized default) so the key uniquely identifies source +
id (use the same composite key when reading and writing, and guard/normalize
params[:source] to avoid nil/unsafe values).

Comment on lines +26 to 30
response = Photos::Thumbnail.new(current_api_user, source, params[:id]).call

if response.success?
Rails.cache.write("photo_thumbnail_#{params[:id]}", response, expires_in: 1.day)
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check Rails cache configuration in environment files
rg -n "config.cache_store" config/environments/

# Check for custom serializers
rg -n "serializer" config/

Repository: Freika/dawarich

Length of output: 536


🏁 Script executed:

# Find and examine the Photos::Thumbnail class
fd -t f "thumbnail" app/ | head -20

Repository: Freika/dawarich

Length of output: 91


🏁 Script executed:

# Search for Photos::Thumbnail class definition
rg -n "class.*Thumbnail" app/

Repository: Freika/dawarich

Length of output: 117


🏁 Script executed:

# Check how responses are typically handled in the codebase with HTTParty
rg -n "HTTParty" app/ -A 3 -B 1 | head -50

Repository: Freika/dawarich

Length of output: 2048


🏁 Script executed:

# Read the full Photos::Thumbnail class
cat -n app/services/photos/thumbnail.rb

Repository: Freika/dawarich

Length of output: 1715


🏁 Script executed:

# Search for where the cached photo_thumbnail is retrieved/used
rg -n "photo_thumbnail" app/ -A 3 -B 1

Repository: Freika/dawarich

Length of output: 1366


🏁 Script executed:

# Check if there's a method that retrieves and uses the cached response
rg -n "Rails.cache.read" app/controllers/api/v1/photos_controller.rb -A 3 -B 1

Repository: Freika/dawarich

Length of output: 284


🏁 Script executed:

# Read the full photos_controller.rb to see how the response is used
cat -n app/controllers/api/v1/photos_controller.rb

Repository: Freika/dawarich

Length of output: 2234


🏁 Script executed:

# Search for HTTParty response serialization in Rails
web_search "HTTParty Net::HTTPResponse serialize Redis Rails cache"

Repository: Freika/dawarich

Length of output: 109


🌐 Web query:

HTTParty response Net::HTTPResponse serialize Redis Rails cache serialization issue

💡 Result:

Short answer: don’t cache the raw HTTParty/Net::HTTPResponse object — cache a plain serializable hash (body, code, headers, parsed_response) instead.

Why: HTTParty::Response tries to implement _dump/_load but it embeds request/response objects (Net::HTTPResponse/Net::HTTPRequest) that aren’t reliably Marshal/JSON serializable across Ruby versions or JVM/native extensions. Rails’ RedisCacheStore uses a coder (Marshal by default) and will fail or produce brittle data when asked to persist low-level Net::HTTP objects. (rubydoc.info)

Recommended pattern:

  • Extract the pieces you need and store a simple Hash or JSON (safe for Redis).
  • Rehydrate into a lightweight wrapper when reading back.

Example:

  • write:
    cached = { body: resp.body, code: resp.code.to_i, headers: resp.headers.to_h, parsed: resp.parsed_response }
    Rails.cache.write(key, cached, expires_in: 10.minutes)
  • read:
    cached = Rails.cache.read(key)
    if cached

    use cached[:parsed] or build a minimal object

    else
    resp = HTTParty.get(url)
    Rails.cache.write(key, { body: resp.body, code: resp.code.to_i, headers: resp.headers.to_h, parsed: resp.parsed_response })
    end

If you must persist the whole HTTParty::Response, be aware HTTParty defines _dump/_load but it relies on Marshal for request/response internals — this is fragile; prefer serializing only primitives instead. (rubydoc.info)

If you want, tell me which fields you need and I’ll give a ready-made serializer/deserializer.

Citations:


🏁 Script executed:

# Check if there are any tests that show response caching/deserialization
rg -n "photo_thumbnail" app/ --type rb | head -20

Repository: Freika/dawarich

Length of output: 85


🏁 Script executed:

# Look for any response handling patterns in the codebase
rg -n "response\.(body|code|success)" app/controllers/ -B 2 -A 2 | head -50

Repository: Freika/dawarich

Length of output: 1133


🏁 Script executed:

# Search for other response caching patterns in the codebase
rg -n "Rails.cache.write.*response" app/ -A 1 -B 1

Repository: Freika/dawarich

Length of output: 319


🏁 Script executed:

# Check if there are any error handling logs related to cache deserialization
rg -n "cache" app/controllers/ | grep -i "error\|fail\|rescue"

Repository: Freika/dawarich

Length of output: 41


Cache only the response body and code, not the entire HTTParty response object.

HTTParty::Response embeds Net::HTTPResponse and Net::HTTPRequest objects that don't serialize reliably with Redis' default Marshal coder. When deserialized from cache, the object may lose methods or data, causing .success?, .body, or .code to fail unexpectedly. Extract and cache only the necessary fields as a plain hash: { body: response.body, code: response.code, success: response.success? }.

🧰 Tools
🪛 RuboCop (1.82.1)

[convention] 28-28: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.

(Style/IfUnlessModifier)

🤖 Prompt for AI Agents
In @app/controllers/api/v1/photos_controller.rb around lines 26 - 30, The cached
value must be a plain hash rather than the HTTParty::Response object; change the
Rails.cache.write call in Photos::Thumbnail usage to store only the response
fields, e.g. build a hash like { body: response.body, code: response.code,
success: response.success? } keyed by "photo_thumbnail_#{params[:id]}", and
ensure any code that reads from cache (the same controller or any consumer)
expects and uses that hash (accessing .dig or hash keys instead of calling
methods on an HTTParty::Response).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant