Skip to content

About

No description, website, or topics provided.

Resources

Stars

11 stars

Watchers

18 watching

Forks

Latest commit

 

History

254 Commits

Folders and files

Repository files navigation

HQ::GraphQL

OneHQ GraphQL interface to Ruby Graphql.

Test and Lint GitHub tag

Configuration

Default Scope

Define a global default scope.

::HQ::GraphQL.config do |config|
  config.default_scope = ->(scope, context) do
    scope.where(organization_id: context[:organization_id])
  end
end

Generated 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
end

Both 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
end

Object-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
end

This skips only HQ::GraphQL's configured object authorization callback for that type. GraphQL's built-in type authorization still runs.

Excluded Inputs

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]
end

GraphQL Resource

Connect to ActiveRecord to auto generate queries and mutations.

Queries

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"
end

Customize

class 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
end

Mutations

Mutations 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
end

Authorizing a record through its owner

A 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
end

Inert 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"] }.

Enums

Auto generate enums from the database using ActiveRecord This comes in handy when we have constants that we want represented as enums.

Example

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
end

This class automatically uses the UserType ActiveRecord model to generate an enum:

enum UserType {
    Admin
    SupportUser
}

Root Mutations

Add mutations to your schema

class RootMutation < ::HQ::GraphQL::RootMutation; end

class Schema < ::GraphQL::Schema
  mutation(RootMutation)
end

Default Root Queries

Create 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)
end

Custom Root Queries

class 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
end

Create a new ::GraphQL::Schema::Object

class 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

About

No description, website, or topics provided.

Resources

Stars

11 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages