67 lines
1.9 KiB
Ruby
67 lines
1.9 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
# 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
|