Skip to content

Return the owning Tempfile from Download.open_each - #73

Merged
rywall merged 2 commits into
masterfrom
download-open-each-owns-tempfile
Sep 8, 2026
Merged

rywall merged 2 commits into
masterfrom
download-open-each-owns-tempfile

Conversation

@rywall

@rywall rywall commented Sep 8, 2026

Copy link
Copy Markdown
Member

Problem

Download.open_each opens a remote or IO-backed source into a Tempfile, then returns a fresh File on that path and drops the Tempfile. Nothing references it after that, so the next garbage collection runs its finalizer and unlinks the path. A caller that keeps the returned file and reads its path later gets Errno::ENOENT.

Today this is latent: KMLFile reads the bytes in the same expression as open_each, and the realistic remote inputs for Shapefile and File are archives whose extracted entries are ordinary files. The EXIF photo importer in #72 is the first caller whose main input is a single remote file and which reads the path lazily, across cache_key, EXIF parsing, and the app's image handler. It currently works around this by copying each photo into a directory it owns.

Change

open_each returns the Tempfile itself for the non-archive case, so the path stays valid for as long as the caller holds the result. Archive entries are still opened by path. Local paths are unchanged.

Verification

  • New specs cover both open-uri return shapes (a StringIO for small bodies, a Tempfile for large ones) and check the path is still readable after GC.start. They fail on master and pass here. A 20-run check showed the old code lost the path in every run and the new code kept it in every run.
  • Full suite: 307 examples, 0 failures.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QME46q3GqY6JQZFwZdnbcd

rywall and others added 2 commits September 8, 2026 14:23
A remote or IO-backed source is held in a Tempfile, which unlinks its
path once it is garbage collected. open_each returned a fresh File
opened on that path and dropped the Tempfile, so a caller that kept the
result and read its path later (a cache key, EXIF parsing, an image
handler) could find it gone with ENOENT.

Return the Tempfile itself for the non-archive case, so the path stays
valid for as long as the caller holds the result. Archive entries are
ordinary files on disk and are still opened by path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QME46q3GqY6JQZFwZdnbcd
json 3.0.0 (released 2026-09-07) removed the positional options argument
to JSON.parse. ActiveSupport::JSON.decode in every released Rails still
passes one, so reading any JSON column raises ArgumentError and the
feature import and queued processing specs fail on the Ruby 4.0 job.
Rails has the fix on main (rails/rails#58601) but no release yet.

Pin json to 2.x in both CI gemfiles until a fixed Rails ships. This is
a test-matrix constraint only; the gemspec is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QME46q3GqY6JQZFwZdnbcd
@rywall
rywall merged commit d366ac3 into master Sep 8, 2026
2 checks passed
rywall added a commit that referenced this pull request Sep 8, 2026
Releases the two commits merged since 3.12.0:

- 66d4859 Add EXIF photo importer (#72)
- d366ac3 Return the owning Tempfile from Download.open_each (#73)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwZduP5HbaZc6i2hoJwFJ5
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