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:
commit
16f026e15a
10 changed files with 112 additions and 3 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`:
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue