Skip to content

Commit baeff01

Browse files
dblockclaude
andcommitted
Add support for multiple files in files_upload_v2 method
- Add files parameter to accept array of file objects - Support both single file and multiple files upload - Add proper validation for file objects in array - Add comprehensive tests for multiple files scenarios - Maintain backward compatibility with existing single file usage Fixes #566 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent fb8f307 commit baeff01

File tree

4 files changed

+198
-22
lines changed

4 files changed

+198
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* [#559](https://github.com/slack-ruby/slack-ruby-client/pull/559): Enable name-to-id translation of non-public channels - [@eizengan](https://github.com/eizengan).
55
* [#560](https://github.com/slack-ruby/slack-ruby-client/pull/560): Name-to-id translation can supply all sensible list options - [@eizengan](https://github.com/eizengan).
66
* [#561](https://github.com/slack-ruby/slack-ruby-client/issues/563): Raise InvalidSignature when verifying a request without a signature - [@wesleyjellis](https://github.com/wesleyjellis).
7+
* [#567](https://github.com/slack-ruby/slack-ruby-client/pull/567): Add support for multiple files in `files_upload_v2` method - [@dblock](https://github.com/dblock).
78
* Your contribution here.
89

910
### 2.6.0 (2025/05/24)

lib/slack/web/api/helpers/files.rb

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ module Files
1313
# Name of the file being uploaded.
1414
# @option params [string] :content
1515
# File contents via a POST variable.
16+
# @option params [Array<Hash>] :files
17+
# Array of file objects with :filename, :content, and optionally :title, :alt_txt, :snippet_type.
1618
# @option params [string] :alt_txt
1719
# Description of image for screen-reader.
1820
# @option params [string] :snippet_type
@@ -32,8 +34,19 @@ module Files
3234
# Never use a reply's ts value; use its parent instead.
3335
# Also make sure to provide only one channel when using 'thread_ts'.
3436
def files_upload_v2(params = {})
35-
%i[filename content].each do |param|
36-
raise ArgumentError, "Required argument :#{param} missing" if params[param].nil?
37+
# Check if multiple files are being uploaded
38+
if params[:files] && params[:files].is_a?(Array)
39+
# Multiple files upload
40+
params[:files].each do |file|
41+
%i[filename content].each do |param|
42+
raise ArgumentError, "Required argument :#{param} missing in file" if file[param].nil?
43+
end
44+
end
45+
else
46+
# Single file upload (existing behavior)
47+
%i[filename content].each do |param|
48+
raise ArgumentError, "Required argument :#{param} missing" if params[param].nil?
49+
end
3750
end
3851

3952
channel_params = %i[channel channels channel_id].map { |param| params[param] }.compact
@@ -53,30 +66,65 @@ def files_upload_v2(params = {})
5366
complete_upload_request_params[:channel_id] = params[:channel_id]
5467
end
5568

56-
content = params[:content]
57-
title = params[:title] || params[:filename]
69+
if params[:files] && params[:files].is_a?(Array)
70+
# Multiple files upload
71+
uploaded_files = []
72+
73+
params[:files].each do |file|
74+
content = file[:content]
75+
title = file[:title] || file[:filename]
5876

59-
upload_url_request_params = params.slice(:filename, :alt_txt, :snippet_type)
60-
upload_url_request_params[:length] = content.bytesize
77+
upload_url_request_params = file.slice(:filename, :alt_txt, :snippet_type)
78+
upload_url_request_params[:length] = content.bytesize
6179

62-
# Get the upload url.
63-
get_upload_url_response = files_getUploadURLExternal(upload_url_request_params)
64-
upload_url = get_upload_url_response[:upload_url]
65-
file_id = get_upload_url_response[:file_id]
80+
# Get the upload url.
81+
get_upload_url_response = files_getUploadURLExternal(upload_url_request_params)
82+
upload_url = get_upload_url_response[:upload_url]
83+
file_id = get_upload_url_response[:file_id]
6684

67-
# Upload the file.
68-
::Faraday::Connection.new(upload_url, options) do |connection|
69-
connection.request :multipart
70-
connection.request :url_encoded
71-
connection.use ::Slack::Web::Faraday::Response::WrapError
72-
connection.response :logger, logger if logger
73-
connection.adapter adapter
74-
end.post do |request|
75-
request.body = content
76-
end
85+
# Upload the file.
86+
::Faraday::Connection.new(upload_url, options) do |connection|
87+
connection.request :multipart
88+
connection.request :url_encoded
89+
connection.use ::Slack::Web::Faraday::Response::WrapError
90+
connection.response :logger, logger if logger
91+
connection.adapter adapter
92+
end.post do |request|
93+
request.body = content
94+
end
95+
96+
uploaded_files << { id: file_id, title: title }
97+
end
98+
99+
# Complete the upload for all files.
100+
complete_upload_request_params[:files] = uploaded_files.to_json
101+
else
102+
# Single file upload (existing behavior)
103+
content = params[:content]
104+
title = params[:title] || params[:filename]
105+
106+
upload_url_request_params = params.slice(:filename, :alt_txt, :snippet_type)
107+
upload_url_request_params[:length] = content.bytesize
77108

78-
# Complete the upload.
79-
complete_upload_request_params[:files] = [{ id: file_id, title: title }].to_json
109+
# Get the upload url.
110+
get_upload_url_response = files_getUploadURLExternal(upload_url_request_params)
111+
upload_url = get_upload_url_response[:upload_url]
112+
file_id = get_upload_url_response[:file_id]
113+
114+
# Upload the file.
115+
::Faraday::Connection.new(upload_url, options) do |connection|
116+
connection.request :multipart
117+
connection.request :url_encoded
118+
connection.use ::Slack::Web::Faraday::Response::WrapError
119+
connection.response :logger, logger if logger
120+
connection.adapter adapter
121+
end.post do |request|
122+
request.body = content
123+
end
124+
125+
# Complete the upload.
126+
complete_upload_request_params[:files] = [{ id: file_id, title: title }].to_json
127+
end
80128

81129
files_completeUploadExternal(complete_upload_request_params)
82130
end

spec/fixtures/slack/web/files_upload_v2_multiple_files.yml

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/slack/web/api/endpoints/custom/files_spec.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,45 @@
141141
).files.size).to eq 1
142142
end
143143
end
144+
145+
context 'with multiple files', vcr: { cassette_name: 'web/files_upload_v2_multiple_files' } do
146+
it 'completes the upload with multiple files' do
147+
expect(client.files_upload_v2(
148+
files: [
149+
{ filename: 'test1.txt', content: 'First file contents', title: 'First File' },
150+
{ filename: 'test2.txt', content: 'Second file contents', title: 'Second File' }
151+
],
152+
channels: 'C08AZ76CA4V',
153+
initial_comment: 'Multiple files upload test'
154+
).files.size).to eq 2
155+
end
156+
end
157+
158+
context 'with multiple files missing filename' do
159+
it 'raises an argument error' do
160+
expect do
161+
client.files_upload_v2(
162+
files: [
163+
{ content: 'First file contents', title: 'First File' },
164+
{ filename: 'test2.txt', content: 'Second file contents' }
165+
],
166+
channels: 'C08AZ76CA4V'
167+
)
168+
end.to raise_error ArgumentError, /Required argument :filename missing in file/
169+
end
170+
end
171+
172+
context 'with multiple files missing content' do
173+
it 'raises an argument error' do
174+
expect do
175+
client.files_upload_v2(
176+
files: [
177+
{ filename: 'test1.txt', title: 'First File' },
178+
{ filename: 'test2.txt', content: 'Second file contents' }
179+
],
180+
channels: 'C08AZ76CA4V'
181+
)
182+
end.to raise_error ArgumentError, /Required argument :content missing in file/
183+
end
184+
end
144185
end

0 commit comments

Comments
 (0)