Skip to content

Commit c270431

Browse files
author
Robert Mosolgo
committed
Merge pull request #108 from rmosolgo/interface-field-lookup
feat(ObjectType) lookup field implementations from interface, too
2 parents f6fc9a7 + b355de2 commit c270431

File tree

13 files changed

+62
-26
lines changed

13 files changed

+62
-26
lines changed

lib/graphql/interface_type.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,14 @@ def kind
2323
def possible_types
2424
@possible_types ||= []
2525
end
26+
27+
# @return [GraphQL::Field] The defined field for `field_name`
28+
def get_field(field_name)
29+
fields[field_name]
30+
end
31+
32+
# @return [Array<GraphQL::Field>] All fields on this type
33+
def all_fields
34+
fields.values
35+
end
2636
end

lib/graphql/introspection/fields_field.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
argument :includeDeprecated, GraphQL::BOOLEAN_TYPE, default_value: false
55
resolve -> (object, arguments, context) {
66
return nil if !object.kind.fields?
7-
fields = object.fields.values
7+
fields = object.all_fields
88
if !arguments["includeDeprecated"]
99
fields = fields.select {|f| !f.deprecation_reason }
1010
end
11-
fields
11+
fields.sort_by { |f| f.name }
1212
}
1313
end

lib/graphql/object_type.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,23 @@ def interfaces=(new_interfaces)
4242
def kind
4343
GraphQL::TypeKinds::OBJECT
4444
end
45+
46+
# @return [GraphQL::Field] The field definition for `field_name` (may be inherited from interfaces)
47+
def get_field(field_name)
48+
fields[field_name] || interface_fields[field_name]
49+
end
50+
51+
# @return [Array<GraphQL::Field>] All fields, including ones inherited from interfaces
52+
def all_fields
53+
interface_fields.merge(self.fields).values
54+
end
55+
56+
private
57+
58+
# Create a {name => defn} hash for fields inherited from interfaces
59+
def interface_fields
60+
interfaces.reduce({}) do |memo, iface|
61+
memo.merge!(iface.fields)
62+
end
63+
end
4564
end

lib/graphql/schema.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def execute(*args)
4949
# Resolve field named `field_name` for type `parent_type`.
5050
# Handles dynamic fields `__typename`, `__type` and `__schema`, too
5151
def get_field(parent_type, field_name)
52-
defined_field = parent_type.fields[field_name]
52+
defined_field = parent_type.get_field(field_name)
5353
if defined_field
5454
defined_field
5555
elsif field_name == "__typename"

lib/graphql/schema/printer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def print_type(type)
4848
module TypeKindPrinters
4949
module FieldPrinter
5050
def print_fields(type)
51-
type.fields.values.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n")
51+
type.all_fields.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n")
5252
end
5353

5454
def print_args(field)

lib/graphql/schema/type_reducer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def self.find_all(types)
3030
def find_types(type, type_hash)
3131
type_hash[type.name] = type
3232
if type.kind.fields?
33-
type.fields.each do |name, field|
33+
type.all_fields.each do |field|
3434
reduce_type(field.type, type_hash)
3535
field.arguments.each do |name, argument|
3636
reduce_type(argument.type, type_hash)

lib/graphql/static_validation/rules/fields_are_defined_on_type.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def validate_field(errors, ast_field, parent_type, parent)
1919
return GraphQL::Language::Visitor::SKIP
2020
end
2121

22-
field = parent_type.fields[ast_field.name]
22+
field = parent_type.get_field(ast_field.name)
2323
if field.nil?
2424
errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent)
2525
return GraphQL::Language::Visitor::SKIP

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
140140
- Add a custom dump for Relay (it expects default value strings to be double-quoted)
141141
- Make variable validation provide a specific, useful message
142142
- Add docs for shared behaviors & DRY code
143+
- Optimize the pure-Ruby parser (hand-write, RACC?!)
143144
- Big ideas:
144145
- Revamp the fixture Schema to be more useful (better names, more extensible)
145146
- __Subscriptions__

spec/graphql/introspection/schema_type_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
"queryType"=>{
2020
"fields"=>[
2121
{"name"=>"cheese"},
22-
{"name"=>"milk"},
23-
{"name"=>"dairy"},
24-
{"name"=>"fromSource"},
25-
{"name"=>"favoriteEdible"},
2622
{"name"=>"cow"},
27-
{"name"=>"searchDairy"},
23+
{"name"=>"dairy"},
2824
{"name"=>"error"},
2925
{"name"=>"executionError"},
30-
{"name"=>"maybeNull"}
26+
{"name"=>"favoriteEdible"},
27+
{"name"=>"fromSource"},
28+
{"name"=>"maybeNull"},
29+
{"name"=>"milk"},
30+
{"name"=>"searchDairy"},
3131
]
3232
},
3333
"mutationType"=> {

spec/graphql/introspection/type_type_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
|}
1313
let(:result) { DummySchema.execute(query_string, context: {}, variables: {"cheeseId" => 2}) }
1414
let(:cheese_fields) {[
15-
{"name"=>"id", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "Int"}}},
1615
{"name"=>"flavor", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "String"}}},
16+
{"name"=>"id", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "Int"}}},
1717
{"name"=>"origin", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "String"}}},
18-
{"name"=>"source", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "DairyAnimal"}}},
1918
{"name"=>"similarCheese", "isDeprecated"=>false, "type"=>{"name"=>"Cheese", "ofType"=>nil}},
19+
{"name"=>"source", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "DairyAnimal"}}},
2020
]}
2121

2222
let(:dairy_animals) {[
@@ -37,11 +37,11 @@
3737
{"name"=>"AnimalProduct"}
3838
],
3939
"fields"=>[
40-
{"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"ID"}}},
41-
{"type"=>{"name"=>"DairyAnimal", "ofType"=>nil}},
42-
{"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"String"}}},
4340
{"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"Float"}}},
4441
{"type"=>{"name"=>"List", "ofType"=>{"name"=>"String"}}},
42+
{"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"ID"}}},
43+
{"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"String"}}},
44+
{"type"=>{"name"=>"DairyAnimal", "ofType"=>nil}},
4545
]
4646
},
4747
"dairyAnimal"=>{
@@ -75,7 +75,7 @@
7575
|}
7676
let(:deprecated_fields) { {"name"=>"fatContent", "isDeprecated"=>true, "type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"Float"}}} }
7777
it 'can expose deprecated fields' do
78-
new_cheese_fields = cheese_fields + [deprecated_fields]
78+
new_cheese_fields = [deprecated_fields] + cheese_fields
7979
expected = { "data" => {
8080
"cheeseType" => {
8181
"name"=> "Cheese",

spec/graphql/object_type_spec.rb

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,25 @@
1818
assert_equal([EdibleInterface, AnimalProductInterface], type.interfaces)
1919
end
2020

21-
describe '.fields ' do
21+
describe '#get_field ' do
2222
it 'exposes fields' do
23-
field = type.fields["id"]
23+
field = type.get_field("id")
2424
assert_equal(GraphQL::TypeKinds::NON_NULL, field.type.kind)
2525
assert_equal(GraphQL::TypeKinds::SCALAR, field.type.of_type.kind)
2626
end
2727

2828
it 'exposes defined field property' do
29-
field_without_prop = CheeseType.fields['flavor']
30-
field_with_prop = CheeseType.fields['fatContent']
29+
field_without_prop = CheeseType.get_field('flavor')
30+
field_with_prop = CheeseType.get_field('fatContent')
3131
assert_equal(field_without_prop.property, nil)
3232
assert_equal(field_with_prop.property, :fat_content)
3333
end
34+
35+
it "looks up from interfaces" do
36+
field_from_self = CheeseType.get_field('fatContent')
37+
field_from_iface = MilkType.get_field('fatContent')
38+
assert_equal(field_from_self.property, :fat_content)
39+
assert_equal(field_from_iface.property, nil)
40+
end
3441
end
3542
end

spec/graphql/schema/type_reducer_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
reducer = GraphQL::Schema::TypeReducer.new(CheeseType, {})
66
expected = {
77
"Cheese" => CheeseType,
8-
"Int" => GraphQL::INT_TYPE,
8+
"Float" => GraphQL::FLOAT_TYPE,
99
"String" => GraphQL::STRING_TYPE,
1010
"DairyAnimal" => DairyAnimalEnum,
11-
"Float" => GraphQL::FLOAT_TYPE,
11+
"Int" => GraphQL::INT_TYPE,
1212
"Edible" => EdibleInterface,
1313
"Milk" => MilkType,
1414
"ID" => GraphQL::ID_TYPE,

spec/support/dairy_app.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class NoSuchDairyError < StandardError; end
55
EdibleInterface = GraphQL::InterfaceType.define do
66
name "Edible"
77
description "Something you can eat, yum"
8-
field :fatContent, !types.Float, "Percentage which is fat", property: :bogus_property
8+
field :fatContent, !types.Float, "Percentage which is fat"
99
field :origin, !types.String, "Place the edible comes from"
1010
end
1111

@@ -65,7 +65,6 @@ class NoSuchDairyError < StandardError; end
6565
field :id, !types.ID
6666
field :source, DairyAnimalEnum, "Animal which produced this milk"
6767
field :origin, !types.String, "Place the milk comes from"
68-
field :fatContent, !types.Float, "Percentage which is milkfat"
6968
field :flavors, types[types.String], "Chocolate, Strawberry, etc" do
7069
argument :limit, types.Int
7170
resolve -> (milk, args, ctx) {

0 commit comments

Comments
 (0)