I have an issue with ActiveStorage that seems to have occured because of the Rails 6.0 upgrade (from 5.2.x).
I have a custom admin interface where Pages have Fields, and each Field has an STI type
to create complex page layouts. The Image
type that uses Active Storage (it was Paperclip but was migrated after Rails 5.2 shipped) to store an image via a simple upload modal - no complicated direct upload JS.
The class structure looks as follows (I've removed extra code for brevity):
class Page < ApplicationRecord
has_many :fields, dependent: :destroy
accepts_nested_attributes_for :fields, allow_destroy: true
end
class Field < ApplicationRecord
belongs_to :page
end
module Fields
class Image < Content::Field
has_one_attached :image
accepts_nested_attributes_for :image_attachment, allow_destroy: true
end
end
When the page is updated, it's through nested form attributes with a simple call to @page.update(permitted_params)
.
The permitted_params
(minus unimportant params), looks like this...
{
"fields_attributes"=> {
"0"=> {
"type"=>"Fields::Image",
"image"=>#<ActionDispatch::Http::UploadedFile:0x00007ff36cdd9908 @tempfile=#<Tempfile:/var/folders/_t/clkdb5ms365617_039m7y7sc0000gn/T/RackMultipart20191021-38488-4kyp8k.jpg>, @original_filename="image.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"content_page[fields_attributes][0][image]\"; filename=\"image.jpg\"\r\nContent-Type: image/jpeg\r\n">,
"id"=>"8059"
}
}
}
To maintain the ActiveStorage 5.2 behaviour of updating a collection, rather than overwriting it, I have the following in my application.rb
:
config.active_storage.replace_on_assign_to_many = false
However, because the fields relationship is a has_one_attached
, this should not affect it.
From the logs, no upload starts and no updates are made to the Active Storage tables, but one update is written to the Page
table:
Content::Page Update (5.2ms) UPDATE `pages` SET `pages`.`updated_at` = '2019-10-21 13:58:18' WHERE `pages`.`id` = 105 /*application:myapp,controller:pages,action:update*/
Thats it though, nothing else.
However, if I byebug
in the update method and then perform the update manually:
$ img = Fields::Image.new(image: permitted_params.dig("fields_attributes", "0", "image"))
#<Content::Fields::Image id: nil, type: "Content::Fields::Image">
$ img.valid?
# true
$ img.save
(4.6ms) BEGIN
Field Load (1.5ms) SELECT `fields`.* FROM `fields` WHERE `fields`.`page_id` IS NULL AND (`fields`.`position` IS NOT NULL) ORDER BY `fields`.`position` DESC LIMIT 1
Fields::Image Create (1.7ms) INSERT INTO `fields` (`type`) VALUES ('Fields::Image')
ActiveStorage::Blob Load (1.6ms) SELECT `active_storage_blobs`.* FROM `active_storage_blobs` INNER JOIN `active_storage_attachments` ON `active_storage_blobs`.`id` = `active_storage_attachments`.`blob_id` WHERE `active_storage_attachments`.`record_id` = 8062 AND `active_storage_attachments`.`record_type` = 'Fields::Image' AND `active_storage_attachments`.`name` = 'image' ORDER BY `active_storage_attachments`.`position` ASC LIMIT 1
ActiveStorage::Attachment Load (1.6ms) SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 8062 AND `active_storage_attachments`.`record_type` = 'Fields::Image' AND `active_storage_attachments`.`name` = 'image' ORDER BY `active_storage_attachments`.`position` ASC LIMIT 1
ActiveStorage::Blob Create (2.7ms) INSERT INTO `active_storage_blobs` (`key`, `filename`) VALUES ('mnf9brbh9e1kyriummf11kpd7t6q', 'image.jpg')
ActiveStorage::Attachment Load (2.0ms) SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_type` = 'Fields::Image' AND `active_storage_attachments`.`name` = 'image' AND `active_storage_attachments`.`record_id` = 8062 AND (`active_storage_attachments`.`position` IS NOT NULL) ORDER BY `active_storage_attachments`.`position` DESC LIMIT 1
ActiveStorage::Attachment Create (4.1ms) INSERT INTO `active_storage_attachments` (`name`, `record_type`, `record_id`) VALUES ('image', 'Fields::Image')
Fields::Image Update (2.3ms) UPDATE `fields` SET `fields`.`updated_at` = '2019-10-21 14:24:06' WHERE `fields`.`id` = 8062
(2.0ms) COMMIT
FlatDisk Storage (5.3ms) Uploaded file to key: mnf9brbh9e1kyriummf11kpd7t6q (checksum: ms9Zu1oagR5kGjcci1YfhQ==)
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: 034d495a-01c1-4935-a873-b07fcec35f47) to Sidekiq(active_storage_analysis) with arguments: #<GlobalID:0x00007ff36c2e2090 @uri=#<URI::GID gid://dswt/ActiveStorage::Blob/52786>>
# true
So this leads me to think it might be something to do with the nested params, but I cannot see an issue with these, so I'm stumped as to what might be going on.
Image uploads function normally elsewhere on the site, its just in this one place that they are not working as expected.
Any help would be greatly appreciated.
Thank you, Paul.
EDIT:
I've narrowed it down to ActiveStorage not updating the image association unless a field on the model (updated_at
or whatever column lives on the table) is updated as well. This seems to trigger ActiveRecord to persist the instance, which triggers ActiveStorage to upload the file.
After a bunch of playing around I worked out that the Active Storage relationships were not being updated unless the model was altered in some way, for example, touching the updated_at
timestamp, or another editing another field - I think this is a Rails bug.
By Adding <%= f.hidden_field :updated_at, value: DateTime.current %>
in my forms, I was able to force Active Storage to persist the file.
Hope this helps someone else.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With