Module: RactorRailsShim::ActiveStorageAttachedPatch::BlobMetadataTypePatch

Defined in:
lib/ractor_rails_shim/patches/active_storage.rb

Overview

ActiveStorage::Blob#metadata is declared via store :metadata, which should register a Type::Serialized cast type. When ActiveStorage::Blob is freshly autoloaded in a worker Ractor's empty constant namespace (before the DB connection/schema is established), cast_types is built from an empty columns_hash, the :metadata decorator is silently skipped, and type_for_attribute(:metadata) returns a plain Type::Text/Type::Value. That makes store_accessor_for raise "the column 'metadata' has not been configured as a store". Force a Type::Serialized for :metadata so worker uploads work regardless of load order.

read_attribute(:metadata) does NOT go through type_for_attribute; it uses the cached @attributes set built from _default_attributes → attribute_types → cast_types, which memoizes a plain Type::Value if the class was loaded without the serialized decorator. We patch attribute_types to return Type::Serialized for :metadata, but since the frozen shared graph prevents writing class ivars in a worker, the REAL fix is to rebuild _default_attributes in MAIN at prepare_for_ractors! time (see _fix_blob_metadata_type! below), so the frozen graph carries the correct Type::Serialized from the start. The attribute_types / type_for_attribute overrides are fallbacks for the case where the class is freshly autoloaded in a worker (separate process).

Instance Method Summary collapse

Instance Method Details

#attribute_types ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/ractor_rails_shim/patches/active_storage.rb', line 105

def attribute_types
  types = super
  return types if types[:metadata].is_a?(::ActiveRecord::Type::Serialized)
  # Worker-safe: don't try to memoize (can't write class ivars in a
  # non-main Ractor). Return a fresh Hash with the corrected type each
  # call. The overhead is negligible (this path is worker-only).
  corrected = types.dup
  coder = ::ActiveRecord::Coders::JSON.new
  ind_coder = ::ActiveRecord::Store::IndifferentCoder.new(:metadata, coder)
  corrected[:metadata] = ::ActiveRecord::Type::Serialized.new(
    types[:metadata] || ::ActiveModel::Type::Value.new,
    ind_coder
  )
  corrected
end

#type_for_attribute(name = :__none__) ⇒ Object



96
97
98
99
100
101
102
103
# File 'lib/ractor_rails_shim/patches/active_storage.rb', line 96

def type_for_attribute(name = :__none__)
  return super if name != :metadata
  t = super(name)
  return t if defined?(::ActiveRecord::Type::Serialized) && t.is_a?(::ActiveRecord::Type::Serialized)
  coder = ::ActiveRecord::Coders::JSON.new
  ind_coder = ::ActiveRecord::Store::IndifferentCoder.new(:metadata, coder)
  ::ActiveRecord::Type::Serialized.new(t, ind_coder)
end