To make your model graphql-firendly, you need to inlcude GraphqlRails::Model
. Your model can be any ruby class (PORO, ActiveRecord::Base or anything else)
Also you need to define which attributes can be exposed via graphql. To do so, use graphql
method inside your model body. Example:
class User # works with any class including ActiveRecord
include GraphqlRails::Model
graphql do |c|
c.attribute :id
c.full_name, type: :string
end
end
This method must be called inside your model body. graphql
is used for making your model convertible to graphql type.
Most commonly you will use attribute
to make your model methods and attributes visible via graphql endpoint.
Some types can be determined by attribute name, so you can skip this attribute:
- attributes which ends with name
*_id
hasID!
type - attributes which ends with
?
hasBoolean!
type - all other attributes without type are considered to be
String
Usage example:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute :shop_id # ID! type
c.attribute :full_name # String type
c.attribute :admin? # Boolean! type
c.attribute :level, type: 'integer'
c.attribute :money, type: 'float'
end
end
You can also use some build in aliases for types, such as:
'id'
is alias forGraphQL::Types::ID
'integer'
,'int'
are aliases forGraphQL::Types::Int
'bigint'
,'big_int'
are aliases forGraphQL::Types::BigInt
- 'float', 'double', 'decimal' are aliases for
GraphQL::Types::Float
'bool'
,'boolean'
are aliases for GraphQL::Types::Boolean- String:
'string'
,'str'
,'text'
- 'date' is alias for
GraphQL::Types::ISO8601Date
- 'time', 'datetime', 'date_time' are aliases for
GraphQL::Types::ISO8601DateTime
- 'json' is alias for
GraphQL::Types::JSON
Usage example:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:about_me).type(:text)
c.attribute(:active).type('bool!')
c.attribute(:created_at).type(:datetime!)
c.attribute(:data).type(:json!)
c.attribute(:login_dates).type('[date!]!')
end
end
You can also use raw graphql-ruby objects as attribute types. Here is an example:
# raw graphql-ruby type:
class AddressType < GraphQL::Schema::Object
graphql_name 'Address'
field :city, String, null: false
field :street_name, String, null: false
field :street_number, Integer
end
# GraphqlRails model:
class User
include GraphqlRails::Model
graphql.attribute :address, type: 'AddressType!', required: true
end
Check graphql-ruby documentation for more details about graphql-ruby types.
By default graphql attribute names are expected to be same as model methods/attributes, but if you want to use different name on graphql side, you can use property
option:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute :shop_id, property: :department_id
end
def department_id
456
end
end
You can also describe each attribute and make graphql documentation even more readable. To do so, add description
option:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute :shop_id, description: 'references to shop'
end
end
Attribute can be marked as deprecated with deprecated
method:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:legacy_name).deprecated
c.attribute(:legacy_id).deprecated('This is my custom deprecation reason')
end
end
Groups are handy feature when you want to have multiple schemas. For example, you want to have public graphql endpoint and internal graphql endpoint where each group has some unique nodes. If attribute has groups
set, then this attribute will be visible only in appropriate group schemas.
class User
include GraphqlRails::Model
graphql do |c|
# visible in all schemas (default):
c.attribute(:email)
# visible in "internal" and "beta" schemas only:
c.attribute(:admin_id).groups(%i[internal beta])
# visible in "external" schema only:
c.attribute(:nickname).groups(%i[external])
end
end
Alias for Attribute#groups.
class User
include GraphqlRails::Model
graphql do |c|
# visible in all schemas (default):
c.attribute(:email)
# visible in "external" schema only:
c.attribute(:nickname).group(:external)
end
end
attribute.hidden_in_groups
Opposite for Attribute#groups. It hides attribute in given groups
class User
include GraphqlRails::Model
graphql do |c|
# visible in all schemas (default):
c.attribute(:email)
# visible in all schemas except "external":
c.attribute(:nickname).hidden_in_groups(:external)
end
end
Allows passing options to attribute definition. Available options:
attribute_name_format
- if:original
value is passed, it will not camelCase attribute name.
class User
include GraphqlRails::Model
graphql do |c|
c.attribute :first_name # will be accessible as firstName from client side
c.attribute :first_name, options: { attribute_name_format: :original } # will be accessible as first_name from client side
end
end
Allows passing extras to enable graphql-ruby field extensions
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:items).extras([:lookahead])
end
end
To define attributes which are accepted by each model method, you need to call permit
method, like this:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:avatar_url).permit(size: :int!)
end
def avatar_url(size:)
# some code here
end
end
Allows to permit single input field. It allows to set additional options for each field.
There are few ways how to mark field as required.
- Adding exclamation mark at the end of type name:
class User
include GraphqlRails::Model
graphql.attribute(:avatar_url).permit_input(:size, type: :int!)
end
- Adding
required: true
options
class User
include GraphqlRails::Model
graphql.attribute(:avatar_url).permit_input(:size, type: :int, required: true)
end
You can describe each input by adding description
keyword argument:
class User
include GraphqlRails::Model
graphql.attribute(:avatar_url).permit_input(:size, description: 'max size of avatar')
end
subtype
allows to specify which named input should be used. Here is an example:
class Image
graphql.input(:size_options) do |c|
c.attribute :width
c.attribute :height
end
end
class User
graphql.attribute(:avatar_url).permit_input(:size, type: Image, subtype: :size_options)
end
You can mark input input as deprecated with deprecated
option:
class User
include GraphqlRails::Model
graphql.attribute(:avatar_url)
.permit_input(:size, type: :int!, deprecated: true)
graphql.attribute(:logo_url)
.permit_input(:size, type: :int!, deprecated: 'custom image size is deprecated')
end
You can mark collection method as paginated
. In this case method will return relay connection type and it will be possible to return only partial results. No need to do anything on method side (you should always return full list of items)
class User
include GraphqlRails::Model
graphql.attribute :items, type: '[Item]', paginated: true
def items
Item.all
end
end
You can mark attribute as required using required
method:
class User
include GraphqlRails::Model
graphql.attribute(:item).type('Item').required
end
You can mark attribute as optional using optional
method:
class User
include GraphqlRails::Model
graphql.attribute(:item).type('Item').optional
end
When you want to have identical attributes, you can use Attribute#same_as
to make sure that attribute params will stay in sync:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:user_id).type('ID').description('User ID')
c.attribute(:person_id).same_as(c.attribute(:user_id))
end
end
When you want to define some options dynamically, it's quite handy to use "Attribute#with" method:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:shop_id).with(type: 'ID', description: 'references to shop')
# same as:
# c.attribute(:shop_id, type: 'ID', description: 'references to shop')
# also same as:
# c.attribute(:shop_id).type('ID').description('references to shop')
end
end
If your attribute definition is complex, you can define attribute in more eye-friendly chainable way with:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:shop_id)
.type('ID!')
.description('references to shop')
end
end
You can also use block in order to specify attribute configuration:
class User
include GraphqlRails::Model
graphql do |c|
c.attribute(:shop_id) do |attr|
attr.type 'ID!'
attr.description 'references to shop'
end
end
end
By default graphql type name will be same as model name, but you can change it via name
method
class User
include GraphqlRails::Model
graphql do |c|
c.name 'Employee'
end
end
To improve graphql documentation, you can description for your graphql type:
class User
include GraphqlRails::Model
graphql do |c|
c.description 'Users are awesome!'
end
end
implements
indicates that graphql type implements one or more interfaces:
module UserInterface
include GraphQL::Schema::Interface
# ....
end
module AdvancedUserInterface
include GraphQL::Schema::Interface
# ...
end
class AdminUser
include GraphqlRails::Model
graphql do |c|
c.implements(UserInterface, AdvancedUserInterface)
end
end
Sometimes it's handy to get raw graphql type. To do so you can call:
YourModel.graphql.graphql_type
You can define input types:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :name
end
end
Also you can have multiple input types:
class User
include GraphqlRails::Model
graphql.input(:create) do |c|
c.attribute :name
end
graphql.input(:update) do |c|
c.attribute :id
c.attribute :name
end
end
Most commonly you will use attribute
to define what kind of values your endpoint accepts
You can specify your input attribute type. If type is not provided then type is set to :string
.
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :friends_count, type: :integer!
end
end
If your input attribute definition is complex, you can define attribute in more eye-friendly chainable way with:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute(:friends_count)
.type(:integer!)
.description('Can not be zero or less')
end
end
There are few ways how to mark field as required.
- Adding exclamation mark at the end of type name:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :friends_count, type: :integer!
end
end
- Adding
required: true
value
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :friends_count, type: :integer, required: true
end
end
You can specify your input attribute as enum:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :favorite_fruit, enum: %i[apple orange]
end
end
By default enum type is not required. To make it required add required: true
:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :favorite_fruit, required: true, enum: %i[apple orange]
end
end
To improve graphql endpoint documentation, you can add description for each input attribute:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute :name, description: "User's first name"
end
end
You can mark input attribute as deprecated with deprecated
method:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute(:full_name).deprecated('Use firstName and lastName instead')
c.attribute(:surname).deprecated
end
end
You can set default value for input attribute:
class User
include GraphqlRails::Model
graphql.input do |c|
c.attribute(:is_admin).type('Boolean').default_value(false)
end
end
You can copy existing config from other attribute using Attribute#same_as
method:
class User
include GraphqlRails::Model
graphql.input(:create) do |c|
c.attribute(:first_name).type('String!')
c.attribute(:last_name).type('String!')
end
graphql.input(:update) do |c|
c.attribute(:id).type('ID!')
graphql.input(:contract).attributes.each_value do |attr|
c.attribute(attr.name).same_as(attr)
end
end
end
Sometimes it's handy to have different input attribute on graphql level and different on controller. That's when Attribute#property
comes to the rescue:
class Post
include GraphqlRails::Model
graphql.input do |c|
c.attribute(:author_id).type('ID').property(:user_id)
end
end
Then mutation such as:
mutation createPost(input: { authorId: 123 }) {
author
}
Will pass user_id
instead of authorId
in controller:
# posts_controller.rb
def create
Post.create!(user_id: params[:input][:user_id])
end
It's possible to access graphql_context in your model using method graphql_context
:
class User
include GraphqlRails::Model
def method_with_context
graphql_context[:some_data]
end
end
Keep in mind that this value will be set only during graphql execution, but in tests this value will be nil
. To avoid this, you need to set show_graphql_context
manually like this:
class User
include GraphqlRails::Model
def show_graphql_context
graphql_context[:data]
end
end
user = User.new(...)
user.show_graphql_context #=> NoMethodError: undefined method `[]' for nil:NilClass
user.graphql_context = { data: 'goes to context' }
user.show_graphql_context #=> { data: 'goes to context' }