diff --git a/lib/graphql/interface_type.rb b/lib/graphql/interface_type.rb index bdf5449ff2..bf82f678eb 100644 --- a/lib/graphql/interface_type.rb +++ b/lib/graphql/interface_type.rb @@ -23,4 +23,14 @@ def kind def possible_types @possible_types ||= [] end + + # @return [GraphQL::Field] The defined field for `field_name` + def get_field(field_name) + fields[field_name] + end + + # @return [Array] All fields on this type + def all_fields + fields.values + end end diff --git a/lib/graphql/introspection/fields_field.rb b/lib/graphql/introspection/fields_field.rb index e9fadc319d..779b0adaa8 100644 --- a/lib/graphql/introspection/fields_field.rb +++ b/lib/graphql/introspection/fields_field.rb @@ -4,10 +4,10 @@ argument :includeDeprecated, GraphQL::BOOLEAN_TYPE, default_value: false resolve -> (object, arguments, context) { return nil if !object.kind.fields? - fields = object.fields.values + fields = object.all_fields if !arguments["includeDeprecated"] fields = fields.select {|f| !f.deprecation_reason } end - fields + fields.sort_by { |f| f.name } } end diff --git a/lib/graphql/object_type.rb b/lib/graphql/object_type.rb index c46906277a..2df09d2a47 100644 --- a/lib/graphql/object_type.rb +++ b/lib/graphql/object_type.rb @@ -42,4 +42,23 @@ def interfaces=(new_interfaces) def kind GraphQL::TypeKinds::OBJECT end + + # @return [GraphQL::Field] The field definition for `field_name` (may be inherited from interfaces) + def get_field(field_name) + fields[field_name] || interface_fields[field_name] + end + + # @return [Array] All fields, including ones inherited from interfaces + def all_fields + interface_fields.merge(self.fields).values + end + + private + + # Create a {name => defn} hash for fields inherited from interfaces + def interface_fields + interfaces.reduce({}) do |memo, iface| + memo.merge!(iface.fields) + end + end end diff --git a/lib/graphql/schema.rb b/lib/graphql/schema.rb index 380adaff00..642578702b 100644 --- a/lib/graphql/schema.rb +++ b/lib/graphql/schema.rb @@ -49,7 +49,7 @@ def execute(*args) # Resolve field named `field_name` for type `parent_type`. # Handles dynamic fields `__typename`, `__type` and `__schema`, too def get_field(parent_type, field_name) - defined_field = parent_type.fields[field_name] + defined_field = parent_type.get_field(field_name) if defined_field defined_field elsif field_name == "__typename" diff --git a/lib/graphql/schema/printer.rb b/lib/graphql/schema/printer.rb index 689bd870b9..a7898d0943 100644 --- a/lib/graphql/schema/printer.rb +++ b/lib/graphql/schema/printer.rb @@ -48,7 +48,7 @@ def print_type(type) module TypeKindPrinters module FieldPrinter def print_fields(type) - type.fields.values.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n") + type.all_fields.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n") end def print_args(field) diff --git a/lib/graphql/schema/type_reducer.rb b/lib/graphql/schema/type_reducer.rb index 687d8a07c3..dd2b89a081 100644 --- a/lib/graphql/schema/type_reducer.rb +++ b/lib/graphql/schema/type_reducer.rb @@ -30,7 +30,7 @@ def self.find_all(types) def find_types(type, type_hash) type_hash[type.name] = type if type.kind.fields? - type.fields.each do |name, field| + type.all_fields.each do |field| reduce_type(field.type, type_hash) field.arguments.each do |name, argument| reduce_type(argument.type, type_hash) diff --git a/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb b/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb index ade9db7ec2..e7a7b890cd 100644 --- a/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +++ b/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb @@ -19,7 +19,7 @@ def validate_field(errors, ast_field, parent_type, parent) return GraphQL::Language::Visitor::SKIP end - field = parent_type.fields[ast_field.name] + field = parent_type.get_field(ast_field.name) if field.nil? errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent) return GraphQL::Language::Visitor::SKIP diff --git a/readme.md b/readme.md index 6828334727..04e8c9a4d3 100644 --- a/readme.md +++ b/readme.md @@ -140,6 +140,7 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered- - Add a custom dump for Relay (it expects default value strings to be double-quoted) - Make variable validation provide a specific, useful message - Add docs for shared behaviors & DRY code +- Optimize the pure-Ruby parser (hand-write, RACC?!) - Big ideas: - Revamp the fixture Schema to be more useful (better names, more extensible) - __Subscriptions__ diff --git a/spec/graphql/introspection/schema_type_spec.rb b/spec/graphql/introspection/schema_type_spec.rb index 7c9a6c4d03..99b7cf238d 100644 --- a/spec/graphql/introspection/schema_type_spec.rb +++ b/spec/graphql/introspection/schema_type_spec.rb @@ -19,15 +19,15 @@ "queryType"=>{ "fields"=>[ {"name"=>"cheese"}, - {"name"=>"milk"}, - {"name"=>"dairy"}, - {"name"=>"fromSource"}, - {"name"=>"favoriteEdible"}, {"name"=>"cow"}, - {"name"=>"searchDairy"}, + {"name"=>"dairy"}, {"name"=>"error"}, {"name"=>"executionError"}, - {"name"=>"maybeNull"} + {"name"=>"favoriteEdible"}, + {"name"=>"fromSource"}, + {"name"=>"maybeNull"}, + {"name"=>"milk"}, + {"name"=>"searchDairy"}, ] }, "mutationType"=> { diff --git a/spec/graphql/introspection/type_type_spec.rb b/spec/graphql/introspection/type_type_spec.rb index 71b857d6d5..459e417ec5 100644 --- a/spec/graphql/introspection/type_type_spec.rb +++ b/spec/graphql/introspection/type_type_spec.rb @@ -12,11 +12,11 @@ |} let(:result) { DummySchema.execute(query_string, context: {}, variables: {"cheeseId" => 2}) } let(:cheese_fields) {[ - {"name"=>"id", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "Int"}}}, {"name"=>"flavor", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "String"}}}, + {"name"=>"id", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "Int"}}}, {"name"=>"origin", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "String"}}}, - {"name"=>"source", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "DairyAnimal"}}}, {"name"=>"similarCheese", "isDeprecated"=>false, "type"=>{"name"=>"Cheese", "ofType"=>nil}}, + {"name"=>"source", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "DairyAnimal"}}}, ]} let(:dairy_animals) {[ @@ -37,11 +37,11 @@ {"name"=>"AnimalProduct"} ], "fields"=>[ - {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"ID"}}}, - {"type"=>{"name"=>"DairyAnimal", "ofType"=>nil}}, - {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"String"}}}, {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"Float"}}}, {"type"=>{"name"=>"List", "ofType"=>{"name"=>"String"}}}, + {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"ID"}}}, + {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"String"}}}, + {"type"=>{"name"=>"DairyAnimal", "ofType"=>nil}}, ] }, "dairyAnimal"=>{ @@ -75,7 +75,7 @@ |} let(:deprecated_fields) { {"name"=>"fatContent", "isDeprecated"=>true, "type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"Float"}}} } it 'can expose deprecated fields' do - new_cheese_fields = cheese_fields + [deprecated_fields] + new_cheese_fields = [deprecated_fields] + cheese_fields expected = { "data" => { "cheeseType" => { "name"=> "Cheese", diff --git a/spec/graphql/object_type_spec.rb b/spec/graphql/object_type_spec.rb index b1d388c18b..1f7dd0c154 100644 --- a/spec/graphql/object_type_spec.rb +++ b/spec/graphql/object_type_spec.rb @@ -18,18 +18,25 @@ assert_equal([EdibleInterface, AnimalProductInterface], type.interfaces) end - describe '.fields ' do + describe '#get_field ' do it 'exposes fields' do - field = type.fields["id"] + field = type.get_field("id") assert_equal(GraphQL::TypeKinds::NON_NULL, field.type.kind) assert_equal(GraphQL::TypeKinds::SCALAR, field.type.of_type.kind) end it 'exposes defined field property' do - field_without_prop = CheeseType.fields['flavor'] - field_with_prop = CheeseType.fields['fatContent'] + field_without_prop = CheeseType.get_field('flavor') + field_with_prop = CheeseType.get_field('fatContent') assert_equal(field_without_prop.property, nil) assert_equal(field_with_prop.property, :fat_content) end + + it "looks up from interfaces" do + field_from_self = CheeseType.get_field('fatContent') + field_from_iface = MilkType.get_field('fatContent') + assert_equal(field_from_self.property, :fat_content) + assert_equal(field_from_iface.property, nil) + end end end diff --git a/spec/graphql/schema/type_reducer_spec.rb b/spec/graphql/schema/type_reducer_spec.rb index ce71ab57c7..7c02bd7c38 100644 --- a/spec/graphql/schema/type_reducer_spec.rb +++ b/spec/graphql/schema/type_reducer_spec.rb @@ -5,10 +5,10 @@ reducer = GraphQL::Schema::TypeReducer.new(CheeseType, {}) expected = { "Cheese" => CheeseType, - "Int" => GraphQL::INT_TYPE, + "Float" => GraphQL::FLOAT_TYPE, "String" => GraphQL::STRING_TYPE, "DairyAnimal" => DairyAnimalEnum, - "Float" => GraphQL::FLOAT_TYPE, + "Int" => GraphQL::INT_TYPE, "Edible" => EdibleInterface, "Milk" => MilkType, "ID" => GraphQL::ID_TYPE, diff --git a/spec/support/dairy_app.rb b/spec/support/dairy_app.rb index f4e964e586..8d2a265d29 100644 --- a/spec/support/dairy_app.rb +++ b/spec/support/dairy_app.rb @@ -5,7 +5,7 @@ class NoSuchDairyError < StandardError; end EdibleInterface = GraphQL::InterfaceType.define do name "Edible" description "Something you can eat, yum" - field :fatContent, !types.Float, "Percentage which is fat", property: :bogus_property + field :fatContent, !types.Float, "Percentage which is fat" field :origin, !types.String, "Place the edible comes from" end @@ -65,7 +65,6 @@ class NoSuchDairyError < StandardError; end field :id, !types.ID field :source, DairyAnimalEnum, "Animal which produced this milk" field :origin, !types.String, "Place the milk comes from" - field :fatContent, !types.Float, "Percentage which is milkfat" field :flavors, types[types.String], "Chocolate, Strawberry, etc" do argument :limit, types.Int resolve -> (milk, args, ctx) {