Create a GraphQL interface by implementing nodes and calls, then running queries.
Example Implementation
- See test implementation in
/spec/support/dummy_app/nodes.rb - See
graphql-ruby-demowith Rails on github or heroku
Usage
Create a GraphQL interface:
- Implement nodes that wrap objects in your application
- Implement calls that expose those objects (and may mutate the application state)
- Execute queries on the system.
API docs: Ruby gem, master branch
Nodes
Nodes are delegators that wrap objects in your app. You must whitelist fields by declaring them in the class definition.
class FishNode < GraphQL::Node exposes "Fish" cursor(:id) field.number(:id) field.string(:name) field.string(:species) # specify an `AquariumNode`: field.aquarium(:aquarium) end
You can also declare connections between objects:
class AquariumNode < GraphQL::Node exposes "Aquarium" cursor(:id) field.number(:id) field.number(:occupancy) field.connection(:fishes) end
You can make custom connections:
class FishSchoolConnection < GraphQL::Connection type :fish_school # now it is a field type call :largest, -> (prev_value, number) { fishes.sort_by(&:weight).first(number.to_i) } field.number(:count) # delegated to `target` field.boolean(:has_more) def has_more # the `largest()` call may have removed some items: target.count < original_target.count end end
Then use them:
class AquariumNode < GraphQL::Node field.fish_school(:fishes) end
And in queries:
aquarium(1) {
name,
occupancy,
fishes.largest(3) {
edges {
node { name, species }
},
count,
has_more
}
}
}
Calls
Calls selectively expose your application to the world. They always return values and they may perform mutations.
Calls declare returns, declare arguments, and implement #execute!.
This call just finds values:
class FindFishCall < GraphQL::RootCall returns :fish argument.number(:id) def execute!(id) Fish.find(id) end end
This call performs a mutation:
class RelocateFishCall < GraphQL::RootCall returns :fish, :previous_aquarium, :new_aquarium argument.number(:fish_id) argument.number(:new_aquarium_id) def execute!(fish_id, new_aquarium_id) fish = Fish.find(fish_id) # context is defined by the query, see below if !context[:user].can_move?(fish) raise RelocateNotAllowedError end previous_aquarium = fish.aquarium new_aquarium = Aquarium.find(new_aquarium_id) fish.update_attributes(aquarium: new_aquarium) { fish: fish, previous_aquarium: previous_aquarium, new_aquarium: new_aquarium, } end end
Queries
When your system is set up, you can perform queries from a string.
query_str = "find_fish(1) { name, species } " query = GraphQL::Query.new(query_str) result = query.as_result result # { # "1" => { # "name" => "Sharky", # "species" => "Goldfish", # } # }
Each query may also define a context object which will be accessible at every point in execution.
query_str = "move_fish(1, 3) { fish { name }, new_aquarium { occupancy } }" query_ctx = {user: current_user, request: request} query = GraphQL::Query.new(query_str, context: query_ctx) result = query.as_result result # { # "fish" => { # "name" => "Sharky" # }, # "new_aquarium" => { # "occupancy" => 12 # } # }
You could do something like this inside a Rails controller.
To Do:
- testing with JSON args
- Make root calls plain ol' calls, on the root?
- Make fields like calls with no args?
- improve debugging experience
- build nodes for Date, DateTime, Time, Hash
- How do you express failure? HTTP response?
errorskey? - Handle blank objects in nested calls (how? wait for spec)
- Implement calls as arguments
- double-check how to handle
pals.first(3) { count } - Implement call argument introspection (wait for spec)

