Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActiveStorage not persisting blob to attachment after Rails 6.0 upgrade

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.

like image 281
Paul Danelli Avatar asked Mar 03 '23 06:03

Paul Danelli


1 Answers

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.

like image 188
Paul Danelli Avatar answered Apr 28 '23 17:04

Paul Danelli