Each controller should inherit from GraphqlRails::Controller. It is handy to have ApplicationGraphqlController:
class ApplicationGraphqlController < GraphqlRails::Controller
# write your shared code here
endto specify details about each controller action, you need to call method action inside controller. For example:
class ApplicationGraphqlController < GraphqlRails::Controller
# write your shared code here
endto define attributes which are accepted by each controller action, you need to call permit method, like this:
class UsersController < GraphqlRails::Controller
action(:create).permit(:id!, :some_string_value)
def create
User.create(params)
end
endpermitted values will be available via params method in controller action. By default all attributes will have String type. Also you can add exclamation mark after attribute name if you want to forbid nil value for that attribute.
If you want to use custom input type, you can define it like this:
class UsersController < GraphqlRails::Controller
action(:create).permit(:id!, some_integer_value: :int!, something_custom: YourGraphqlInputType)
def create
User.create(params)
end
endIf your model has defined input, then you can provide your model as input type, like this:
class User
graphql.input do
c.attribute :name
end
end
class UsersController < GraphqlRails::Controller
action(:create).permit(create_params: 'User')
# this is equivalent to:
# `action(:create).permit(create_params: User.graphql.input)`
def create
User.create(params[:create_params])
end
endAllows to permit single input field. It allows to set additional options for each field.
Specifies input type:
class OrderController < GraphqlRails::Controller
action(:create)
.permit_input(:price, type: :integer!)
# Same as `.permit(price: :integer!)`
endThere are few ways how to mark field as required.
- Adding exclamation mark at the end of type name:
class UsersController < GraphqlRails::Controller
action(:create).permit_input(:some_field, type: :int!)
end- Adding exclamation mark at the end of name
class UsersController < GraphqlRails::Controller
action(:create).permit_input(:some_field!)
end- Adding
required: trueoptions
class UsersController < GraphqlRails::Controller
action(:create).permit_input(:some_field, type: :bool, required: true)
endYou can describe each input by adding description keyword argument:
class OrderController < GraphqlRails::Controller
action(:create)
.permit_input(:price, type: :integer!, description: 'Price in Euro cents')
endsubtype allows to specify which named input should be used. Here is an example:
Let's say you have user with two input types
class User
graphql.input do |c|
c.attribute :full_name
c.attribute :email
end
graphql.input(:change_password) do |c|
c.attribute :password
c.attribute :password_confirmation
end
endIf you do not specify subtype then default (without name) input will be used. You need to specify subtype if you want to use non-default input:
class UsersController < GraphqlRails::Controller
# this is the input with email and full_name:
action(:create)
.permit_input(:input, type: 'User!')
# this is the input with password and password_confirmation:
action(:update_password)
.permit_input(:input, type: 'User!', subtype: :change_password)
endYou can mark input input as deprecated with deprecated option:
class UsersController < GraphqlRails::Controller
action(:create)
.permit_input(:input, type: 'User', deprecated: true)
action(:update)
.permit_input(:input, type: 'User', deprecated: 'use updateBasicUser instead')
endYou can mark collection action as paginated. In this case controller will return relay connection type and it will be possible to return only partial results. No need to do anything on controller side (you should always return full list of items)
class UsersController < GraphqlRails::Controller
action(:index).paginated
def index
User.all
end
endAlso check 'decorating controller responses' for more details about working with active record and decorators.
Allows to specify max items count per request
class UsersController < GraphqlRails::Controller
action(:index).paginated(max_page_size: 10) # add max items limit
def index
User.all # it will render max 10 users even you have requested more
end
endAllows to specify max items count per request
class UsersController < GraphqlRails::Controller
action(:index).paginated(default_page_size: 5) # add default items per page size
def index
User.all # it will render 5 users even you have more
end
endIf you want to define model dynamically, you can use combination of model and returns_list or returns_single. This is especially handy when model is used with action_default:
class OrdersController < GraphqlRails::Controller
model('Order')
action(:show).returns_single # returns `Order!`
action(:index).returns_list # returns `[Order!]!`
def show
Order.first
end
def index
Order.all
end
endYou must specify what each action will return. This is done with returns method:
class UsersController < GraphqlRails::Controller
action(:last_order).permit(:id).returns('Order!') # Order is your model class name
def last_order
user = User.find(params[:id]).orders.last
end
endYou can also return raw graphql-ruby types:
# raw graphql-ruby type:
class OrderType < GraphQL::Schema::Object
graphql_name 'Order'
field :id, ID
end
class UsersController < GraphqlRails::Controller
action(:last_order).permit(:id).returns(OrderType)
endCheck graphql-ruby documentation for more details about graphql-ruby types.
When you have defined model dynamically, you can use returns_list to indicate that action must return list without specifying model type for each action. By default list and inner types are required but you can change that with required_list: false and required_inner: false
class OrdersController < GraphqlRails::Controller
model('Order')
action(:index).returns_list(required_list: false, required_inner: false) # returns `[Order]`
action(:search).permit(:filter).returns_list # returns `[Order!]!`
endWhen you have defined model dynamically, you can use returns_single to indicate that action must return single item without specifying model type for each action. By default return type is required, but you can change that by providing required: false flag:
class OrdersController < GraphqlRails::Controller
model('Order')
action(:show).returns_single(required: false) # returns `Order`
action(:update).permit(title: :string!).returns_single # returns `Order!`
endIf you want to improve graphql documentation, you can add description for each action. To do so, use describe method:
class UsersController < GraphqlRails::Controller
action(:create).describe('Creates user')
def create
User.create(params)
end
endYou can customize your queries using options method. So far we've added input_format and allowed value is :original which specifies to keep the field name format.
options method can be used like so:
class UsersController < GraphqlRails::Controller
action(:create).options(input_format: :original).permit(:full_name)
def create
User.create(params)
end
endIf you do not like chainable methods, you can use "block" style action configuration:
class UsersController < GraphqlRails::Controller
action(:index) do |action|
action.paginated
action.permit(limit: :int!)
action.returns '[User!]!'
end
def create
User.create(params)
end
endmodel is just a shorter version of action_default.model. See action.model and action_default for more information:
class OrdersController < GraphqlRails::Controller
model('Order')
action(:show).returns_single # returns `Order!`
action(:index).returns_list # returns `[Order!]!`
def show
Order.first
end
def index
Order.all
end
endSometimes you want to have some shared attributes for all your actions. In order to make this possible you need to use action_default. It acts identical to action and is "inherited" by all actions defined after action_default part:
class UsersController < GraphqlRails::Controller
action_default.permit(token: :string!)
action(:update).returns('User!') # action(:update) has `permit(token: :string!)
action(:create).returns('User') # action(:create) has `permit(token: :string!)
endYou can add before_action to run some filters before calling your controller action. Here is an example:
class UsersController < GraphqlRails::Controller
before_action :require_auth_token
def create
User.create(params)
end
private
def require_auth_token # will run before `UsersController#create` action
raise 'Not authenticated' unless User.where(token: params[:token]).exist?
end
endYou can add after_action to run some filters after calling your controller action. Here is an example:
class UsersController < GraphqlRails::Controller
after_action :clear_cache
def create
User.create(params)
end
private
def clear_cache # will run after `UsersController#create` action
logger.log('Create action is completed')
end
endYou can add around_action to run some filters before and after calling your controller action. Here is an example:
class UsersController < GraphqlRails::Controller
around_action :use_custom_locale
def create
User.create(params)
end
private
def with_custom_locale
I18n.with_locale('it') do
yield # yield is mandatory
end
end
endbefore/after/around action filters can be written as blocks too:
class UsersController < GraphqlRails::Controller
around_action do |controller, block|
I18n.with_locale('it') do
block.call
end
end
def create
User.create(params)
end
endit's not recommended but might be helpful in some edge cases.
UsersController.before_action accepts only or except options which allows to skip filters for some actions.
class UsersController < GraphqlRails::Controller
before_action :require_auth_token, except: :show
before_action :require_admin_token, only: %i[update destroy]
def create
User.create(params)
end
def destroy
User.create(params)
end
private
def require_auth_token
raise 'Not authenticated' unless User.where(token: params[:token]).exist?
end
def require_admin_token
raise 'Admin not authenticated' unless Admin.where(token: params[:admin_token]).exist?
end
endSee 'Decorating controller responses' for various options how you can decorate paginated responses
The simplest way to render an error is to provide a list of error messages, like this:
class UsersController < GraphqlRails::Controller
action(:update).permit(:id, input: 'UserInput!').returns('User!')
def update
user = User.find(params[:id])
if user.update(params[:input])
user
else
render(errors: ['Something went wrong'])
end
end
endGraphqlRails controller has #render method which you can use to render errors:
class UsersController < GraphqlRails::Controller
action(:update).permit(:id, input: 'UserInput!').returns('User!')
def update
user = User.find(params[:id])
if user.update(params[:input])
user
else
render(errors: user.errors)
end
end
endWhen you want to return errors with custom data, you can provide hash like this:
class UsersController < GraphqlRails::Controller
action(:update).permit(:id, input: 'UserInput!').returns('User!')
def update
user = User.find(params[:id])
if user.update(params[:input])
user
else
render(
errors: [
{ message: 'Something went wrong', code: 500, type: 'fatal' },
{ message: 'Something went wrong', custom_param: true, ... },
]
)
end
end
endIf you want to have customized error classes you need to create errors which inherit from GraphqlRails::ExecutionError
class MyCustomError < GraphqlRails::ExecutionError
def to_h
# this part will be rendered in graphql
{ something_custom: 'yes' }
end
end
class UsersController < GraphqlRails::Controller
action(:update).permit(:id, input: 'UserInput!').returns('User!')
def update
user = User.find(params[:id])
if user.update(params[:input])
user
else
raise MyCustomError, 'ups!'
end
end
end