# references: # https://evilmartians.com/chronicles/active-storage-meets-graphql-pt-2-exposing-attachment-urls module Extensions class Attachment < GraphQL::Schema::FieldExtension attr_reader :attachment_assoc, :root def apply # Here we try to define the ActiveRecord association name: # - it could be set explicitly via extension options # - or we imply that is the same as the field name w/o "_url" # suffix (e.g., "avatar_url" => "avatar") @attachment_assoc = if options.key?(:attachment) "#{options[:attachment]}_attachment" elsif options.key?(:attachments) "#{options[:attachments]}_attachments" else attachment = field.original_name.to_s.sub(/_url$/, "") "#{attachment}_attachment" end @root = options&.dig(:root) || :object end # This method resolves (as it states) the field itself # (it's the same as defining a method within a type) def resolve(object:, **_rest) root_instance = object.public_send(root) Loaders::Association.for( root_instance.class, attachment_assoc => :blob ).load(root_instance) end # This method is called if the result of the `resolve` # is a lazy value (e.g., a Promise – like in our case) def after_resolve(value:, **_rest) if value.respond_to?(:map) attachments = value.map { after_resolve_attachment(_1) }.compact if options[:as] == :single attachments.first else attachments end else attachment = after_resolve_attachment(value) if options[:as] == :multiple [attachment].compact else attachment end end end private def after_resolve_attachment(attachment) return unless attachment if attachment.virus_scanner.safe? || attachment.virus_scanner.pending? attachment end end end end