ruby: 2.7.1
rails: 6.0.3.4
graphql-ruby: 1.11.6
GraphQL Ruby
When dealing with GraphQL in Rails We will implement the API using the gem ↑.
graphiql-rails In addition, if you put graphiql-rails gem, GraphQL implemented on the browser You will be able to use an IDE that allows you to check: sparkles:
graphql-ruby
, the gem of graphiql-rails
will be added to the Gemfile.Image image
Gemfile
gem 'graphql'
gem 'graphiql-rails' #This time I put it first
Once the gem is installed, run the rails generate graphql: install
command to generate each file.
The generated file is as follows ↓
$ rails generate graphql:install
create app/graphql/types
create app/graphql/types/.keep
create app/graphql/app_schema.rb
create app/graphql/types/base_object.rb
create app/graphql/types/base_argument.rb
create app/graphql/types/base_field.rb
create app/graphql/types/base_enum.rb
create app/graphql/types/base_input_object.rb
create app/graphql/types/base_interface.rb
create app/graphql/types/base_scalar.rb
create app/graphql/types/base_union.rb
create app/graphql/types/query_type.rb
add_root_type query
create app/graphql/mutations
create app/graphql/mutations/.keep
create app/graphql/mutations/base_mutation.rb
create app/graphql/types/mutation_type.rb
add_root_type mutation
create app/controllers/graphql_controller.rb
route post "/graphql", to: "graphql#execute"
gemfile graphiql-rails
route graphiql-rails
At this point, routes.rb
looks like this:
Rails.application.routes.draw do
# GraphQL
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
end
post '/graphql', to: 'graphql#execute'
end
First of all, we have to define the Type corresponding to each table, so
As an example, I would like to create a user_type
that corresponds to the following users
table.
create_table :users do |t|
t.string :name, null: false
t.string :email
t.timestamps
end
Execute the following command to create user_type
.
(The specified type is the type for id whose ID
is defined in GraphQL (actually String)
Also, those with !
At the end are nullable types, and those without !
Are nullable. )
$ bundle exec rails g graphql:object User id:ID! name:String! email:String
[Supplement] If the table already exists in the DB, it seems that it will do it for you.
$ bundle exec rails g graphql:object User
↑ This was fine: sparkles:
The generated file graphql/type/user_type.rb
looks like this:
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :email, String, null: true
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end
Add the following to the already generated graphql/type/query_type.rb
.
field :users, [Types::UserType], null: false
def users
User.all
end
If you throw the following query on http: // localhost: 3000/graphiql
, you will get a response.
{
users {
id
name
email
}
}
Next, I would like to create Mutations CreateUser
to create users.
$ bundle exec rails g graphql:mutation CreateUser
graphql/mutations/create_user.rb
will be created, so modify it as follows.
module Mutations
class CreateUser < BaseMutation
field :user, Types::UserType, null: true
argument :name, String, required: true
argument :email, String, required: false
def resolve(**args)
user = User.create!(args)
{
user: user
}
end
end
end
Add the following to the already generated graphql/types/mutation_type.rb
.
module Types
class MutationType < Types::BaseObject
field :createUser, mutation: Mutations::CreateUser #Postscript
end
end
User is created by executing the following on http: // localhost: 3000/graphiql
.
mutation {
createUser(
input:{
name: "user"
email: "[email protected]"
}
){
user {
id
name
email
}
}
}
Association
--For a 1: 1 related table
For example, if Post
is associated with Label
1: 1.
label_type.rb
module Types
class LabelType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
...
end
end
module Types
class PostType < Types::BaseObject
field :label, LabelType, null: true
end
end
You can define label
as LabelType
as in ↑.
As an image of Query in this case
{
posts {
id
label {
id
name
}
}
}
As mentioned above, you can query the required value with label
as LabelType
.
--1: For N related tables
For example, if User
is Post
and 1: N
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :label, LabelType, null: true
end
end
module Types
class UserType < Types::BaseObject
field :posts, [PostType], null: false
end
end
As mentioned above, posts
can be defined as[PostType]
, and as a Query
{
user(id: 1234) {
id
posts {
id
label {
id
name
}
}
}
}
You can call it like ↑.
graphql-batch
As explained in ↑, you can also fetch data from related tables of 1: 1 and 1: N.
If it is left as it is, a large number of inquiries to the DB may occur.
In the example when User
is Post
and 1: N, if there are 100 Post
s, the query will occur 100 times each.
Therefore, I will introduce graphql-batch which is one of the solutions to collect multiple inquiries.
gem 'graphql-batch'
After installing Gem, create a loader
.
loader
is an implementation of the" group multiple queries "part.
graphql/loaders/record_loader.rb
module Loaders
class RecordLoader < GraphQL::Batch::Loader
def initialize(model)
@model = model
end
def perform(ids)
@model.where(id: ids).each { |record| fulfill(record.id, record) }
ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
end
end
end
Applying this when the previous Post
is associated with Label
1: 1
module Types
class PostType < Types::BaseObject
field :label, LabelType, null: true
def label
Loaders::RecordLoader.for(Label).load(object.label_id)
end
end
end
You can write like this.
If User
is Post
and 1: N, create a separate loader.
graphql/loaders/association_loader.rb
module Loaders
class AssociationLoader < GraphQL::Batch::Loader
def self.validate(model, association_name)
new(model, association_name)
nil
end
def initialize(model, association_name)
@model = model
@association_name = association_name
validate
end
def load(record)
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
return Promise.resolve(read_association(record)) if association_loaded?(record)
super
end
# We want to load the associations on all records, even if they have the same id
def cache_key(record)
record.object_id
end
def perform(records)
preload_association(records)
records.each { |record| fulfill(record, read_association(record)) }
end
private
def validate
unless @model.reflect_on_association(@association_name)
raise ArgumentError, "No association #{@association_name} on #{@model}"
end
end
def preload_association(records)
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
end
def read_association(record)
record.public_send(@association_name)
end
def association_loaded?(record)
record.association(@association_name).loaded?
end
end
end
If you write as follows, you will be able to make inquiries all at once.
module Types
class UserType < Types::BaseObject
field :posts, [PostType], null: false
def posts
Loaders::AssociationLoader.for(User, :posts).load(object)
end
end
end
I would like to automatically generate a nice document from the schema file defined at the end.
Can be mounted on routes.rb
and automatically updates graphdoc with each deployment
When I was looking for a convenient gem, there was a gem called graphdoc-ruby, so I will try it.
Add the following to Gemfile
gem 'graphdoc-ruby'
You also need the npm package @ 2fd/graphdoc. Install it in the Docker image in advance. (If you are not using Docker, you should install it in your local environment)
Example)
RUN set -ex \
&& wget -qO- https://deb.nodesource.com/setup_10.x | bash - \
&& apt-get update \
&& apt-get install -y \
...
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g yarn \
&& npm install -g @2fd/graphdoc #Install
Add the following to config/routes.rb
config/routes.rb
Rails.application.routes.draw do
mount GraphdocRuby::Application, at: 'graphdoc'
end
config/initializers/graphdoc.rb
Example)
GraphdocRuby.configure do |config|
config.endpoint = 'http://0.0.0.0:3000/api/v1/graphql'
end
Restart Rails and it's okay if the documentation is generated at http: // localhost: 3000/graphdoc: sparkles:
-- http: // localhost: 3000/graphiql
If the following error occurs when accessing
```
Sprockets::Rails::Helper::AssetNotPrecompiled in GraphiQL::Rails::Editors#show
```
--Solution 1
Add the following to app/assets/config/manifest.js
```
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js
```
[AssetNotPrecompiled error with Sprockets 4.0 · Issue #75 · rmosolgo/graphiql-rails](https://github.com/rmosolgo/graphiql-rails/issues/75#issuecomment-546306742)
-> However, with this, I get a Sprockets :: FileNotFound: couldn't find file'graphiql/rails/application.css'
error during Production and cannot use it ...
-** Solution 2 (Successful method) **
Lower to version 3.7.2 of gem'sprocket'
```ruby
gem 'sprockets', '~> 3.7.2' [#1098: slowdev/knowledge/ios/Add Firebase with Carthage](/posts/1098)
```
Add ↑ and bundle update
How to use GraphQL in Rails 6 API mode (including error countermeasures) --Qiita
--The graphiql screen shows TypeError: Cannot read property'types' of undefined
-> In my environment, it was cured by restarting Rails
--The graphiql screen displays SyntaxError: Unexpected token <in JSON at position 0
-> An error may have occurred, so look at the log and correct it.
-[Rails] Create API with graphql-ruby --Qiita --The story of introducing GraphQL in a project where REST API is the mainstream (server side) --Sansan Builders Blog -Thorough introduction to "GraphQL" ─ Comparison with REST, learning from implementation of both API and front --Engineer Hub | Think about the career of a young Web engineer! -Introduction of API specification-centered development using GraphQL and its effect --Kaizen Platform developer blog -GraphQL Ruby [class-based API] --Qiita -hawksnowlog: Getting Started with GraphQL in Ruby (Sinatra) --I added GraphQL API to an existing Rails project --Qiita -How to migrate from sprockets to Webpacker with Ruby on Rails and coexist those that cannot be migrated --Qiita -Reading: GraphQL for the first time-Basics of types | tkhm | note
Recommended Posts