lib/rails/graphql/helpers/with_schema_fields.rb in rails-graphql-1.0.0.beta vs lib/rails/graphql/helpers/with_schema_fields.rb in rails-graphql-1.0.0.rc1

- old
+ new

@@ -15,25 +15,28 @@ module ClassMethods def inherited(subclass) super if defined? super - TYPE_FIELD_CLASS.each_key do |kind| - fields = instance_variable_defined?("@#{kind}_fields") - fields = fields ? instance_variable_get("@#{kind}_fields") : {} - fields.each_value { |field| subclass.add_proxy_field(kind, field) } + TYPE_FIELD_CLASS.each_key do |type| + fields = instance_variable_defined?("@#{type}_fields") + fields = fields ? instance_variable_get("@#{type}_fields") : EMPTY_HASH + fields.each_value { |field| subclass.add_proxy_field(type, field) } end end end # Helper class to be used as the +self+ in configuration blocks ScopedConfig = Struct.new(:source, :type) do - def arg(*args, **xargs, &block) + def argument(*args, **xargs, &block) xargs[:owner] ||= source GraphQL::Argument.new(*args, **xargs, &block) end + alias arg argument + alias kind type + private def respond_to_missing?(method_name, include_private = false) schema_methods.key?(method_name) || source.respond_to?(method_name, include_private) || super @@ -53,11 +56,10 @@ fields: :fields_for, fields?: :fields_for?, safe_field: :safe_add_field, field: :add_field, proxy_field: :add_proxy_field, - field?: :has_field?, import: :import_into, import_all: :import_all_into, ) end end @@ -74,17 +76,20 @@ elsif initialize instance_variable_set(ivar, Concurrent::Map.new) end end + # Allow hash access with the type or the type and the name + def [](type, name = nil) + name.nil? ? fields_for(type) : find_field(type, name) + end + # Check if there are fields set fot he given type def fields_for?(type) public_send("#{type}_fields?") end - alias [] :fields_for - # Return the object name for a given +type+ of list of fields def type_name_for(type) method_name = :"#{type}_type_name" public_send(method_name) if respond_to?(method_name) end @@ -114,10 +119,11 @@ end # Add a new field to the list but use a proxy instead of a hard copy of # a given +field+ def add_proxy_field(type, field, *args, **xargs, &block) + field = field.field if field.is_a?(Module) && field <= Alternative::Query raise ArgumentError, (+<<~MSG).squish if field.schema_type != type A #{field.schema_type} field cannot be added as a #{type} field. MSG klass = Field.const_get(TYPE_FIELD_CLASS[type]) @@ -178,27 +184,28 @@ find_field(type, object) || raise(NotFoundError, (+<<~MSG).squish) The #{object.inspect} field on #{type} is not defined yet. MSG end - # Get the list of GraphQL names of all the fields difined + # Get the list of GraphQL names of all the fields defined def field_names_for(type, enabled_only = true) - return unless fields_for?(type) - list = fields_for(type) - list = list.select(&:enabled?) if enabled_only - list.map(&:gql_name).compact + source = (enabled_only ? enabled_fields_from(type) : lazy_each_field_from(type)) + source&.map(&:gql_name)&.eager end + # Return a lazy enumerator for enabled fields + def enabled_fields_from(type) + lazy_each_field_from(type)&.select(&:enabled?) + end + # Run a configuration block for the given +type+ def configure_fields(type, &block) WithSchemaFields::ScopedConfig.new(self, type).instance_exec(&block) end # Import a class of fields into the given section of schema fields def import_into(type, source) - return if source.try(:abstract?) - # Import an alternative declaration of a field if source.is_a?(Module) && source <= Alternative::Query return add_proxy_field(type, source.field) end @@ -230,11 +237,11 @@ def import_all_into(type, mod, recursive: false, **xargs) mod.constants.each do |const_name| object = mod.const_get(const_name) import_into(type, object, **xargs) if object.is_a?(Class) - import_all_into(type, object, recursive: recursive, **xargs) if recursive + import_all_into(type, object, recursive: recursive, **xargs) if recursive && object.is_a?(Module) end end # Same as above, but if the name of the module being imported already # dictates the type, skip specifying it @@ -251,66 +258,83 @@ # Validate all the fields to make sure the definition is valid def validate!(*) super if defined? super - TYPE_FIELD_CLASS.each_key do |kind| - next unless public_send("#{kind}_fields?") - fields_for(kind).each_value(&:validate!) + TYPE_FIELD_CLASS.each_key do |type| + next unless public_send("#{type}_fields?") + fields_for(type).each_value(&:validate!) end end # Find a specific field using its id as +gql_name.type+ def find_by_gid(gid) find_field!(gid.scope, gid.name) end - TYPE_FIELD_CLASS.each_key do |kind| + TYPE_FIELD_CLASS.each_key do |type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{kind}_field?(name) - has_field?(:#{kind}, name) + def #{type}_fields? + defined?(@#{type}_fields) && @#{type}_fields.present? end - def #{kind}_field(name) - find_field(:#{kind}, name) + def #{type}_fields(&block) + configure_fields(:#{type}, &block) if block.present? + @#{type}_fields if defined?(@#{type}_fields) end - def add_#{kind}_field(*args, **xargs, &block) - add_field(:#{kind}, *args, **xargs, &block) + def add_#{type}_field(*args, **xargs, &block) + add_field(:#{type}, *args, **xargs, &block) end - def #{kind}_fields? - defined?(@#{kind}_fields) && @#{kind}_fields.present? + def #{type}_field?(name) + has_field?(:#{type}, name) end - def #{kind}_fields(&block) - configure_fields(:#{kind}, &block) if block.present? - @#{kind}_fields if defined?(@#{kind}_fields) + def #{type}_field(name) + find_field(:#{type}, name) end - def #{kind}_type_name + def #{type}_type_name source = (respond_to?(:config) ? config : GraphQL.config) - source.schema_type_names[:#{kind}] + source.schema_type_names[:#{type}] end - def #{kind}_type - if defined?(@#{kind}_fields) && @#{kind}_fields.present? - OpenStruct.new( - name: "\#{name}[:#{kind}]", - kind: :object, - object?: true, - kind_enum: 'OBJECT', - fields: @#{kind}_fields, - gql_name: #{kind}_type_name, - interfaces: nil, - description: nil, - interfaces?: false, - internal?: false, - ).freeze - end + def #{type}_type + return unless #{type}_fields? + + OpenStruct.new( + name: "\#{name}[:#{type}]", + kind: :object, + object?: true, + kind_enum: 'OBJECT', + fields: @#{type}_fields, + gql_name: #{type}_type_name, + description: nil, + output_type?: true, + operational?: true, + interfaces?: false, + internal?: false, + ).freeze end RUBY end + + protected + + # A little helper to define arguments using the :arguments key + def argument(*args, **xargs, &block) + xargs[:owner] = self + GraphQL::Argument.new(*args, **xargs, &block) + end + + alias arg argument + + private + + def lazy_each_field_from(type) + fields_for(type).each_pair.lazy.each_entry.map(&:last) if fields_for?(type) + end end end end end