Skip to content

Commit fb36712

Browse files
authored
Merge pull request rails#38957 from DmitryTsepelev/store-per-attachment-direct-upload
Pass service_name param to DirectUploadsController
2 parents 6ddd2cf + 193289d commit fb36712

File tree

18 files changed

+433
-151
lines changed

18 files changed

+433
-151
lines changed

actiontext/app/assets/javascripts/actiontext.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -506,14 +506,16 @@ var activestorage = {exports: {}};
506506
}
507507
}
508508
class BlobRecord {
509-
constructor(file, checksum, url) {
509+
constructor(file, checksum, url, directUploadToken, attachmentName) {
510510
this.file = file;
511511
this.attributes = {
512512
filename: file.name,
513513
content_type: file.type || "application/octet-stream",
514514
byte_size: file.size,
515-
checksum: checksum
515+
checksum: checksum,
516516
};
517+
this.directUploadToken = directUploadToken;
518+
this.attachmentName = attachmentName;
517519
this.xhr = new XMLHttpRequest;
518520
this.xhr.open("POST", url, true);
519521
this.xhr.responseType = "json";
@@ -541,7 +543,9 @@ var activestorage = {exports: {}};
541543
create(callback) {
542544
this.callback = callback;
543545
this.xhr.send(JSON.stringify({
544-
blob: this.attributes
546+
blob: this.attributes,
547+
direct_upload_token: this.directUploadToken,
548+
attachment_name: this.attachmentName
545549
}));
546550
}
547551
requestDidLoad(event) {
@@ -599,10 +603,12 @@ var activestorage = {exports: {}};
599603
}
600604
let id = 0;
601605
class DirectUpload {
602-
constructor(file, url, delegate) {
606+
constructor(file, url, directUploadToken, attachmentName, delegate) {
603607
this.id = ++id;
604608
this.file = file;
605609
this.url = url;
610+
this.directUploadToken = directUploadToken;
611+
this.attachmentName = attachmentName;
606612
this.delegate = delegate;
607613
}
608614
create(callback) {
@@ -611,7 +617,7 @@ var activestorage = {exports: {}};
611617
callback(error);
612618
return;
613619
}
614-
const blob = new BlobRecord(this.file, checksum, this.url);
620+
const blob = new BlobRecord(this.file, checksum, this.url, this.directUploadToken, this.attachmentName);
615621
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
616622
blob.create((error => {
617623
if (error) {
@@ -640,7 +646,7 @@ var activestorage = {exports: {}};
640646
constructor(input, file) {
641647
this.input = input;
642648
this.file = file;
643-
this.directUpload = new DirectUpload(this.file, this.url, this);
649+
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this);
644650
this.dispatch("initialize");
645651
}
646652
start(callback) {
@@ -671,6 +677,12 @@ var activestorage = {exports: {}};
671677
get url() {
672678
return this.input.getAttribute("data-direct-upload-url");
673679
}
680+
get directUploadToken() {
681+
return this.input.getAttribute("data-direct-upload-token");
682+
}
683+
get attachmentName() {
684+
return this.input.getAttribute("data-direct-upload-attachment-name");
685+
}
674686
dispatch(name, detail = {}) {
675687
detail.file = this.file;
676688
detail.id = this.directUpload.id;
@@ -830,7 +842,7 @@ class AttachmentUpload {
830842
constructor(attachment, element) {
831843
this.attachment = attachment;
832844
this.element = element;
833-
this.directUpload = new activestorage.exports.DirectUpload(attachment.file, this.directUploadUrl, this);
845+
this.directUpload = new activestorage.exports.DirectUpload(attachment.file, this.directUploadUrl, this.directUploadToken, this.directUploadAttachmentName, this);
834846
}
835847

836848
start() {
@@ -865,6 +877,14 @@ class AttachmentUpload {
865877
return this.element.dataset.directUploadUrl
866878
}
867879

880+
get directUploadToken() {
881+
return this.element.dataset.directUploadToken
882+
}
883+
884+
get directUploadAttachmentName() {
885+
return this.element.dataset.directUploadAttachmentName
886+
}
887+
868888
get blobUrlTemplate() {
869889
return this.element.dataset.blobUrlTemplate
870890
}

actiontext/app/helpers/action_text/tag_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ def rich_text_area_tag(name, value = nil, options = {})
3232
options[:data][:direct_upload_url] ||= main_app.rails_direct_uploads_url
3333
options[:data][:blob_url_template] ||= main_app.rails_service_blob_url(":signed_id", ":filename")
3434

35+
class_with_attachment = "ActionText::RichText#embeds"
36+
options[:data][:direct_upload_attachment_name] ||= class_with_attachment
37+
options[:data][:direct_upload_token] = ActiveStorage::DirectUploadToken.generate_direct_upload_token(
38+
class_with_attachment,
39+
ActiveStorage::Blob.service.name,
40+
session
41+
)
42+
3543
editor_tag = content_tag("trix-editor", "", options)
3644
input_tag = hidden_field_tag(name, value.try(:to_trix_html) || value, id: options[:input], form: form)
3745

actiontext/test/template/form_helper_test.rb

Lines changed: 135 additions & 108 deletions
Large diffs are not rendered by default.

actionview/lib/action_view/helpers/form_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@ def hidden_field(object_name, method, options = {})
12391239
# file_field(:attachment, :file, class: 'file_input')
12401240
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
12411241
def file_field(object_name, method, options = {})
1242-
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
1242+
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(method, options.dup)).render
12431243
end
12441244

12451245
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)

actionview/lib/action_view/helpers/form_tag_helper.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def hidden_field_tag(name, value = nil, options = {})
319319
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
320320
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
321321
def file_field_tag(name, options = {})
322-
text_field_tag(name, nil, convert_direct_upload_option_to_url(options.merge(type: :file)))
322+
text_field_tag(name, nil, convert_direct_upload_option_to_url(name, options.merge(type: :file)))
323323
end
324324

325325
# Creates a password field, a masked text field that will hide the users input behind a mask character.
@@ -961,9 +961,23 @@ def set_default_disable_with(value, tag_options)
961961
tag_options.delete("data-disable-with")
962962
end
963963

964-
def convert_direct_upload_option_to_url(options)
964+
def convert_direct_upload_option_to_url(name, options)
965965
if options.delete(:direct_upload) && respond_to?(:rails_direct_uploads_url)
966966
options["data-direct-upload-url"] = rails_direct_uploads_url
967+
968+
if options[:object] && options[:object].class.respond_to?(:reflect_on_attachment)
969+
attachment_reflection = options[:object].class.reflect_on_attachment(name)
970+
971+
class_with_attachment = "#{options[:object].class.name.underscore}##{name}"
972+
options["data-direct-upload-attachment-name"] = class_with_attachment
973+
974+
service_name = attachment_reflection.options[:service_name] || ActiveStorage::Blob.service.name
975+
options["data-direct-upload-token"] = ActiveStorage::DirectUploadToken.generate_direct_upload_token(
976+
class_with_attachment,
977+
service_name,
978+
session
979+
)
980+
end
967981
end
968982
options
969983
end

activestorage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Support direct uploads to multiple services.
2+
3+
*Dmitry Tsepelev*
4+
15
* Invalid default content types are deprecated
26

37
Blobs created with content_type `image/jpg`, `image/pjpeg`, `image/bmp`, `text/javascript` will now produce

activestorage/app/assets/javascripts/activestorage.esm.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -508,14 +508,16 @@ function toArray(value) {
508508
}
509509

510510
class BlobRecord {
511-
constructor(file, checksum, url) {
511+
constructor(file, checksum, url, directUploadToken, attachmentName) {
512512
this.file = file;
513513
this.attributes = {
514514
filename: file.name,
515515
content_type: file.type || "application/octet-stream",
516516
byte_size: file.size,
517517
checksum: checksum
518518
};
519+
this.directUploadToken = directUploadToken;
520+
this.attachmentName = attachmentName;
519521
this.xhr = new XMLHttpRequest;
520522
this.xhr.open("POST", url, true);
521523
this.xhr.responseType = "json";
@@ -543,7 +545,9 @@ class BlobRecord {
543545
create(callback) {
544546
this.callback = callback;
545547
this.xhr.send(JSON.stringify({
546-
blob: this.attributes
548+
blob: this.attributes,
549+
direct_upload_token: this.directUploadToken,
550+
attachment_name: this.attachmentName
547551
}));
548552
}
549553
requestDidLoad(event) {
@@ -604,10 +608,12 @@ class BlobUpload {
604608
let id = 0;
605609

606610
class DirectUpload {
607-
constructor(file, url, delegate) {
611+
constructor(file, url, serviceName, attachmentName, delegate) {
608612
this.id = ++id;
609613
this.file = file;
610614
this.url = url;
615+
this.serviceName = serviceName;
616+
this.attachmentName = attachmentName;
611617
this.delegate = delegate;
612618
}
613619
create(callback) {
@@ -616,7 +622,7 @@ class DirectUpload {
616622
callback(error);
617623
return;
618624
}
619-
const blob = new BlobRecord(this.file, checksum, this.url);
625+
const blob = new BlobRecord(this.file, checksum, this.url, this.serviceName, this.attachmentName);
620626
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
621627
blob.create((error => {
622628
if (error) {
@@ -647,7 +653,7 @@ class DirectUploadController {
647653
constructor(input, file) {
648654
this.input = input;
649655
this.file = file;
650-
this.directUpload = new DirectUpload(this.file, this.url, this);
656+
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this);
651657
this.dispatch("initialize");
652658
}
653659
start(callback) {
@@ -678,6 +684,12 @@ class DirectUploadController {
678684
get url() {
679685
return this.input.getAttribute("data-direct-upload-url");
680686
}
687+
get directUploadToken() {
688+
return this.input.getAttribute("data-direct-upload-token");
689+
}
690+
get attachmentName() {
691+
return this.input.getAttribute("data-direct-upload-attachment-name");
692+
}
681693
dispatch(name, detail = {}) {
682694
detail.file = this.file;
683695
detail.id = this.directUpload.id;

activestorage/app/assets/javascripts/activestorage.js

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

activestorage/app/controllers/active_storage/direct_uploads_controller.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
# When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference
55
# the blob that was created up front.
66
class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
7+
include ActiveStorage::DirectUploadToken
8+
79
def create
8-
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
10+
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args.merge(service_name: verified_service_name))
911
render json: direct_upload_json(blob)
1012
end
1113

@@ -14,6 +16,10 @@ def blob_args
1416
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
1517
end
1618

19+
def verified_service_name
20+
ActiveStorage::DirectUploadToken.verify_direct_upload_token(params[:direct_upload_token], params[:attachment_name], session)
21+
end
22+
1723
def direct_upload_json(blob)
1824
blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
1925
url: blob.service_url_for_direct_upload,

activestorage/app/javascript/activestorage/blob_record.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { getMetaValue } from "./helpers"
22

33
export class BlobRecord {
4-
constructor(file, checksum, url) {
4+
constructor(file, checksum, url, directUploadToken, attachmentName) {
55
this.file = file
66

77
this.attributes = {
88
filename: file.name,
99
content_type: file.type || "application/octet-stream",
1010
byte_size: file.size,
11-
checksum: checksum
11+
checksum: checksum,
1212
}
1313

14+
this.directUploadToken = directUploadToken
15+
this.attachmentName = attachmentName
16+
1417
this.xhr = new XMLHttpRequest
1518
this.xhr.open("POST", url, true)
1619
this.xhr.responseType = "json"
@@ -43,7 +46,11 @@ export class BlobRecord {
4346

4447
create(callback) {
4548
this.callback = callback
46-
this.xhr.send(JSON.stringify({ blob: this.attributes }))
49+
this.xhr.send(JSON.stringify({
50+
blob: this.attributes,
51+
direct_upload_token: this.directUploadToken,
52+
attachment_name: this.attachmentName
53+
}))
4754
}
4855

4956
requestDidLoad(event) {

0 commit comments

Comments
 (0)