Skip to content

Commit 22b4973

Browse files
committed
Add support for ActiveStorage direct uploads
Closes #3296
1 parent ec83444 commit 22b4973

File tree

13 files changed

+161
-18
lines changed

13 files changed

+161
-18
lines changed

app/views/rails_admin/main/_form_file_upload.html.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<% if value = field.pretty_value %>
77
<%= value %>
88
<% end %>
9-
<%= form.file_field(field.name, field.html_attributes.reverse_merge({ data: { fileupload: true }})) %>
9+
<%= form.file_field(field.name, {data: {fileupload: true}}.deep_merge(field.html_attributes)) %>
1010
</div>
1111
<% if field.optional? && field.errors.blank? && file && field.delete_method %>
1212
<a class="btn btn-info btn-remove-image" data-toggle="button" href="#" role="button">

app/views/rails_admin/main/_form_multiple_file_upload.html.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<% end %>
1515
</div>
1616
<% end %>
17-
<%= form.file_field(field.name, field.html_attributes.reverse_merge({ data: { :"multiple-fileupload" => true }, multiple: true })) %>
17+
<%= form.file_field(field.name, { data: { :"multiple-fileupload" => true }, multiple: true }.deep_merge(field.html_attributes)) %>
1818
<% if field.cache_method %>
1919
<%= form.hidden_field(field.cache_method) %>
2020
<% end %>

lib/rails_admin/config/fields/types/active_storage.rb

+12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ class ActiveStorage < RailsAdmin::Config::Fields::Types::FileUpload
2828
{"#{name}_attachment": :blob}
2929
end
3030

31+
register_instance_option :direct? do
32+
false
33+
end
34+
35+
register_instance_option :html_attributes do
36+
{
37+
required: required? && !value.present?,
38+
}.merge(
39+
direct? && {data: {direct_upload_url: bindings[:view].main_app.rails_direct_uploads_url}} || {},
40+
)
41+
end
42+
3143
def resource_url(thumb = false)
3244
return nil unless value
3345

lib/rails_admin/config/fields/types/multiple_active_storage.rb

+12
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ def resource_url(thumb = false)
5858
register_instance_option :eager_load do
5959
{"#{name}_attachments": :blob}
6060
end
61+
62+
register_instance_option :direct? do
63+
false
64+
end
65+
66+
register_instance_option :html_attributes do
67+
{
68+
required: required? && !value.present?,
69+
}.merge(
70+
direct? && {data: {direct_upload_url: bindings[:view].main_app.rails_direct_uploads_url}} || {},
71+
)
72+
end
6173
end
6274
end
6375
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<% if defined?(ActiveStorage) %>
2+
//= require activestorage
3+
<% end %>
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
import "rails_admin/src/rails_admin/base";
22
import "flatpickr/dist/l10n/fr.js";
3+
import * as ActiveStorage from "@rails/activestorage";
4+
ActiveStorage.start();

spec/dummy_app/config/importmap.rails_admin.rb

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
pin '@hotwired/turbo-rails', to: 'https://ga.jspm.io/npm:@hotwired/[email protected]/app/javascript/turbo/index.js'
99
pin '@popperjs/core', to: 'https://ga.jspm.io/npm:@popperjs/[email protected]/dist/esm/popper.js'
1010
pin '@rails/actioncable/src', to: 'https://ga.jspm.io/npm:@rails/[email protected]/src/index.js'
11+
pin '@rails/activestorage', to: 'https://ga.jspm.io/npm:@rails/[email protected]/app/assets/javascripts/activestorage.esm.js'
1112
pin '@rails/ujs', to: 'https://ga.jspm.io/npm:@rails/[email protected]/lib/assets/compiled/rails-ujs.js'
1213
pin 'bootstrap', to: 'https://ga.jspm.io/npm:[email protected]/dist/js/bootstrap.esm.js'
1314
pin 'flatpickr', to: 'https://ga.jspm.io/npm:[email protected]/dist/flatpickr.js'

spec/dummy_app/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": "0.1.0",
55
"dependencies": {
66
"@rails/webpacker": "5.4.3",
7+
"@rails/activestorage": "^7.0.3-1",
78
"rails_admin": "file:../../",
89
"webpack": "^4.46.0",
910
"webpack-cli": "^3.3.12"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe 'ActiveStorage field', type: :request, active_record: true do
6+
subject { page }
7+
let(:field_test) { FactoryBot.create :field_test }
8+
before do
9+
# To suppress 'SQLite3::BusyException: database is locked' exception
10+
@original = page.driver.browser.url_blacklist # rubocop:disable Naming/InclusiveLanguage
11+
page.driver.browser.url_blacklist = ['/rails/active_storage/representations'] # rubocop:disable Naming/InclusiveLanguage
12+
end
13+
after { page.driver.browser.url_blacklist = @original } # rubocop:disable Naming/InclusiveLanguage
14+
15+
describe 'direct upload', js: true do
16+
before do
17+
RailsAdmin.config FieldTest do
18+
edit do
19+
field(:active_storage_asset) { direct true }
20+
end
21+
end
22+
end
23+
24+
it 'works' do
25+
visit edit_path(model_name: 'field_test', id: field_test.id)
26+
attach_file 'Active storage asset', file_path('test.jpg')
27+
expect_any_instance_of(ActiveStorage::DirectUploadsController).to receive(:create).and_call_original
28+
click_button 'Save'
29+
expect(page).to have_content 'Field test successfully updated'
30+
field_test.reload
31+
expect(field_test.active_storage_asset.filename).to eq 'test.jpg'
32+
end
33+
end
34+
end

spec/integration/fields/multiple_active_storage_spec.rb

+22-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
end
1313
# To suppress 'SQLite3::BusyException: database is locked' exception
1414
@original = page.driver.browser.url_blacklist # rubocop:disable Naming/InclusiveLanguage
15-
page.driver.browser.url_blacklist = ['/rails/active_storage/'] # rubocop:disable Naming/InclusiveLanguage
15+
page.driver.browser.url_blacklist = ['/rails/active_storage/representations'] # rubocop:disable Naming/InclusiveLanguage
1616
end
1717
after { page.driver.browser.url_blacklist = @original } # rubocop:disable Naming/InclusiveLanguage
1818

@@ -45,4 +45,25 @@
4545
expect(field_test.active_storage_assets.map { |image| image.filename.to_s }).to eq ['test.png']
4646
end
4747
end
48+
49+
describe 'direct upload', js: true do
50+
let(:field_test) { FactoryBot.create :field_test }
51+
before do
52+
RailsAdmin.config FieldTest do
53+
edit do
54+
configure(:active_storage_assets) { direct true }
55+
end
56+
end
57+
end
58+
59+
it 'works' do
60+
visit edit_path(model_name: 'field_test', id: field_test.id)
61+
attach_file 'Active storage assets', [file_path('test.jpg')]
62+
expect_any_instance_of(ActiveStorage::DirectUploadsController).to receive(:create).and_call_original
63+
click_button 'Save'
64+
expect(page).to have_content 'Field test successfully updated'
65+
field_test.reload
66+
expect(field_test.active_storage_assets.map { |image| image.filename.to_s }).to eq ['test.jpg']
67+
end
68+
end
4869
end

spec/rails_admin/config/fields/types/active_storage_spec.rb

+30
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,35 @@
9393
expect(field.eager_load).to eq({active_storage_asset_attachment: :blob})
9494
end
9595
end
96+
97+
describe '#direct' do
98+
let(:view) { double }
99+
let(:field) do
100+
RailsAdmin.config('FieldTest').fields.detect do |f|
101+
f.name == :active_storage_asset
102+
end.with(view: view)
103+
end
104+
before do
105+
allow(view).to receive_message_chain(:main_app, :rails_direct_uploads_url) { 'http://www.example.com/rails/active_storage/direct_uploads' }
106+
end
107+
108+
context 'when false' do
109+
it "doesn't put the direct upload url in html_attributes" do
110+
expect(field.html_attributes[:data]&.[](:direct_upload_url)).to be_nil
111+
end
112+
end
113+
114+
context 'when true' do
115+
before do
116+
RailsAdmin.config FieldTest do
117+
field(:active_storage_asset) { direct true }
118+
end
119+
end
120+
121+
it 'puts the direct upload url in html_attributes' do
122+
expect(field.html_attributes[:data]&.[](:direct_upload_url)).to eq 'http://www.example.com/rails/active_storage/direct_uploads'
123+
end
124+
end
125+
end
96126
end
97127
end

spec/rails_admin/config/fields/types/multiple_active_storage_spec.rb

+30
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,35 @@
9797
expect(field.eager_load).to eq({active_storage_assets_attachments: :blob})
9898
end
9999
end
100+
101+
describe '#direct' do
102+
let(:view) { double }
103+
let(:field) do
104+
RailsAdmin.config('FieldTest').fields.detect do |f|
105+
f.name == :active_storage_assets
106+
end.with(view: view)
107+
end
108+
before do
109+
allow(view).to receive_message_chain(:main_app, :rails_direct_uploads_url) { 'http://www.example.com/rails/active_storage/direct_uploads' }
110+
end
111+
112+
context 'when false' do
113+
it "doesn't put the direct upload url in html_attributes" do
114+
expect(field.html_attributes[:data]&.[](:direct_upload_url)).to be_nil
115+
end
116+
end
117+
118+
context 'when true' do
119+
before do
120+
RailsAdmin.config FieldTest do
121+
field(:active_storage_assets) { direct true }
122+
end
123+
end
124+
125+
it 'puts the direct upload url in html_attributes' do
126+
expect(field.html_attributes[:data]&.[](:direct_upload_url)).to eq 'http://www.example.com/rails/active_storage/direct_uploads'
127+
end
128+
end
129+
end
100130
end
101131
end

src/rails_admin/ui.js

+12-15
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,18 @@ import I18n from "./i18n";
8484
.each(function () {
8585
$(this).siblings(".control-group").hide();
8686
});
87-
$('button[name][type="submit"]')
88-
.attr("type", "button")
89-
.on("click", function () {
90-
var form = $(this).closest("form");
91-
form.append(
92-
$("<input />")
93-
.attr("type", "hidden")
94-
.attr("name", $(this).attr("name"))
95-
.attr("value", $(this).attr("value"))
96-
);
97-
if ($(this).is("[formnovalidate]")) {
98-
form.attr("novalidate", true);
99-
}
100-
form.trigger("submit");
101-
});
87+
$('button[name][type="submit"]').on("click", function () {
88+
var form = $(this).closest("form");
89+
form.append(
90+
$("<input />")
91+
.attr("type", "hidden")
92+
.attr("name", $(this).attr("name"))
93+
.attr("value", $(this).attr("value"))
94+
);
95+
if ($(this).is("[formnovalidate]")) {
96+
form.attr("novalidate", true);
97+
}
98+
});
10299
$.each($("#filters_box").data("options"), function () {
103100
$.filters.append(this);
104101
});

0 commit comments

Comments
 (0)