1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #43511 from seanpdoyle/active-storage-overwrite-many-with-emptyish

ActiveStorage: support empty attachments submits
This commit is contained in:
Rafael Mendonça França 2021-12-14 19:52:34 -05:00 committed by GitHub
commit 16f026e15a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 112 additions and 3 deletions

View file

@ -1,3 +1,16 @@
* Support `include_hidden:` option in calls to
`ActionView::Helper::FormBuilder#file_field` with `multiple: true` to
support submitting an empty collection of files.
```ruby
form.file_field :attachments, multiple: true
# => <input type="hidden" autocomplete="off" name="post[attachments][]" value="">
<input type="file" multiple="multiple" id="post_attachments" name="post[attachments][]">
form.file_field :attachments, multiple: true, include_hidden: false
# => <input type="file" multiple="multiple" id="post_attachments" name="post[attachments][]">
```
*Sean Doyle*
Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/actionview/CHANGELOG.md) for previous changes.

View file

@ -478,6 +478,8 @@ module ActionView
mattr_accessor :form_with_generates_ids, default: false
mattr_accessor :multiple_file_field_include_hidden, default: false
# Creates a form tag based on mixing URLs, scopes, or models.
#
# # Using just a URL:
@ -1218,6 +1220,7 @@ module ActionView
# * Creates standard HTML attributes for the tag.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
# * <tt>:include_hidden</tt> - When <tt>multiple: true</tt> and <tt>include_hidden: true</tt>, the field will be prefixed with an <tt><input type="hidden"></tt> field with an empty value to support submitting an empty collection of files.
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
#
# ==== Examples
@ -1236,7 +1239,9 @@ module ActionView
# file_field(:attachment, :file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
def file_field(object_name, method, options = {})
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(method, options.dup)).render
options = { include_hidden: multiple_file_field_include_hidden }.merge!(options)
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(method, options)).render
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@ -2489,6 +2494,7 @@ module ActionView
# * Creates standard HTML attributes for the tag.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
# * <tt>:include_hidden</tt> - When <tt>multiple: true</tt> and <tt>include_hidden: true</tt>, the field will be prefixed with an <tt><input type="hidden"></tt> field with an empty value to support submitting an empty collection of files.
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
#
# ==== Examples

View file

@ -4,6 +4,22 @@ module ActionView
module Helpers
module Tags # :nodoc:
class FileField < TextField # :nodoc:
def render
include_hidden = @options.delete(:include_hidden)
options = @options.stringify_keys
add_default_name_and_id(options)
if options["multiple"] && include_hidden
hidden_field_for_multiple_file(options) + super
else
super
end
end
private
def hidden_field_for_multiple_file(options)
tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off")
end
end
end
end

View file

@ -552,11 +552,38 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, file_field("import", "file", multiple: true)
end
def test_file_field_with_multiple_behavior_configured_include_hidden
old_value = ActionView::Helpers::FormHelper.multiple_file_field_include_hidden
ActionView::Helpers::FormHelper.multiple_file_field_include_hidden = true
expected = '<input type="hidden" name="import[file][]" autocomplete="off" value="">' \
'<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
assert_dom_equal expected, file_field("import", "file", multiple: true)
ensure
ActionView::Helpers::FormHelper.multiple_file_field_include_hidden = old_value
end
def test_file_field_with_multiple_behavior_include_hidden_false
expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
assert_dom_equal expected, file_field("import", "file", multiple: true, include_hidden: false)
end
def test_file_field_with_multiple_behavior_and_explicit_name
expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />'
assert_dom_equal expected, file_field("import", "file", multiple: true, name: "custom")
end
def test_file_field_with_multiple_behavior_and_explicit_name_configured_include_hidden
old_value = ActionView::Helpers::FormHelper.multiple_file_field_include_hidden
ActionView::Helpers::FormHelper.multiple_file_field_include_hidden = true
expected = '<input type="hidden" name="custom" autocomplete="off" value="">' \
'<input id="import_file" multiple="multiple" name="custom" type="file" />'
assert_dom_equal expected, file_field("import", "file", multiple: true, name: "custom")
ensure
ActionView::Helpers::FormHelper.multiple_file_field_include_hidden = old_value
end
def test_file_field_with_direct_upload_when_rails_direct_uploads_url_is_not_defined
expected = '<input type="file" name="import[file]" id="import_file" />'
assert_dom_equal expected, file_field("import", "file", direct_upload: true)

View file

@ -1,3 +1,10 @@
* Support transforming empty-ish `has_many_attached` value into `[]` (e.g. `[""]`)
```ruby
@user.highlights = [""]
@user.highlights # => []
```
*Sean Doyle*
Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md) for previous changes.

View file

@ -137,9 +137,11 @@ module ActiveStorage
end
def #{name}=(attachables)
attachables = Array(attachables).compact_blank
if ActiveStorage.replace_on_assign_to_many
attachment_changes["#{name}"] =
if Array(attachables).none?
if attachables.none?
ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
else
ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
@ -151,7 +153,7 @@ module ActiveStorage
"To append new attachables to the Active Storage association, prefer using `attach`. " \
"Using association setter would result in purging the existing attached attachments and replacing them with new ones."
if Array(attachables).any?
if attachables.any?
attachment_changes["#{name}"] =
ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, #{name}.blobs + attachables)
end

View file

@ -154,6 +154,18 @@ module ActiveStorage
end
end
initializer "action_view.configuration" do
config.after_initialize do |app|
ActiveSupport.on_load(:action_view) do
multiple_file_field_include_hidden = app.config.active_storage.delete(:multiple_file_field_include_hidden)
unless multiple_file_field_include_hidden.nil?
ActionView::Helpers::FormHelper.multiple_file_field_include_hidden = multiple_file_field_include_hidden
end
end
end
end
initializer "active_storage.asset" do
if Rails.application.config.respond_to?(:assets)
Rails.application.config.assets.precompile += %w( activestorage activestorage.esm )

View file

@ -209,6 +209,22 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
assert_equal "wherever.mp4", @user.vlogs.second.filename.to_s
end
test "replacing attachments with an empty list" do
@user.highlights = []
assert_empty @user.highlights
end
test "replacing attachments with a list containing empty items" do
@user.highlights = [""]
assert_empty @user.highlights
end
test "replacing attachments with a list containing a mixture of empty and present items" do
@user.highlights = [ "", fixture_file_upload("racecar.jpg") ]
assert_equal 1, @user.highlights.size
assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
end
test "successfully updating an existing record to replace existing, dependent attachments" do
[ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |old_blobs|
@user.highlights.attach old_blobs

View file

@ -1719,6 +1719,13 @@ config.active_storage.video_preview_arguments = "-vf 'select=eq(n\\,0)+eq(key\\,
1. `select=eq(n\,0)+eq(key\,1)+gt(scene\,0.015)`: Select the first video frame, plus keyframes, plus frames that meet the scene change threshold.
2. `loop=loop=-1:size=2,trim=start_frame=1`: To use the first video frame as a fallback when no other frames meet the criteria, loop the first (one or) two selected frames, then drop the first looped frame.
#### `config.active_storage.multiple_file_field_include_hidden`
In Rails 7.1 and beyond, Active Storage `has_many_attached` relationships will
default to _replacing_ the current collection instead of _appending_ to it. To
support submitting an _empty_ collection, render an auxiliary hidden field
similar to how Action View Form Builder render checkbox elements.
### Configuring Action Text
#### `config.action_text.attachment_tag_name`
@ -1747,6 +1754,7 @@ Accepts a string for the HTML tag used to wrap attachments. Defaults to `"action
- `config.action_dispatch.return_only_request_media_type_on_content_type`: `false`
- `config.action_mailer.smtp_timeout`: `5`
- `config.active_storage.video_preview_arguments`: `"-vf 'select=eq(n\\,0)+eq(key\\,1)+gt(scene\\,0.015),loop=loop=-1:size=2,trim=start_frame=1' -frames:v 1 -f image2"`
- `config.active_storage.multiple_file_field_include_hidden`: `true`
- `config.active_record.automatic_scope_inversing`: `true`
- `config.active_record.verify_foreign_keys_for_fixtures`: `true`
- `config.active_record.partial_inserts`: `false`
@ -1845,6 +1853,7 @@ Accepts a string for the HTML tag used to wrap attachments. Defaults to `"action
- `ActiveSupport.utc_to_local_returns_utc_offset_times`: `false`
- `config.action_mailer.smtp_timeout`: `nil`
- `config.active_storage.video_preview_arguments`: `"-y -vframes 1 -f image2"`
- `config.active_storage.multiple_file_field_include_hidden`: `false`
- `config.active_storage.variant_processor`: `:mini_magick`
- `config.action_controller.wrap_parameters_by_default`: `false`
- `config.action_dispatch.default_headers`:

View file

@ -236,6 +236,7 @@ module Rails
" -frames:v 1 -f image2"
active_storage.variant_processor = :vips
active_storage.multiple_file_field_include_hidden = true
end
if respond_to?(:active_record)