Active Storage Attached Model

Provides the class-level DSL for declaring an Active Record modelโ€™s attachments.

Methods

Instance Public methods

*_attachment

Returns the attachment for the has_one_attached.

User.last.avatar_attachment
๐Ÿ”Ž See on GitHub

*_attachments

Returns the attachments for the has_many_attached.

Gallery.last.photos_attachments
๐Ÿ”Ž See on GitHub

*_blob

Returns the blob for the has_one_attached attachment.

User.last.avatar_blob
๐Ÿ”Ž See on GitHub

*_blobs

Returns the blobs for the has_many_attached attachments.

Gallery.last.photos_blobs
๐Ÿ”Ž See on GitHub

has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)

Specifies the relation between multiple attachments and the model.

class Gallery < ApplicationRecord
  has_many_attached :photos
end

There are no columns defined on the model side, Active Storage takes care of the mapping between your records and the attachments.

Under the covers, this relationship is implemented as a has_many association to a ActiveStorage::Attachment record and a has_many-through association to a ActiveStorage::Blob record. These associations are available as photos_attachments and photos_blobs. But you shouldnโ€™t need to work with these associations directly in most circumstances.

Instead, has_many_attached generates an ActiveStorage::Attached::Many proxy to provide access to the associations and factory methods, like attach:

user.photos.attach(uploaded_file)

The :dependent option defaults to :purge_later. This means the attachments will be purged (i.e. destroyed) in the background whenever the record is destroyed. If an ActiveJob::Backend queue adapter is not set in the application set it to purge instead.

If you need the attachment to use a service which differs from the globally configured one, pass the :service option. For example:

class Gallery < ActiveRecord::Base
  has_many_attached :photos, service: :s3
end

:service can also be specified as a proc, and it will be called with the model instance:

class Gallery < ActiveRecord::Base
  has_many_attached :photos, service: ->(gallery) { gallery.personal? ? :personal_s3 : :s3 }
end

To avoid N+1 queries, you can include the attached blobs in your query like so:

Gallery.where(user: Current.user).with_attached_photos

If you need to enable strict_loading to prevent lazy loading of attachments, pass the :strict_loading option. You can do:

class Gallery < ApplicationRecord
  has_many_attached :photos, strict_loading: true
end

Note: Active Storage relies on polymorphic associations, which in turn store class names in the database. When renaming classes that use has_many, make sure to also update the class names in the active_storage_attachments.record_type polymorphic type column of the corresponding rows.

๐Ÿ“ Source code
# File activestorage/lib/active_storage/attached/model.rb, line 210
      def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
        ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)

        generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
          # frozen_string_literal: true
          def #{name}
            @active_storage_attached ||= {}
            @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
          end

          def #{name}=(attachables)
            attachables = Array(attachables).compact_blank
            pending_uploads = attachment_changes["#{name}"].try(:pending_uploads)

            attachment_changes["#{name}"] = if attachables.none?
              ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
            else
              ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables, pending_uploads: pending_uploads)
            end
          end
        CODE

        has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
        has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading

        scope :"with_attached_#{name}", -> {
          if ActiveStorage.track_variants
            includes("#{name}_attachments": { blob: {
              variant_records: { image_attachment: :blob },
              preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
            } })
          else
            includes("#{name}_attachments": :blob)
          end
        }

        after_save { attachment_changes[name.to_s]&.save }

        after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }

        reflection = ActiveRecord::Reflection.create(
          :has_many_attached,
          name,
          nil,
          { dependent: dependent, service_name: service },
          self
        )
        yield reflection if block_given?
        ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
      end
๐Ÿ”Ž See on GitHub

has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)

Specifies the relation between a single attachment and the model.

class User < ApplicationRecord
  has_one_attached :avatar
end

There is no column defined on the model side, Active Storage takes care of the mapping between your records and the attachment.

Under the covers, this relationship is implemented as a has_one association to a ActiveStorage::Attachment record and a has_one-through association to a ActiveStorage::Blob record. These associations are available as avatar_attachment and avatar_blob. But you shouldnโ€™t need to work with these associations directly in most circumstances.

Instead, has_one_attached generates an ActiveStorage::Attached::One proxy to provide access to the associations and factory methods, like attach:

user.avatar.attach(uploaded_file)

The :dependent option defaults to :purge_later. This means the attachment will be purged (i.e. destroyed) in the background whenever the record is destroyed. If an ActiveJob::Backend queue adapter is not set in the application set it to purge instead.

If you need the attachment to use a service which differs from the globally configured one, pass the :service option. For example:

class User < ActiveRecord::Base
  has_one_attached :avatar, service: :s3
end

:service can also be specified as a proc, and it will be called with the model instance:

class User < ActiveRecord::Base
  has_one_attached :avatar, service: ->(user) { user.in_europe_region? ? :s3_europe : :s3_usa }
end

To avoid N+1 queries, you can include the attached blobs in your query like so:

User.with_attached_avatar

If you need to enable strict_loading to prevent lazy loading of attachment, pass the :strict_loading option. You can do:

class User < ApplicationRecord
  has_one_attached :avatar, strict_loading: true
end

Note: Active Storage relies on polymorphic associations, which in turn store class names in the database. When renaming classes that use has_one_attached, make sure to also update the class names in the active_storage_attachments.record_type polymorphic type column of the corresponding rows.

๐Ÿ“ Source code
# File activestorage/lib/active_storage/attached/model.rb, line 108
      def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
        ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)

        generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
          # frozen_string_literal: true
          def #{name}
            @active_storage_attached ||= {}
            @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
          end

          def #{name}=(attachable)
            attachment_changes["#{name}"] =
              if attachable.nil? || attachable == ""
                ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
              else
                ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
              end
          end
        CODE

        has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
        has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading

        scope :"with_attached_#{name}", -> {
          if ActiveStorage.track_variants
            includes("#{name}_attachment": { blob: {
              variant_records: { image_attachment: :blob },
              preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
            } })
          else
            includes("#{name}_attachment": :blob)
          end
        }

        after_save { attachment_changes[name.to_s]&.save }

        after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }

        reflection = ActiveRecord::Reflection.create(
          :has_one_attached,
          name,
          nil,
          { dependent: dependent, service_name: service },
          self
        )
        yield reflection if block_given?
        ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
      end
๐Ÿ”Ž See on GitHub

with_attached_*

Includes the attached blobs in your query to avoid N+1 queries.

If ActiveStorage.track_variants is enabled, it will also include the variants record and their attached blobs.

User.with_attached_avatar

Use the plural form for has_many_attached:

Gallery.with_attached_photos
๐Ÿ“ Source code
# File activestorage/lib/active_storage/attached/model.rb, line 54
    class_methods do
      # Specifies the relation between a single attachment and the model.
      #
      #   class User < ApplicationRecord
      #     has_one_attached :avatar
      #   end
      #
      # There is no column defined on the model side, Active Storage takes
      # care of the mapping between your records and the attachment.
      #
      # Under the covers, this relationship is implemented as a +has_one+ association to a
      # ActiveStorage::Attachment record and a +has_one-through+ association to a
      # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
      # and +avatar_blob+. But you shouldn't need to work with these associations directly in
      # most circumstances.
      #
      # Instead, +has_one_attached+ generates an ActiveStorage::Attached::One proxy to
      # provide access to the associations and factory methods, like +attach+:
      #
      #   user.avatar.attach(uploaded_file)
      #
      # The +:dependent+ option defaults to +:purge_later+. This means the attachment will be
      # purged (i.e. destroyed) in the background whenever the record is destroyed.
      # If an ActiveJob::Backend queue adapter is not set in the application set it to
      # +purge+ instead.
      #
      # If you need the attachment to use a service which differs from the globally configured one,
      # pass the +:service+ option. For example:
      #
      #   class User < ActiveRecord::Base
      #     has_one_attached :avatar, service: :s3
      #   end
      #
      # +:service+ can also be specified as a proc, and it will be called with the model instance:
      #
      #   class User < ActiveRecord::Base
      #     has_one_attached :avatar, service: ->(user) { user.in_europe_region? ? :s3_europe : :s3_usa }
      #   end
      #
      # To avoid N+1 queries, you can include the attached blobs in your query like so:
      #
      #   User.with_attached_avatar
      #
      # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
      # pass the +:strict_loading+ option. You can do:
      #
      #   class User < ApplicationRecord
      #     has_one_attached :avatar, strict_loading: true
      #   end
      #
      # Note: Active Storage relies on polymorphic associations, which in turn store class names in the database.
      # When renaming classes that use <tt>has_one_attached</tt>, make sure to also update the class names in the
      # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
      # the corresponding rows.
      def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
        ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)

        generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
          # frozen_string_literal: true
          def #{name}
            @active_storage_attached ||= {}
            @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
          end

          def #{name}=(attachable)
            attachment_changes["#{name}"] =
              if attachable.nil? || attachable == ""
                ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
              else
                ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
              end
          end
        CODE

        has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
        has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading

        scope :"with_attached_#{name}", -> {
          if ActiveStorage.track_variants
            includes("#{name}_attachment": { blob: {
              variant_records: { image_attachment: :blob },
              preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
            } })
          else
            includes("#{name}_attachment": :blob)
          end
        }

        after_save { attachment_changes[name.to_s]&.save }

        after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }

        reflection = ActiveRecord::Reflection.create(
          :has_one_attached,
          name,
          nil,
          { dependent: dependent, service_name: service },
          self
        )
        yield reflection if block_given?
        ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
      end

      # Specifies the relation between multiple attachments and the model.
      #
      #   class Gallery < ApplicationRecord
      #     has_many_attached :photos
      #   end
      #
      # There are no columns defined on the model side, Active Storage takes
      # care of the mapping between your records and the attachments.
      #
      # Under the covers, this relationship is implemented as a +has_many+ association to a
      # ActiveStorage::Attachment record and a +has_many-through+ association to a
      # ActiveStorage::Blob record. These associations are available as +photos_attachments+
      # and +photos_blobs+. But you shouldn't need to work with these associations directly in
      # most circumstances.
      #
      # Instead, +has_many_attached+ generates an ActiveStorage::Attached::Many proxy to
      # provide access to the associations and factory methods, like +attach+:
      #
      #   user.photos.attach(uploaded_file)
      #
      # The +:dependent+ option defaults to +:purge_later+. This means the attachments will be
      # purged (i.e. destroyed) in the background whenever the record is destroyed.
      # If an ActiveJob::Backend queue adapter is not set in the application set it to
      # +purge+ instead.
      #
      # If you need the attachment to use a service which differs from the globally configured one,
      # pass the +:service+ option. For example:
      #
      #   class Gallery < ActiveRecord::Base
      #     has_many_attached :photos, service: :s3
      #   end
      #
      # +:service+ can also be specified as a proc, and it will be called with the model instance:
      #
      #   class Gallery < ActiveRecord::Base
      #     has_many_attached :photos, service: ->(gallery) { gallery.personal? ? :personal_s3 : :s3 }
      #   end
      #
      # To avoid N+1 queries, you can include the attached blobs in your query like so:
      #
      #   Gallery.where(user: Current.user).with_attached_photos
      #
      # If you need to enable +strict_loading+ to prevent lazy loading of attachments,
      # pass the +:strict_loading+ option. You can do:
      #
      #   class Gallery < ApplicationRecord
      #     has_many_attached :photos, strict_loading: true
      #   end
      #
      # Note: Active Storage relies on polymorphic associations, which in turn store class names in the database.
      # When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
      # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
      # the corresponding rows.
      def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
        ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)

        generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
          # frozen_string_literal: true
          def #{name}
            @active_storage_attached ||= {}
            @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
          end

          def #{name}=(attachables)
            attachables = Array(attachables).compact_blank
            pending_uploads = attachment_changes["#{name}"].try(:pending_uploads)

            attachment_changes["#{name}"] = if attachables.none?
              ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
            else
              ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables, pending_uploads: pending_uploads)
            end
          end
        CODE

        has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
        has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading

        scope :"with_attached_#{name}", -> {
          if ActiveStorage.track_variants
            includes("#{name}_attachments": { blob: {
              variant_records: { image_attachment: :blob },
              preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
            } })
          else
            includes("#{name}_attachments": :blob)
          end
        }

        after_save { attachment_changes[name.to_s]&.save }

        after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }

        reflection = ActiveRecord::Reflection.create(
          :has_many_attached,
          name,
          nil,
          { dependent: dependent, service_name: service },
          self
        )
        yield reflection if block_given?
        ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
      end
    end
๐Ÿ”Ž See on GitHub