diff --git a/Gemfile b/Gemfile index 897dd45..465eb59 100644 --- a/Gemfile +++ b/Gemfile @@ -7,12 +7,11 @@ end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.0.1' -# use PG in prod for heroku -gem 'pg' # Use Puma as the app server gem 'puma', '~> 3.0' # Use GraphQL! gem 'graphql', '~> 1.4.3' +gem 'graphql-batch' # GraphiQL Interface gem 'graphiql-rails', '~> 1.4.1' @@ -24,6 +23,10 @@ group :development, :test do gem 'sqlite3' end +group :production do + gem 'pg' +end + group :development do # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. gem 'web-console', '>= 3.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index be02e6d..5df6de2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,6 +51,9 @@ GEM graphiql-rails (1.4.1) rails graphql (1.4.3) + graphql-batch (0.3.1) + graphql (>= 0.8, < 2) + promise.rb (~> 0.7.2) i18n (0.8.0) json (2.0.2) listen (3.0.8) @@ -70,6 +73,7 @@ GEM nokogiri (1.7.0.1) mini_portile2 (~> 2.1.0) pg (0.19.0) + promise.rb (0.7.2) puma (3.7.0) rack (2.0.1) rack-test (0.6.3) @@ -137,6 +141,7 @@ DEPENDENCIES byebug graphiql-rails (~> 1.4.1) graphql (~> 1.4.3) + graphql-batch listen (~> 3.0.5) pg puma (~> 3.0) diff --git a/app/models/graph.rb b/app/models/graph.rb new file mode 100644 index 0000000..5579df0 --- /dev/null +++ b/app/models/graph.rb @@ -0,0 +1,17 @@ +module Graph + class << self + def find_by_id_field(type, model) + GraphQL::Field.define do + type type + argument :id, !types.ID + resolve ->(_, args, _) do + gid = GlobalID.parse(args[:id]) + + return unless gid.model_name == type.name + + Graph::FindLoader.for(model).load(gid.model_id.to_i) + end + end + end + end +end diff --git a/app/models/graph/find_loader.rb b/app/models/graph/find_loader.rb new file mode 100644 index 0000000..45f4c90 --- /dev/null +++ b/app/models/graph/find_loader.rb @@ -0,0 +1,11 @@ +class Graph::FindLoader < GraphQL::Batch::Loader + def initialize(model) + @model = model + end + + def perform(ids) + records = @model.where(id: ids.uniq) + records.each { |record| fulfill(record.id, record) } + ids.each { |id| fulfill(id, nil) unless fulfilled?(id) } + end +end diff --git a/app/models/graph/schema.rb b/app/models/graph/schema.rb index 80a672f..2959af8 100644 --- a/app/models/graph/schema.rb +++ b/app/models/graph/schema.rb @@ -19,5 +19,8 @@ module Graph Object.const_get(gid.model_name).find(gid.model_id) end + + lazy_resolve(Promise, :sync) + instrument(:query, GraphQL::Batch::Setup) end end diff --git a/app/models/graph/types/query.rb b/app/models/graph/types/query.rb index ae1ebd6..926777a 100644 --- a/app/models/graph/types/query.rb +++ b/app/models/graph/types/query.rb @@ -8,56 +8,32 @@ module Types field :node, GraphQL::Relay::Node.field field :nodes, GraphQL::Relay::Node.plural_field - field :person, Graph::Types::Person do - argument :id, types.ID - resolve ->(_, args, _) { ::Person.find_by(id: args['id']) } - end - + field :person, Graph.find_by_id_field(Graph::Types::Person, ::Person) field :people, types[Graph::Types::Person] do resolve ->(_, _, _) { ::Person.all } end - field :planet, Graph::Types::Planet do - argument :id, types.ID - resolve ->(_, args, _) { ::Planet.find(args['id']) } - end - + field :planet, Graph.find_by_id_field(Graph::Types::Planet, ::Planet) field :planets, types[Graph::Types::Planet] do resolve ->(_, _, _) { ::Planet.all } end - field :film, Graph::Types::Film do - argument :id, types.ID - resolve ->(_, args, _) { ::Film.find(args['id']) } - end - + field :film, Graph.find_by_id_field(Graph::Types::Film, ::Film) field :films, types[Graph::Types::Film] do resolve ->(_, _, _) { ::Film.all } end - field :species, Graph::Types::Species do - argument :id, types.ID - resolve ->(_, args, _) { ::Species.find(args['id']) } - end - + field :species, Graph.find_by_id_field(Graph::Types::Species, ::Species) field :allSpecies, types[Graph::Types::Species] do resolve ->(_, _, _) { ::Species.all } end - field :starship, Graph::Types::Starship do - argument :id, types.ID - resolve ->(_, args, _) { ::Starship.find(args['id']) } - end - + field :starship, Graph.find_by_id_field(Graph::Types::Starship, ::Starship) field :starships, types[Graph::Types::Starship] do resolve ->(_, _, _) { ::Starship.all } end - field :vehicle, Graph::Types::Vehicle do - argument :id, types.ID - resolve ->(_, args, _) { ::Vehicle.find(args['id']) } - end - + field :vehicle, Graph.find_by_id_field(Graph::Types::Vehicle, ::Vehicle) field :vehicles, types[Graph::Types::Vehicle] do resolve ->(_, _, _) { ::Vehicle.all } end diff --git a/config/secrets.yml b/config/secrets.yml index 979406d..ef2cf88 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -1,8 +1,8 @@ development: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + secret_key_base: secret-for-development-not-so-secret test: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + secret_key_base: secret-for-test-not-so-secret production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/test/controllers/graphql_controller_test.rb b/test/controllers/graphql_controller_test.rb index a70f06a..1ca1af5 100644 --- a/test/controllers/graphql_controller_test.rb +++ b/test/controllers/graphql_controller_test.rb @@ -113,7 +113,7 @@ class GraphQLControllerTest < ActionDispatch::IntegrationTest def full_graphql_query " - query Full($personID: ID, $filmID: ID, $planetID: ID, $starshipID: ID, $speciesID: ID, $vehicleID: ID) { + query Full($personID: ID!, $filmID: ID!, $planetID: ID!, $starshipID: ID!, $speciesID: ID!, $vehicleID: ID!) { person(id: $personID) { birthYear eyeColor @@ -204,12 +204,12 @@ def default_variables vehicle = vehicles(:snowspeeder) { - "starshipID" => starship.id, - "personID" => person.id, - "filmID" => film.id, - "planetID" => planet.id, - "speciesID" => species.id, - "vehicleID" => vehicle.id + "starshipID" => starship.to_global_id, + "personID" => person.to_global_id, + "filmID" => film.to_global_id, + "planetID" => planet.to_global_id, + "speciesID" => species.to_global_id, + "vehicleID" => vehicle.to_global_id, } end end