OneHQ GraphQL interface to Ruby Graphql.
Define a global default scope.
::HQ::GraphQL.config do |config|
config.default_scope = ->(scope, context) do
scope.where(organization_id: context[:organization_id])
end
endGenerated collection associations can apply that scope before sorting and pagination. Unauthorized records can also be removed before GraphQL resolves non-null list elements:
::HQ::GraphQL.configure do |config|
config.apply_default_scope_to_associations = true
config.filter_unauthorized_collection_items = true
endBoth settings are off by default. An individual generated association can override either setting in its field block:
query do
update :audit_entries do
apply_default_scope false
filter_unauthorized_items false
end
endObject-level authorization is enabled by default for every GraphQL object type. A projection that intentionally exposes records outside the normal authorization boundary can opt out explicitly:
class OrganizationOptionType < ::GraphQL::Schema::Object
authorize_objects false
endThis skips only HQ::GraphQL's configured object authorization callback for
that type. GraphQL's built-in type authorization still runs.
Define a global excluded input fields. Useful for excluding (autogenerated | auto set) fields like below.
::HQ::GraphQL.configure do |config|
config.excluded_inputs = [:id, :created_at, :updated_at]
endConnect to ActiveRecord to auto generate queries and mutations.
Include ::HQ::GraphQL::Resource and set self.model_name to start using queries.
Fields will be created for all active record columns. Association fields will be created if the association
is also a GraphQL Resource.
class AdvisorResource
include ::HQ::GraphQL::Resource
# ActiveRecord Model Name
self.model_name = "Advisor"
endclass AdvisorResource
include ::HQ::GraphQL::Resource
self.model_name = "Advisor"
# Turn off fields for active record associations
query attributes: true, associations: false do
# Create field for addresses
add_association :addresses
# add a custom field
field :custom_field, String, null: false
def custom_field
"Fizz"
end
end
endMutations will not be created by default. Add mutations to a resource to build mutations for create, update, and
destroy.
class AdvisorResource
include ::HQ::GraphQL::Resource
self.model_name = "Advisor"
# Builds the following mutations:
# createAdvisor
# updateAdvisor
# destroyAdvisor
mutations create: true, update: true, destroy: true
# Turn off fields for active record associations
inputs attributes: true, associations: false do
# Create input argument for addresses
add_association :addresses
end
endA mutation's own check only knows the model (updateSalesManager asks "may I update SalesManager?"). A host that
scopes its restrictions per parent (Advisor -> SalesManager) authors them under the parent, which that question can
never reach. record_parents says whose a record is, and the gem asks about it as a child of each:
HQ::GraphQL.configure do |config|
# Who a record belongs to: the parent classes to ask about, an empty list for none.
# EVERY owner it has, not just the first: a model that allows several owners is restricted
# under each of them, and a restriction on one that was skipped would be bypassed.
config.record_parents = ->(record, context) do
record.class.owner_associations.filter_map { |name| record.public_send(name)&.class }.uniq
end
# Answers every question: the nested-attribute rows and, now, a record as a child of its owner.
config.authorize_nested_attributes = ->(action, klass, parent_klass, context) do
# may context[:current_user] `action` a `klass` under a `parent_klass`?
end
endInert until both are set. create, update, destroy and copy ask about the record the mutation found (or built);
an update also asks, as a :create, about any new owner it would leave the record with. A refusal writes nothing and answers
{ "base" => ["You are not allowed to update sales managers"] }.
Auto generate enums from the database using ActiveRecord This comes in handy when we have constants that we want represented as enums.
Let's assume we're saving data into a user types table
# select * from user_types;
id | name
--- +-------------
1 | Admin
2 | Support User
(2 rows)
class Enums::UserType < ::GraphQL::Schema::Enum
with_model
endThis class automatically uses the UserType ActiveRecord model to generate an enum:
enum UserType {
Admin
SupportUser
}Add mutations to your schema
class RootMutation < ::HQ::GraphQL::RootMutation; end
class Schema < ::GraphQL::Schema
mutation(RootMutation)
endCreate a root query:
class AdvisorResource
include ::HQ::GraphQL::Resource
self.model_name = "Advisor"
root_query
end
class RootQuery < ::HQ::GraphQL::RootQuery; end
class Schema < ::GraphQL::Schema
mutation(RootQuery)
endclass AdvisorResource
include ::HQ::GraphQL::Resource
self.model_name = "Advisor"
def_root :advisors, is_array: true, null: false do
argument :active, ::GraphQL::Types::Boolean, required: false
def resolve(active: nil)
scope = Advisor.all
if active
scope = scope.where(active: true)
end
end
end
endclass AdvisorType < ::GraphQL::Schema::Object
# Supports graphql-ruby functionality
field :id, Int, null: false
# Lazy Loading
# Useful for loading data from the database to generate a schema
lazy_load do
load_data_from_db.each do |name|
field name, String, null: false
end
end
# Attach the GraphQL object to an ActiveRecord Model
# First argument is the string form of your ActiveRecord model.
#
# attributes:
# Set it to false if you don't want to auto-include your model's attributes.
# Defaults to true.
#
# associations:
# Set it to false if you don't want to auto-include your model's associations.
# Defaults to true.
with_model "Advisor", attributes: true, associations: false
# Remove attributes that were included by `with_model`
remove_attrs :id, :created_at, :updated_at
# Remove associations that were included by `with_model`
remove_associations :organization, :created_by
end