Skip to content

Commit 20543c0

Browse files
cupakromerkaspth
authored andcommitted
Add implicit to path conversion to uploaded file (rails#28676)
* Add implicit to path conversion to uploaded file Ruby has a few implicit conversion protocols (e.g. `to_hash`, `to_str`, `to_path`, etc.). These are considered implicit conversion protocols because in certain instances Ruby (MRI core objects) will check if an argument responds to the appropriate protocol and automatically convert it when it does; this is why you can provide a `Pathname` instance into `File.read` without having to explicitly call `to_s`. ```ruby a_file_path = 'some/path/file.ext' File.write a_file_path, 'String Path Content' File.read a_file_path a_pathname = Pathname(a_file_path) File.write core_file, 'Pathname Content' File.read a_file_path core_file = File.new(a_pathname) File.write core_file, 'File Content' File.read core_file tmp_file = Tempfile.new('example') File.write tmp_file, 'Tempfile Content' File.read tmp_file ``` So how does an uploaded file work in such cases? ```ruby tmp_file = Tempfile.new('example') File.write tmp_file, 'Uploaded Content' uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file) File.read uploaded_file ``` It fails with a `TypeError`: no implicit conversion of ActionDispatch::Http::UploadedFile into String In order to make an uploaded file work it must be explicitly converted to a file path using `path`. ```ruby File.read uploaded_file.path ``` This requires any code that expects path/file like objects to either special case an uploaded file, re-implement the path conversion protocol to use `path`, or forces the developer to explicitly cast uploaded files to paths. This last option can sometimes be difficult to do when such calls are deep within the inner workings of libraries. Since an uploaded file already has a path it makes sense to implement the implicit "path" conversion protocol (just like `File` and `Tempfile`). This change allows uploaded file content to be treated more closely to regular file content, without requiring any special case handling or explicit conversion for common file utilities. * Note uploaded file path delegation in CHANGELOG
1 parent 76f22e1 commit 20543c0

File tree

3 files changed

+21
-0
lines changed

3 files changed

+21
-0
lines changed

actionpack/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
* `ActionDispatch::Http::UploadedFile` now delegates `to_path` to its tempfile.
2+
3+
This allows uploaded file objects to be passed directly to `File.read`
4+
without raising a `TypeError`:
5+
6+
uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file)
7+
File.read(uploaded_file)
8+
9+
*Aaron Kromer*
10+
111
* Pass along arguments to underlying `get` method in `follow_redirect!`
212

313
Now all arguments passed to `follow_redirect!` are passed to the underlying

actionpack/lib/action_dispatch/http/upload.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ def path
6565
@tempfile.path
6666
end
6767

68+
# Shortcut for +tempfile.to_path+.
69+
def to_path
70+
@tempfile.to_path
71+
end
72+
6873
# Shortcut for +tempfile.rewind+.
6974
def rewind
7075
@tempfile.rewind

actionpack/test/dispatch/uploaded_file_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ def test_delegate_eof_to_tempfile
103103
assert_predicate uf, :eof?
104104
end
105105

106+
def test_delegate_to_path_to_tempfile
107+
tf = Class.new { def to_path; "/any/file/path" end; }
108+
uf = Http::UploadedFile.new(tempfile: tf.new)
109+
assert_equal "/any/file/path", uf.to_path
110+
end
111+
106112
def test_respond_to?
107113
tf = Class.new { def read; yield end }
108114
uf = Http::UploadedFile.new(tempfile: tf.new)

0 commit comments

Comments
 (0)