Skip to content

Commit

Permalink
Support multiple file upload for ActiveStorage and CarrierWave
Browse files Browse the repository at this point in the history
Refs. #2990, Refs. #3037, Closes #2555, Closes #2916, Closes #3036
  • Loading branch information
mshibuya committed Jul 16, 2018
1 parent e1ec821 commit 5bb2d37
Show file tree
Hide file tree
Showing 20 changed files with 552 additions and 13 deletions.
17 changes: 17 additions & 0 deletions app/assets/javascripts/rails_admin/ra.widgets.coffee
Expand Up @@ -70,6 +70,23 @@ $(document).on 'rails_admin.dom_ready', (e, content) ->
else
image_container.hide()

# multiple-fileupload-preview

content.find('[data-multiple-fileupload]').change ->
input = this
$("#" + input.id).parent().children(".preview").remove()
for file in input.files
ext = file.name.split('.').pop().toLowerCase()
if $.inArray(ext, ['gif','png','jpg','jpeg','bmp']) == -1
continue
image_container = $('<img />').addClass('preview').addClass('img-thumbnail')
do (image_container) ->
reader = new FileReader()
reader.onload = (e) ->
image_container.attr "src", e.target.result
reader.readAsDataURL file
$("#" + input.id).parent().append($('<div></div>').addClass('preview').append(image_container))

# filtering-multiselect

content.find('[data-filteringmultiselect]').each ->
Expand Down
14 changes: 14 additions & 0 deletions app/views/rails_admin/main/_form_multiple_file_upload.html.haml
@@ -0,0 +1,14 @@
- field.attachments.each_with_index do |attachment, i|
.toggle
= attachment.pretty_value
- if field.delete_method
%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;"}
%i.icon-white.icon-trash
= I18n.t('admin.actions.delete.menu').capitalize + " #{field.label.downcase} ##{i + 1}"

= form.check_box(field.delete_method, {multiple:true, style: 'display:none;'}, attachment.delete_key, nil)

= form.file_field(field.name, field.html_attributes.reverse_merge({ data: { :"multiple-fileupload" => true }, multiple: true }))

- if field.cache_method
= form.hidden_field(field.cache_method)
15 changes: 11 additions & 4 deletions lib/rails_admin/config/fields/factories/active_storage.rb
Expand Up @@ -3,18 +3,25 @@
require 'rails_admin/config/fields/types/file_upload'

RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
if defined?(::ActiveStorage) && properties.is_a?(RailsAdmin::Adapters::ActiveRecord::Association) && (match = /\A(.+)_attachment\Z/.match properties.name) && properties.klass.to_s == 'ActiveStorage::Attachment'
if defined?(::ActiveStorage) && properties.is_a?(RailsAdmin::Adapters::ActiveRecord::Association) && (match = /\A(.+)_attachments?\Z/.match properties.name) && properties.klass.to_s == 'ActiveStorage::Attachment'
name = match[1]
field = RailsAdmin::Config::Fields::Types.load(:active_storage).new(parent, name, properties)
field = RailsAdmin::Config::Fields::Types.load(
properties.type == :has_many ? :multiple_active_storage : :active_storage,
).new(parent, name, properties)
fields << field
associations = ["#{name}_attachment".to_sym, "#{name}_blob".to_sym]
associations =
if properties.type == :has_many
["#{name}_attachments".to_sym, "#{name}_blobs".to_sym]
else
["#{name}_attachment".to_sym, "#{name}_blob".to_sym]
end
children_fields = associations.map do |child_name|
next unless child_association = parent.abstract_model.associations.detect { |p| p.name.to_sym == child_name }
child_field = fields.detect { |f| f.name == child_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_association, fields)
child_field.hide unless field == child_field
child_field.filterable(false) unless field == child_field
child_field.name
end.flatten
end.flatten.compact
field.children_fields(children_fields)
true
else
Expand Down
4 changes: 3 additions & 1 deletion lib/rails_admin/config/fields/factories/carrierwave.rb
Expand Up @@ -6,7 +6,9 @@
model = parent.abstract_model.model
if defined?(::CarrierWave) && model.is_a?(CarrierWave::Mount) && model.uploaders.include?(attachment_name = properties.name.to_s.chomp('_file_name').to_sym)
columns = [model.uploader_options[attachment_name][:mount_on] || attachment_name, "#{attachment_name}_content_type".to_sym, "#{attachment_name}_file_size".to_sym]
field = RailsAdmin::Config::Fields::Types.load(:carrierwave).new(parent, attachment_name, properties)
field = RailsAdmin::Config::Fields::Types.load(
[:serialized, :json].include?(properties.type) ? :multiple_carrierwave : :carrierwave,
).new(parent, attachment_name, properties)
fields << field
children_fields = []
columns.each do |children_column_name|
Expand Down
3 changes: 3 additions & 0 deletions lib/rails_admin/config/fields/types/all.rb
Expand Up @@ -12,6 +12,9 @@
require 'rails_admin/config/fields/types/paperclip'
require 'rails_admin/config/fields/types/carrierwave'
require 'rails_admin/config/fields/types/refile'
require 'rails_admin/config/fields/types/multiple_file_upload'
require 'rails_admin/config/fields/types/multiple_active_storage'
require 'rails_admin/config/fields/types/multiple_carrierwave'
require 'rails_admin/config/fields/types/float'
require 'rails_admin/config/fields/types/has_and_belongs_to_many_association'
require 'rails_admin/config/fields/types/has_many_association'
Expand Down
49 changes: 49 additions & 0 deletions lib/rails_admin/config/fields/types/multiple_active_storage.rb
@@ -0,0 +1,49 @@
require 'rails_admin/config/fields/types/multiple_file_upload'

module RailsAdmin
module Config
module Fields
module Types
class MultipleActiveStorage < RailsAdmin::Config::Fields::Types::MultipleFileUpload
RailsAdmin::Config::Fields::Types.register(self)

class ActiveStorageAttachment < RailsAdmin::Config::Fields::Types::MultipleFileUpload::AbstractAttachment
register_instance_option :thumb_method do
{resize: '100x100>'}
end

register_instance_option :delete_key do
value.id
end

register_instance_option :image? do
if value
value.filename.to_s.split('.').last =~ /jpg|jpeg|png|gif|svg/i
end
end

def resource_url(thumb = false)
return nil unless value
if thumb && value.variable?
variant = value.variant(thumb_method)
Rails.application.routes.url_helpers.rails_blob_representation_path(
variant.blob.signed_id, variant.variation.key, variant.blob.filename, only_path: true
)
else
Rails.application.routes.url_helpers.rails_blob_path(value, only_path: true)
end
end
end

register_instance_option :attachment_class do
ActiveStorageAttachment
end

register_instance_option :delete_method do
"remove_#{name}" if bindings[:object].respond_to?("remove_#{name}")
end
end
end
end
end
end
44 changes: 44 additions & 0 deletions lib/rails_admin/config/fields/types/multiple_carrierwave.rb
@@ -0,0 +1,44 @@
require 'rails_admin/config/fields/types/multiple_file_upload'

module RailsAdmin
module Config
module Fields
module Types
class MultipleCarrierwave < RailsAdmin::Config::Fields::Types::MultipleFileUpload
RailsAdmin::Config::Fields::Types.register(self)

class CarrierwaveAttachment < RailsAdmin::Config::Fields::Types::MultipleFileUpload::AbstractAttachment
register_instance_option :thumb_method do
@thumb_method ||= ((versions = value.versions.keys).detect { |k| k.in?([:thumb, :thumbnail, 'thumb', 'thumbnail']) } || versions.first.to_s)
end

register_instance_option :delete_key do
value.file.identifier
end

def resource_url(thumb = false)
return nil unless value
thumb.present? ? value.send(thumb).url : value.url
end
end

register_instance_option :attachment_class do
CarrierwaveAttachment
end

register_instance_option :cache_method do
"#{name}_cache"
end

register_instance_option :delete_method do
"delete_#{name}" if bindings[:object].respond_to?("delete_#{name}")
end

def value
bindings[:object].send(name)
end
end
end
end
end
end
106 changes: 106 additions & 0 deletions lib/rails_admin/config/fields/types/multiple_file_upload.rb
@@ -0,0 +1,106 @@
module RailsAdmin
module Config
module Fields
module Types
class MultipleFileUpload < RailsAdmin::Config::Fields::Base
RailsAdmin::Config::Fields::Types.register(self)

class AbstractAttachment
include RailsAdmin::Config::Proxyable
include RailsAdmin::Config::Configurable

attr_reader :value

def initialize(value)
@value = value
end

register_instance_option :thumb_method do
nil
end

register_instance_option :delete_key do
nil
end

register_instance_option :export_value do
resource_url.to_s
end

register_instance_option :pretty_value do
if value.presence
v = bindings[:view]
url = resource_url
if image
thumb_url = resource_url(thumb_method)
image_html = v.image_tag(thumb_url, class: 'img-thumbnail')
url != thumb_url ? v.link_to(image_html, url, target: '_blank') : image_html
else
v.link_to(value, url, target: '_blank')
end
end
end

register_instance_option :image? do
(url = resource_url.to_s) && url.split('.').last =~ /jpg|jpeg|png|gif|svg/i
end

def resource_url
raise('not implemented')
end
end

def initialize(*args)
super
@attachment_configurations = []
end

register_instance_option :attachment_class do
AbstractAttachment
end

register_instance_option :partial do
:form_multiple_file_upload
end

register_instance_option :cache_method do
nil
end

register_instance_option :delete_method do
nil
end

register_instance_option :allowed_methods do
[method_name, cache_method, delete_method].compact
end

register_instance_option :html_attributes do
{
required: required? && !value.present?,
}
end

def attachment(&block)
@attachment_configurations << block
end

def attachments
Array(value).map do |attached|
attachment = attachment_class.new(attached).with(bindings)
@attachment_configurations.each do |config|
attachment.instance_eval(&config)
end
attachment
end
end

# virtual class
def virtual?
true
end
end
end
end
end
end
16 changes: 14 additions & 2 deletions spec/controllers/rails_admin/main_controller_spec.rb
Expand Up @@ -378,6 +378,9 @@ class TeamWithNumberedPlayers < Team
it 'allows for delete method with Carrierwave' do
RailsAdmin.config FieldTest do
field :carrierwave_asset
field :carrierwave_assets do
delete_method :delete_carrierwave_assets
end
field :dragonfly_asset
field :paperclip_asset do
delete_method :delete_paperclip_asset
Expand All @@ -386,34 +389,43 @@ class TeamWithNumberedPlayers < Team
field :active_storage_asset do
delete_method :remove_active_storage_asset
end if defined?(ActiveStorage)
field :active_storage_assets do
delete_method :remove_active_storage_assets
end if defined?(ActiveStorage)
end
controller.params = HashWithIndifferentAccess.new(
'field_test' => {
'carrierwave_asset' => 'test',
'carrierwave_asset_cache' => 'test',
'remove_carrierwave_asset' => 'test',
'carrierwave_assets' => 'test',
'carrierwave_assets_cache' => 'test',
'delete_carrierwave_assets' => 'test',
'dragonfly_asset' => 'test',
'remove_dragonfly_asset' => 'test',
'retained_dragonfly_asset' => 'test',
'paperclip_asset' => 'test',
'delete_paperclip_asset' => 'test',
'should_not_be_here' => 'test',
}.merge(defined?(Refile) ? {'refile_asset' => 'test', 'remove_refile_asset' => 'test'} : {}).
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test'} : {}),
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test', 'active_storage_assets' => 'test', 'remove_active_storage_assets' => 'test'} : {}),
)

controller.send(:sanitize_params_for!, :create, RailsAdmin.config(FieldTest), controller.params['field_test'])
expect(controller.params[:field_test].to_h).to eq({
'carrierwave_asset' => 'test',
'remove_carrierwave_asset' => 'test',
'carrierwave_asset_cache' => 'test',
'carrierwave_assets' => 'test',
'carrierwave_assets_cache' => 'test',
'delete_carrierwave_assets' => 'test',
'dragonfly_asset' => 'test',
'remove_dragonfly_asset' => 'test',
'retained_dragonfly_asset' => 'test',
'paperclip_asset' => 'test',
'delete_paperclip_asset' => 'test',
}.merge(defined?(Refile) ? {'refile_asset' => 'test', 'remove_refile_asset' => 'test'} : {}).
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test'} : {}))
merge(defined?(ActiveStorage) ? {'active_storage_asset' => 'test', 'remove_active_storage_asset' => 'test', 'active_storage_assets' => 'test', 'remove_active_storage_assets' => 'test'} : {}))
end

it 'allows for polymorphic associations parameters' do
Expand Down
35 changes: 32 additions & 3 deletions spec/dummy_app/app/active_record/field_test.rb
Expand Up @@ -10,13 +10,42 @@ class FieldTest < ActiveRecord::Base
before_validation { self.paperclip_asset = nil if delete_paperclip_asset == '1' }

dragonfly_accessor :dragonfly_asset

mount_uploader :carrierwave_asset, CarrierwaveUploader
mount_uploaders :carrierwave_assets, CarrierwaveUploader
serialize :carrierwave_assets, JSON
attr_accessor :delete_carrierwave_assets
after_validation do
uploaders = carrierwave_assets.delete_if do |uploader|
if Array(delete_carrierwave_assets).include?(uploader.file.identifier)
uploader.remove!
true
end
end
write_attribute(:carrierwave_assets, uploaders.map { |uploader| uploader.file.identifier })
end
def carrierwave_assets=(files)
appended = files.map do |file|
uploader = _mounter(:carrierwave_assets).blank_uploader
uploader.cache! file
uploader
end
super(carrierwave_assets + appended)
end

attachment :refile_asset if defined?(Refile)

has_one_attached :active_storage_asset if defined?(ActiveStorage)
attr_accessor :remove_active_storage_asset
after_save { active_storage_asset.purge if remove_active_storage_asset == '1' }
if defined?(ActiveStorage)
has_one_attached :active_storage_asset
attr_accessor :remove_active_storage_asset
after_save { active_storage_asset.purge if remove_active_storage_asset == '1' }

has_many_attached :active_storage_assets
attr_accessor :remove_active_storage_assets
after_save do
Array(remove_active_storage_assets).each { |id| active_storage_assets.find_by_id(id).try(:purge) }
end
end

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

0 comments on commit 5bb2d37

Please sign in to comment.