Module: Rails::GraphQL::Helpers::WithSchemaFields

Included in:
Schema, Source::Base
Defined in:
lib/rails/graphql/helpers/with_schema_fields.rb

Overview

Helper module that allows other objects to hold schema fields (query, mutation, and subscription fields). Works very similar to fields, but they are placed in different places regarding their type.

Defined Under Namespace

Modules: ClassMethods Classes: ScopedConfig

Constant Summary collapse

TYPE_FIELD_CLASS =
{
  query:        'OutputField',
  mutation:     'MutationField',
  subscription: 'SubscriptionField',
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(other) ⇒ Object



68
69
70
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 68

def self.extended(other)
  other.extend(WithSchemaFields::ClassMethods)
end

Instance Method Details

#[](type, name = nil) ⇒ Object

Allow hash access with the type or the type and the name



82
83
84
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 82

def [](type, name = nil)
  name.nil? ? fields_for(type) : find_field(type, name)
end

#add_field(type, *args, **xargs, &block) ⇒ Object

Add a new field of the give type See OutputField class.



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 107

def add_field(type, *args, **xargs, &block)
  klass = Field.const_get(TYPE_FIELD_CLASS[type], false)
  object = klass.new(*args, **xargs, owner: self, &block)

  raise DuplicatedError, (+"    The \"\#{object.name}\" field is already defined on \#{type} fields and\n    cannot be redefined.\n  MSG\n\n  fields_for(type, true)[object.name] = object\nrescue DefinitionError => e\n  raise e.class, +\"\#{e.message}\\n  Defined at: \#{caller(2)[0]}\"\nend\n").squish if has_field?(type, object.name)

#add_proxy_field(type, field, *args, **xargs, &block) ⇒ Object

Add a new field to the list but use a proxy instead of a hard copy of a given field

Raises:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 123

def add_proxy_field(type, field, *args, **xargs, &block)
  field = field.field if field.is_a?(Module) && field <= Alternative::Query
  raise ArgumentError, (+"    A \#{field.schema_type} field cannot be added as a \#{type} field.\n  MSG\n\n  klass = Field.const_get(TYPE_FIELD_CLASS[type], false)\n  raise ArgumentError, (+<<~MSG).squish unless field.is_a?(klass)\n    The \#{field.class.name} is not a valid field for \#{type} fields.\n  MSG\n\n  xargs[:owner] = self\n  object = field.to_proxy(*args, **xargs, &block)\n  raise DuplicatedError, (+<<~MSG).squish if has_field?(type, object.name)\n    The \#{field.name.inspect} field is already defined on \#{type} fields\n    and cannot be replaced.\n  MSG\n\n  fields_for(type, true)[object.name] = object\nend\n").squish if field.schema_type != type

#change_field(type, object, **xargs, &block) ⇒ Object Also known as: overwrite_field

Find a field and then change some flexible attributes of it



145
146
147
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 145

def change_field(type, object, **xargs, &block)
  find_field!(type, object).apply_changes(**xargs, &block)
end

#configure_field(type, object, &block) ⇒ Object

Run a configuration block for the given field of a given type



152
153
154
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 152

def configure_field(type, object, &block)
  find_field!(type, object).configure(&block)
end

#configure_fields(type, &block) ⇒ Object

Run a configuration block for the given type



201
202
203
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 201

def configure_fields(type, &block)
  WithSchemaFields::ScopedConfig.new(self, type).instance_exec(&block)
end

#disable_fields(type, *list) ⇒ Object

Disable a list of given fields from a given type



157
158
159
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 157

def disable_fields(type, *list)
  list.flatten.map { |item| find_field(type, item)&.disable! }
end

#enable_fields(type, *list) ⇒ Object

Enable a list of given fields from a given type



162
163
164
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 162

def enable_fields(type, *list)
  list.flatten.map { |item| find_field(type, item)&.enable! }
end

#enabled_fields_from(type) ⇒ Object

Return a lazy enumerator for enabled fields



196
197
198
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 196

def enabled_fields_from(type)
  lazy_each_field_from(type)&.select(&:enabled?)
end

#field_names_for(type, enabled_only = true) ⇒ Object

Get the list of GraphQL names of all the fields defined



190
191
192
193
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 190

def field_names_for(type, enabled_only = true)
  source = (enabled_only ? enabled_fields_from(type) : lazy_each_field_from(type))
  source&.map(&:gql_name)&.eager
end

#fields_for(type, initialize = nil) ⇒ Object

A little helper for getting the list of fields of a given type



73
74
75
76
77
78
79
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 73

def fields_for(type, initialize = nil)
  if instance_variable_defined?(ivar = :"@#{type}_fields")
    instance_variable_get(ivar)
  elsif initialize
    instance_variable_set(ivar, Concurrent::Map.new)
  end
end

#fields_for?(type) ⇒ Boolean

Check if there are fields set fot he given type

Returns:

  • (Boolean)


87
88
89
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 87

def fields_for?(type)
  public_send("#{type}_fields?")
end

#find_by_gid(gid) ⇒ Object

Find a specific field using its id as gql_name.type



270
271
272
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 270

def find_by_gid(gid)
  find_field!(gid.scope, gid.name)
end

#find_field(type, object) ⇒ Object

Find a specific field on the given type list. The object can be the gql_name, name, or an actual field.



176
177
178
179
180
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 176

def find_field(type, object)
  return unless fields_for?(type)
  object = object.name if object.is_a?(GraphQL::Field)
  fields_for(type)[object.is_a?(String) ? object.underscore.to_sym : object]
end

#find_field!(type, object) ⇒ Object

If the field is not found it will raise an exception



183
184
185
186
187
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 183

def find_field!(type, object)
  find_field(type, object) || raise(NotFoundError, (+"    The \#{object.inspect} field on \#{type} is not defined yet.\n  MSG\nend\n").squish)

#has_field?(type, object) ⇒ Boolean

Check if a field of the given type exists. The object can be the gql_name, name, or an actual field.

Returns:

  • (Boolean)


168
169
170
171
172
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 168

def has_field?(type, object)
  return false unless fields_for?(type)
  object = object.name if object.is_a?(GraphQL::Field)
  fields_for(type).key?(object.is_a?(String) ? object.underscore.to_sym : object)
end

#import_all(mod, **xargs) ⇒ Object

Same as above, but if the name of the module being imported already dictates the type, skip specifying it

Raises:

  • (::ArgumentError)


248
249
250
251
252
253
254
255
256
257
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 248

def import_all(mod, **xargs)
  type = mod.name.demodulize.underscore.singularize
  type = TYPE_FIELD_CLASS.each_key.find { |key| key.to_s == type }
  return import_all_into(type, mod, **xargs) unless type.nil?

  raise(::ArgumentError, (+"    Unable to extract type from \#{mod.name}.\n    Please use \"import_all_into(_type_, \#{mod.name}) instead.\"\n  MSG\nend\n").squish)

#import_all_into(type, mod, recursive: false, **xargs) ⇒ Object

Import a module containing several classes to be imported TODO: Maybe add deepness into the recursive value



237
238
239
240
241
242
243
244
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 237

def import_all_into(type, mod, recursive: false, **xargs)
  mod.constants.each do |const_name|
    object = mod.const_get(const_name, false)

    import_into(type, object, **xargs) if object.is_a?(Class)
    import_all_into(type, object, recursive: recursive, **xargs) if recursive && object.is_a?(Module)
  end
end

#import_into(type, source) ⇒ Object

Import a class of fields into the given section of schema fields



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 206

def import_into(type, source)
  # Import an alternative declaration of a field
  if source.is_a?(Module) && source <= Alternative::Query
    return add_proxy_field(type, source.field)
  end

  case source
  when Array
    # Import a list of fields
    source.each { |field| add_proxy_field(type, field) }
  when Hash, Concurrent::Map
    # Import a keyed list of fields
    source.each_value { |field| add_proxy_field(type, field) }
  when Helpers::WithFields
    # Import a set of fields
    source.fields.each_value { |field| add_proxy_field(type, field) }
  when Helpers::WithSchemaFields
    # Import other schema fields
    (type == :all ? TYPE_FIELD_CLASS.each_key : type.then).each do |import_type|
      source.fields_for(import_type)&.each_value do |field|
        add_proxy_field(import_type, field)
      end
    end
  else
    return if GraphQL.config.silence_import_warnings
    GraphQL.logger.warn(+"Unable to import #{source.inspect} into #{self.name}.")
  end
end

#safe_add_field(*args, of_type: nil, **xargs, &block) ⇒ Object

Only add the field if it is not already defined



98
99
100
101
102
103
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 98

def safe_add_field(*args, of_type: nil, **xargs, &block)
  method_name = of_type.nil? ? :add_field : "add_#{of_type}_field"
  public_send(method_name, *args, **xargs, &block)
rescue DuplicatedError
  # Do not do anything if it is duplicated
end

#type_name_for(type) ⇒ Object

Return the object name for a given type of list of fields



92
93
94
95
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 92

def type_name_for(type)
  method_name = :"#{type}_type_name"
  public_send(method_name) if respond_to?(method_name)
end

#validate!Object

Validate all the fields to make sure the definition is valid



260
261
262
263
264
265
266
267
# File 'lib/rails/graphql/helpers/with_schema_fields.rb', line 260

def validate!(*)
  super if defined? super

  TYPE_FIELD_CLASS.each_key do |type|
    next unless public_send("#{type}_fields?")
    fields_for(type).each_value(&:validate!)
  end
end