Store id of attacher as attribute

Hello,

Is there any supported way to store the id of the attacher as an attribute, as we can store metadata attributes with the metadata_attributes plugin? Using shrine with activerecord.
I need to detect then the “id” change (when a file attached is change) and is already uploaded to store to trigger some actions through subscribers.
It seems the metadata_attributes changes are triggered on any method that calls Attacher#write, is that the right place to look at?

Thanks!

Hi :wave:

Yes, you can even go one level higher and override Attacher#column_values:

class Shrine::Attacher
  def column_values
    super.merge("#{name}_id": file&.id)
  end
end

If you have an image attachment, this will write to an image_id column.

Thanks for this one. Is this something useful as a plugin/PR or you think is a very special requirement?

The metadata attributes plugin doesn’t seem the right place to add it as it’s intended for the metadata hash.

Is this something useful as a plugin/PR or you think is a very special requirement?

Yeah, I don’t know how many people will need this functionality, so I’m not sure whether it belongs in the core. For your use case, could you use #<name>_changed?? It returns true when a new file is attached, otherwise it returns false, including when the cached file was promoted to permanent storage (and I don’t know whether you need to detect that as well):

if photo.image_changed?
  # ...
end

The metadata attributes plugin doesn’t seem the right place to add it as it’s intended for the metadata hash.

Yeah, it would be convenient if we had a file_attributes plugin that would include the metadata_attributes behaviour, but also allow you to forward id and storage. There is probably benefit in being able to have this data written in individual columns (like what Active Storage has), I’m just looking for a strong enough use case.

That was my first try, but on my use case #<name>_changed? is always returning false and that was the reason i was looking to have the id store (as i can’t check #<name>_data_changed? because it changed both in upload/caching and on promotion).

I’m not sure why is always returning false, maybe is because i’m using shrine with multiple uploads as explained here (object with nested shrine uploads with activerecord) and the save is done through the parent object, not the upload itself.

To be more specific, I have a gallery object with multiple image objects, so i save the gallery and all the images are automatically saved, but all the images that are created/updated return false for the image_changed? call.

This ugly hack on the image object works though

  def image_id_changed?
    image_id_changed = false

    if self.previous_changes['image_data'] && self.image.storage_key == :store #in store
      if self.previous_changes['image_data'][0]  #updated value
        previous = JSON.parse(self.previous_changes['image_data'][0])
        current = JSON.parse(self.previous_changes['image_data'][1])
        image_id_changed = (previous['id'] != current['id'])
      else
        image_id_changed = true  #new value
      end
    end
  
    return image_id_changed
  end

So having the id stored as a single column will make things easier. But i know that could be a very specific requests so i can live with a custom hack :wink:

Thanks for explaining. It’s strange that #<name>_changed? always returns false for you, the default multiple files setup should trigger dirty checking for nested resources, because it should still call model.attachment = in the end.

FWIW, the Attacher#column_values patch I proposed should work, as Attacher#column_values is public API.

@janko, i found out that the after_commit problem i mention on After commit on create callback not triggered was the reason i was getting always false on #<name>_changed?

Once i revert the order of the after_commit callbacks (putting first the one of the attacher, and then the one i was interested on), i found out that #<name>_changed? starts working.