# frozen_string_literal: true
module Rails
module GraphQL
# = GraphQL Proxied Field
#
# Proxied fields are a soft way to copy a real field. The good part is that
# if the field changes for any reason all its copies will change as well.
#
# The owner of a proxy field is different from the owner of the actual field
# but that doesn't affect the field operations.
#
# Proxied field also supports aliases, which helps implementing independent
# fields and then providing them as proxy to other objects.
#
# Proxies can be created from any kind of input
#
# ==== Options
#
# It accepts all the options of any other type of field plus the following
#
# * :owner - The main object that this field belongs to.
# * :as - The actual name to be used on the field while assigning
# the proxy (defaults to nil).
# * :alias - Same as the +:as+ key (defaults to nil).
module Field::ProxiedField
delegate_missing_to :field
delegate :leaf_type?, :array?, :internal?, :proxied_owner, to: :field
Field.proxyable_methods %w[name gql_name method_name description
null? nullable? enabled?], klass: self
def initialize(field, owner:, **xargs, &block)
@field = field
@owner = owner
apply_changes(**xargs, &block)
end
# Once this module is added then the field becomes a proxy
def proxy?
true
end
# Make sure to check the original field as well
# TODO: Maybe not use the +==+ method, but so far so good
def ==(other)
super || field == other
end
# Allow changing most of the general kind-independent initialize settings
def apply_changes(**xargs, &block)
if (deprecated = xargs[:deprecated])
xargs[:directives] = ::Array.wrap(xargs[:directives])
xargs[:directives] << Directive::DeprecatedDirective.new(
reason: (deprecated.is_a?(String) ? deprecated : nil),
)
end
# TODO: Replace by a proper method to build and set @directives
@directives = GraphQL.directives_to_set(xargs[:directives], source: self) \
if xargs.key?(:directives)
self.description = xargs[:desc] if xargs.key?(:desc)
self.description = xargs[:description] if xargs.key?(:description)
@enabled = xargs.fetch(:enabled, !xargs.fetch(:disabled, false)) \
if xargs.key?(:enabled) || xargs.key?(:disabled)
normalize_name(xargs.fetch(:alias, xargs[:as]))
super
end
# Override this to include proxied owners
def all_owners
super + @field.all_owners
end
# Return the proxied field
def proxied_field
@field
end
# Just ensure that when the field is proxied to an interface it does not
# allow disabling
def disable!
super unless non_interface_proxy!('disable')
end
# Just ensure that when the field is proxied to an interface it does not
# allow enabling
def enable!
super unless non_interface_proxy!('enable')
end
# Prepend the proxy directives and then the source directives
def all_directives
inherited = field.all_directives
return inherited unless defined?(@directives)
inherited.present? ? inherited + super : super
end
# Check if the field has directives locally or in the proxied field
def directives?
super || field.directives?
end
# It is important to ensure that the proxied field is also valid
def validate!(*)
super if defined? super
field.validate!
end
protected
alias field proxied_field
def normalize_name(value)
super unless value.blank? || non_interface_proxy!('rename')
end
# Prohibits changes to proxy fields based on interfaces
def non_interface_proxy!(action)
raise ::ArgymentError, <<~MSG.squish if interface_proxy?
Unable to #{action} the "#{gql_name}" field because it is
associated to the #{field.owner.name} interface.
MSG
end
# Checks if the proxy is based on an interface field
def interface_proxy?
field.owner.try(:interface?)
end
# Display the source of the proxy for inspection
def inspect_source
+"@source=#{field.owner.name}[:#{field.name}] [proxied]"
end
# This is trigerred when the field is proxied
def proxied
super if defined? super
end
end
end
end