Skip to content

Commit e2330f7

Browse files
committed
Support multiple file upload for ActiveStorage and CarrierWave
Refs. #2990, Refs. #3037, Closes #2555, Closes #2916, Closes #3036
1 parent e1ec821 commit e2330f7

18 files changed

Lines changed: 547 additions & 11 deletions

File tree

app/assets/javascripts/rails_admin/ra.widgets.coffee

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ $(document).on 'rails_admin.dom_ready', (e, content) ->
7070
else
7171
image_container.hide()
7272

73+
# multiple-fileupload-preview
74+
75+
content.find('[data-multiple-fileupload]').change ->
76+
input = this
77+
$("#" + input.id).parent().children(".preview").remove()
78+
for file in input.files
79+
ext = file.name.split('.').pop().toLowerCase()
80+
if $.inArray(ext, ['gif','png','jpg','jpeg','bmp']) == -1
81+
continue
82+
image_container = $('<img />').addClass('preview').addClass('img-thumbnail')
83+
do (image_container) ->
84+
reader = new FileReader()
85+
reader.onload = (e) ->
86+
image_container.attr "src", e.target.result
87+
reader.readAsDataURL file
88+
$("#" + input.id).parent().append($('<div></div>').addClass('preview').append(image_container))
89+
7390
# filtering-multiselect
7491

7592
content.find('[data-filteringmultiselect]').each ->
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
- field.attachments.each_with_index do |attachment, i|
2+
.toggle
3+
= attachment.pretty_value
4+
- if field.delete_method
5+
%a.btn.btn-info.btn-remove-image{href: '#', :'data-toggle' => 'button', role: 'button', onclick: "$(this).siblings('[type=checkbox]').click(); $(this).parent('.toggle').toggle('slow'); jQuery(this).toggleClass('btn-danger btn-info'); return false;"}
6+
%i.icon-white.icon-trash
7+
= I18n.t('admin.actions.delete.menu').capitalize + " #{field.label.downcase} ##{i + 1}"
8+
9+
= form.check_box(field.delete_method, {multiple:true, style: 'display:none;'}, attachment.delete_key, nil)
10+
11+
= form.file_field(field.name, field.html_attributes.reverse_merge({ data: { "multiple-fileupload": true }, multiple: true }))
12+
13+
- if field.cache_method
14+
= form.hidden_field(field.cache_method)

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@
33
require 'rails_admin/config/fields/types/file_upload'
44

55
RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
6-
if defined?(::ActiveStorage) && properties.is_a?(RailsAdmin::Adapters::ActiveRecord::Association) && (match = /\A(.+)_attachment\Z/.match properties.name) && properties.klass.to_s == 'ActiveStorage::Attachment'
6+
if defined?(::ActiveStorage) && properties.is_a?(RailsAdmin::Adapters::ActiveRecord::Association) && (match = /\A(.+)_attachments?\Z/.match properties.name) && properties.klass.to_s == 'ActiveStorage::Attachment'
77
name = match[1]
8-
field = RailsAdmin::Config::Fields::Types.load(:active_storage).new(parent, name, properties)
8+
field = RailsAdmin::Config::Fields::Types.load(
9+
properties.type == :has_many ? :multiple_active_storage : :active_storage,
10+
).new(parent, name, properties)
911
fields << field
10-
associations = ["#{name}_attachment".to_sym, "#{name}_blob".to_sym]
12+
associations =
13+
if properties.type == :has_many
14+
["#{name}_attachments".to_sym, "#{name}_blobs".to_sym]
15+
else
16+
["#{name}_attachment".to_sym, "#{name}_blob".to_sym]
17+
end
1118
children_fields = associations.map do |child_name|
1219
next unless child_association = parent.abstract_model.associations.detect { |p| p.name.to_sym == child_name }
1320
child_field = fields.detect { |f| f.name == child_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_association, fields)
1421
child_field.hide unless field == child_field
1522
child_field.filterable(false) unless field == child_field
1623
child_field.name
17-
end.flatten
24+
end.flatten.compact
1825
field.children_fields(children_fields)
1926
true
2027
else

lib/rails_admin/config/fields/factories/carrierwave.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
model = parent.abstract_model.model
77
if defined?(::CarrierWave) && model.is_a?(CarrierWave::Mount) && model.uploaders.include?(attachment_name = properties.name.to_s.chomp('_file_name').to_sym)
88
columns = [model.uploader_options[attachment_name][:mount_on] || attachment_name, "#{attachment_name}_content_type".to_sym, "#{attachment_name}_file_size".to_sym]
9-
field = RailsAdmin::Config::Fields::Types.load(:carrierwave).new(parent, attachment_name, properties)
9+
field = RailsAdmin::Config::Fields::Types.load(
10+
[:serialized, :json].include?(properties.type) ? :multiple_carrierwave : :carrierwave,
11+
).new(parent, attachment_name, properties)
1012
fields << field
1113
children_fields = []
1214
columns.each do |children_column_name|

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
require 'rails_admin/config/fields/types/paperclip'
1313
require 'rails_admin/config/fields/types/carrierwave'
1414
require 'rails_admin/config/fields/types/refile'
15+
require 'rails_admin/config/fields/types/multiple_file_upload'
16+
require 'rails_admin/config/fields/types/multiple_active_storage'
17+
require 'rails_admin/config/fields/types/multiple_carrierwave'
1518
require 'rails_admin/config/fields/types/float'
1619
require 'rails_admin/config/fields/types/has_and_belongs_to_many_association'
1720
require 'rails_admin/config/fields/types/has_many_association'
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'rails_admin/config/fields/types/multiple_file_upload'
2+
3+
module RailsAdmin
4+
module Config
5+
module Fields
6+
module Types
7+
class MultipleActiveStorage < RailsAdmin::Config::Fields::Types::MultipleFileUpload
8+
RailsAdmin::Config::Fields::Types.register(self)
9+
10+
class ActiveStorageAttachment < RailsAdmin::Config::Fields::Types::MultipleFileUpload::AbstractAttachment
11+
register_instance_option :thumb_method do
12+
{resize: '100x100>'}
13+
end
14+
15+
register_instance_option :delete_key do
16+
value.id
17+
end
18+
19+
register_instance_option :image? do
20+
if value
21+
value.filename.to_s.split('.').last =~ /jpg|jpeg|png|gif|svg/i
22+
end
23+
end
24+
25+
def resource_url(thumb = false)
26+
return nil unless value
27+
if thumb && value.variable?
28+
variant = value.variant(thumb_method)
29+
Rails.application.routes.url_helpers.rails_blob_representation_path(
30+
variant.blob.signed_id, variant.variation.key, variant.blob.filename, only_path: true
31+
)
32+
else
33+
Rails.application.routes.url_helpers.rails_blob_path(value, only_path: true)
34+
end
35+
end
36+
end
37+
38+
register_instance_option :attachment_class do
39+
ActiveStorageAttachment
40+
end
41+
42+
register_instance_option :delete_method do
43+
"remove_#{name}" if bindings[:object].respond_to?("remove_#{name}")
44+
end
45+
end
46+
end
47+
end
48+
end
49+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
require 'rails_admin/config/fields/types/multiple_file_upload'
2+
3+
module RailsAdmin
4+
module Config
5+
module Fields
6+
module Types
7+
class MultipleCarrierwave < RailsAdmin::Config::Fields::Types::MultipleFileUpload
8+
RailsAdmin::Config::Fields::Types.register(self)
9+
10+
class CarrierwaveAttachment < RailsAdmin::Config::Fields::Types::MultipleFileUpload::AbstractAttachment
11+
register_instance_option :thumb_method do
12+
@thumb_method ||= ((versions = value.versions.keys).detect { |k| k.in?([:thumb, :thumbnail, 'thumb', 'thumbnail']) } || versions.first.to_s)
13+
end
14+
15+
register_instance_option :delete_key do
16+
value.file.identifier
17+
end
18+
19+
def resource_url(thumb = false)
20+
return nil unless value
21+
thumb.present? ? value.send(thumb).url : value.url
22+
end
23+
end
24+
25+
register_instance_option :attachment_class do
26+
CarrierwaveAttachment
27+
end
28+
29+
register_instance_option :cache_method do
30+
"#{name}_cache"
31+
end
32+
33+
register_instance_option :delete_method do
34+
"delete_#{name}" if bindings[:object].respond_to?("delete_#{name}")
35+
end
36+
37+
def value
38+
bindings[:object].send(name)
39+
end
40+
end
41+
end
42+
end
43+
end
44+
end
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
module RailsAdmin
2+
module Config
3+
module Fields
4+
module Types
5+
class MultipleFileUpload < RailsAdmin::Config::Fields::Base
6+
RailsAdmin::Config::Fields::Types.register(self)
7+
8+
class AbstractAttachment
9+
include RailsAdmin::Config::Proxyable
10+
include RailsAdmin::Config::Configurable
11+
12+
attr_reader :value
13+
14+
def initialize(value)
15+
@value = value
16+
end
17+
18+
register_instance_option :thumb_method do
19+
nil
20+
end
21+
22+
register_instance_option :delete_key do
23+
nil
24+
end
25+
26+
register_instance_option :export_value do
27+
resource_url.to_s
28+
end
29+
30+
register_instance_option :pretty_value do
31+
if value.presence
32+
v = bindings[:view]
33+
url = resource_url
34+
if image
35+
thumb_url = resource_url(thumb_method)
36+
image_html = v.image_tag(thumb_url, class: 'img-thumbnail')
37+
url != thumb_url ? v.link_to(image_html, url, target: '_blank') : image_html
38+
else
39+
v.link_to(value, url, target: '_blank')
40+
end
41+
end
42+
end
43+
44+
register_instance_option :image? do
45+
(url = resource_url.to_s) && url.split('.').last =~ /jpg|jpeg|png|gif|svg/i
46+
end
47+
48+
def resource_url
49+
raise('not implemented')
50+
end
51+
end
52+
53+
def initialize(*args)
54+
super
55+
@attachment_configurations = []
56+
end
57+
58+
register_instance_option :attachment_class do
59+
AbstractAttachment
60+
end
61+
62+
register_instance_option :partial do
63+
:form_multiple_file_upload
64+
end
65+
66+
register_instance_option :cache_method do
67+
nil
68+
end
69+
70+
register_instance_option :delete_method do
71+
nil
72+
end
73+
74+
register_instance_option :allowed_methods do
75+
[method_name, cache_method, delete_method].compact
76+
end
77+
78+
register_instance_option :html_attributes do
79+
{
80+
required: required? && !value.present?,
81+
}
82+
end
83+
84+
def attachment(&block)
85+
@attachment_configurations << block
86+
end
87+
88+
def attachments
89+
Array(value).map do |attached|
90+
attachment = attachment_class.new(attached).with(bindings)
91+
@attachment_configurations.each do |config|
92+
attachment.instance_eval(&config)
93+
end
94+
attachment
95+
end
96+
end
97+
98+
# virtual class
99+
def virtual?
100+
true
101+
end
102+
end
103+
end
104+
end
105+
end
106+
end

spec/controllers/rails_admin/main_controller_spec.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ class TeamWithNumberedPlayers < Team
378378
it 'allows for delete method with Carrierwave' do
379379
RailsAdmin.config FieldTest do
380380
field :carrierwave_asset
381+
field :carrierwave_assets do
382+
delete_method :delete_carrierwave_assets
383+
end
381384
field :dragonfly_asset
382385
field :paperclip_asset do
383386
delete_method :delete_paperclip_asset
@@ -386,34 +389,43 @@ class TeamWithNumberedPlayers < Team
386389
field :active_storage_asset do
387390
delete_method :remove_active_storage_asset
388391
end if defined?(ActiveStorage)
392+
field :active_storage_assets do
393+
delete_method :remove_active_storage_assets
394+
end if defined?(ActiveStorage)
389395
end
390396
controller.params = HashWithIndifferentAccess.new(
391397
'field_test' => {
392398
'carrierwave_asset' => 'test',
393399
'carrierwave_asset_cache' => 'test',
394400
'remove_carrierwave_asset' => 'test',
401+
'carrierwave_assets' => 'test',
402+
'carrierwave_assets_cache' => 'test',
403+
'delete_carrierwave_assets' => 'test',
395404
'dragonfly_asset' => 'test',
396405
'remove_dragonfly_asset' => 'test',
397406
'retained_dragonfly_asset' => 'test',
398407
'paperclip_asset' => 'test',
399408
'delete_paperclip_asset' => 'test',
400409
'should_not_be_here' => 'test',
401410
}.merge(defined?(Refile) ? {'refile_asset' => 'test', 'remove_refile_asset' => 'test'} : {}).
402-
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test'} : {}),
411+
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test', 'active_storage_assets' => 'test', 'remove_active_storage_assets' => 'test'} : {}),
403412
)
404413

405414
controller.send(:sanitize_params_for!, :create, RailsAdmin.config(FieldTest), controller.params['field_test'])
406415
expect(controller.params[:field_test].to_h).to eq({
407416
'carrierwave_asset' => 'test',
408417
'remove_carrierwave_asset' => 'test',
409418
'carrierwave_asset_cache' => 'test',
419+
'carrierwave_assets' => 'test',
420+
'carrierwave_assets_cache' => 'test',
421+
'delete_carrierwave_assets' => 'test',
410422
'dragonfly_asset' => 'test',
411423
'remove_dragonfly_asset' => 'test',
412424
'retained_dragonfly_asset' => 'test',
413425
'paperclip_asset' => 'test',
414426
'delete_paperclip_asset' => 'test',
415427
}.merge(defined?(Refile) ? {'refile_asset' => 'test', 'remove_refile_asset' => 'test'} : {}).
416-
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test'} : {}))
428+
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test', 'active_storage_assets' => 'test', 'remove_active_storage_assets' => 'test'} : {}))
417429
end
418430

419431
it 'allows for polymorphic associations parameters' do

spec/dummy_app/app/active_record/field_test.rb

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,42 @@ class FieldTest < ActiveRecord::Base
1010
before_validation { self.paperclip_asset = nil if delete_paperclip_asset == '1' }
1111

1212
dragonfly_accessor :dragonfly_asset
13+
1314
mount_uploader :carrierwave_asset, CarrierwaveUploader
15+
mount_uploaders :carrierwave_assets, CarrierwaveUploader
16+
serialize :carrierwave_assets, JSON
17+
attr_accessor :delete_carrierwave_assets
18+
after_validation do
19+
uploaders = carrierwave_assets.delete_if do |uploader|
20+
if Array(delete_carrierwave_assets).include?(uploader.file.identifier)
21+
uploader.remove!
22+
true
23+
end
24+
end
25+
write_attribute(:carrierwave_assets, uploaders.map { |uploader| uploader.file.identifier })
26+
end
27+
def carrierwave_assets=(files)
28+
appended = files.map do |file|
29+
uploader = _mounter(:carrierwave_assets).blank_uploader
30+
uploader.cache! file
31+
uploader
32+
end
33+
super(carrierwave_assets + appended)
34+
end
1435

1536
attachment :refile_asset if defined?(Refile)
1637

17-
has_one_attached :active_storage_asset if defined?(ActiveStorage)
18-
attr_accessor :remove_active_storage_asset
19-
after_save { active_storage_asset.purge if remove_active_storage_asset == '1' }
38+
if defined?(ActiveStorage)
39+
has_one_attached :active_storage_asset
40+
attr_accessor :remove_active_storage_asset
41+
after_save { active_storage_asset.purge if remove_active_storage_asset == '1' }
42+
43+
has_many_attached :active_storage_assets
44+
attr_accessor :remove_active_storage_assets
45+
after_save do
46+
Array(remove_active_storage_assets).each { |id| active_storage_assets.find_by_id(id).try(:purge) }
47+
end
48+
end
2049

2150
if ::Rails.version >= '4.1' # enum support was added in Rails 4.1
2251
enum string_enum_field: {S: 's', M: 'm', L: 'l'}

0 commit comments

Comments
 (0)