Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

graphql-relay

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

graphql-relay

  • 0.12.0
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

graphql-relay

Gem Version Build Status Code Climate Test Coverage

Helpers for using graphql with Relay. Includes support for serving Relay connections from Arrays, ActiveRecord::Relations and Sequel::Datasets.

API Documentation

Installation

gem "graphql-relay"
bundle install

Usage

graphql-relay provides several helpers for making a Relay-compliant GraphQL endpoint in Ruby:

  • global ids support Relay's UUID-based refetching
  • connections implement Relay's pagination
  • mutations allow Relay to mutate your system predictably

Global Ids

Global ids (or UUIDs) provide refetching & global identification for Relay.

UUID Lookup

Use GraphQL::Relay::GlobalNodeIdentification helper by defining object_from_id(global_id, ctx) & type_from_object(object). Then, assign the result to Schema#node_identification so that it can be used for query execution.

For example, define a node identification helper:

NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
  # Given a UUID & the query context,
  # return the corresponding application object
  object_from_id -> (id, ctx) do
    type_name, id = NodeIdentification.from_global_id(id)
    # "Post" -> Post.find(id)
    Object.const_get(type_name).find(id)
  end

  # Given an application object,
  # return a GraphQL ObjectType to expose that object
  type_from_object -> (object) do
    if object.is_a?(Post)
      PostType
    else
      CommentType
    end
  end
end

Then assign it to the schema:

MySchema = GraphQL::Schema.new(...)
# Assign your node identification helper:
MySchema.node_identification = NodeIdentification
UUID fields

ObjectTypes in your schema should implement NodeIdentification.interface with the global_id_field helper, for example:

PostType = GraphQL::ObjectType.define do
  name "Post"
  interfaces [NodeIdentification.interface]
  # `id` exposes the UUID
  global_id_field :id

  # ...
end
node field (find-by-UUID)

You should also add a field to your root query type for Relay to re-fetch objects:

QueryType = GraphQL::ObjectType.define do
  name "Query"
  # Used by Relay to lookup objects by UUID:
  field :node, field: NodeIdentification.field

  # ...
end
Custom UUID Generation

By default, graphql-relay uses Base64.strict_encode64 to generate opaque global ids. You can modify this behavior by providing two configurations. They work together to encode and decode ids:

NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
  # ...

  # Return a string for re-fetching this object
  to_global_id -> (type_name, id) {
    "#{type_name}/#{id}"
  }

  # Based on the incoming string, extract the type_name and id
  from_global_id -> (global_id) {
    id_parts  = global_id.split("/")
    type_name = id_parts[0]
    id        = id_parts[1]
    # Return *both*:
    [type_name, id]
  }
end

# ...

MySchema.node_identification = NodeIdentification

graphql-relay will use those procs for interacting with global ids.

Connections

Connections provide pagination and pageInfo for Arrays, ActiveRecord::Relations or Sequel::Datasets.

Connection fields

To define a connection field, use the connection helper. For a return type, get a type's .connection_type. For example:

PostType = GraphQL::ObjectType.define do
  # `comments` field returns a CommentsConnection:
  connection :comments, CommentType.connection_type
  # To avoid circular dependencies, wrap the return type in a proc:
  connection :similarPosts, -> { PostType.connection_type }

  # ...
end

You can also define custom arguments and a custom resolve function for connections, just like other fields:

connection :featured_comments, CommentType.connection_type do
  # Use a name to disambiguate this from `CommentType.connection_type`
  name "CommentConnectionWithSince"

  # Add an argument:
  argument :since, types.String

  # Return an Array or ActiveRecord::Relation
  resolve -> (post, args, ctx) {
    comments = post.comments.featured

    if args[:since]
      comments = comments.where("created_at >= ", since)
    end

    comments
  }
end
Maximum Page Size

You can limit the number of results with max_page_size::

connection :featured_comments, CommentType.connection_type, max_page_size: 50
Connection types

You can customize a connection type with .define_connection:

PostConnectionWithTotalCountType = PostType.define_connection do
  field :totalCount do
    type types.Int
    # `obj` is the Connection, `obj.object` is the collection of Posts
    resolve -> (obj, args, ctx) { obj.object.count }
  end
end

Now, you can use PostConnectionWithTotalCountType to define a connection with the "totalCount" field:

AuthorType = GraphQL::ObjectType.define do
  # Use the custom connection type:
  connection :posts, PostConnectionWithTotalCountType
end
Custom edge types

If you need custom fields on edges, you can define an edge type and pass it to a connection:

# Person => Membership => Team
MembershipSinceEdgeType = BaseType.define_edge do
  name "MembershipSinceEdge"
  field :memberSince, types.Int, "The date that this person joined this team" do
    resolve -> (obj, args, ctx) {
      obj # => GraphQL::Relay::Edge instnce
      person = obj.parent
      team = obj.node
      membership = Membership.where(person: person, team: team).first
      membership.created_at.to_i
    }
  end
end

Then, pass the edge type when defining the connection type:

TeamMembershipsConnectionType = TeamType.define_connection(edge_type: MembershipSinceEdgeType) do
  # Use a name so it doesn't conflict with "TeamConnection"
  name "TeamMembershipsConnection"
end

Now, you can query custom fields on the edge:

{
  me {
    teams {
      edge {
        memberSince     # <= Here's your custom field
        node {
          teamName: name
        }
      }
    }
  }
}
Custom Edge classes

For more robust custom edges, you can define a custom edge class. It will be obj in the edge type's resolve function. For example, to define a membership edge:

# Make sure to familiarize yourself with GraphQL::Relay::Edge --
# you have to avoid naming conflicts here!
class MembershipSinceEdge < GraphQL::Relay::Edge
  # Cache `membership` to avoid multiple DB queries
  def membership
    @membership ||= begin
      # "parent" and "node" are passed in from the surrounding Connection,
      # See `Edge#initialize` for details
      person = self.parent
      team = self.node
      Membership.where(person: person, team: team).first
    end
  end

  def member_since
    membership.created_at.to_i
  end

  def leader?
    membership.leader?
  end
end

Then, hook it up with custom edge type and custom connection type:

# Person => Membership => Team
MembershipSinceEdgeType = BaseType.define_edge do
  name "MembershipSinceEdge"
  field :memberSince, types.Int, "The date that this person joined this team", property: :member_since
  field :isPrimary, types.Boolean, "Is this person the team leader?". property: :primary?
  end
end

TeamMembershipsConnectionType = TeamType.define_connection(
    edge_class: MembershipSinceEdge,
    edge_type: MembershipSinceEdgeType,
  ) do
  # Use a name so it doesn't conflict with "TeamConnection"
  name "TeamMembershipsConnection"
end
Connection objects

Maybe you need to make a connection object yourself (for example, to return a connection type from a mutation). You can create a connection object like this:

items = [...]     # your collection objects
args = {}         # stub out arguments for this connection object
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
connection_class.new(items, args)

.connection_for_items will return RelationConnection or ArrayConnection depending on items, then you can make a new connection

Custom connections

You can define a custom connection class and add it to GraphQL::Relay.

First, define the custom connection:

class SetConnection < BaseConnection
  # derive a cursor from `item`
  def cursor_from_node(item)
    # ...
  end

  private
  # apply `#first` & `#last` to limit results
  def paged_nodes
    # ...
  end

  # apply cursor, order, filters, etc
  # to get a subset of matching objects
  def sliced_nodes
    # ...
  end
end

Then, register the new connection with GraphQL::Relay::BaseConnection:

# When exposing a `Set`, use `SetConnection`:
GraphQL::Relay::BaseConnection.register_connection_implementation(Set, SetConnection)

At runtime, GraphQL::Relay will use SetConnection to expose Sets.

Creating connection fields by hand

If you need lower-level access to Connection fields, you can create them programmatically. Given a GraphQL::Field which returns a collection of items, you can turn it into a connection field with ConnectionField.create.

For example, to wrap a field with a connection field:

field = GraphQL::Field.new
# ... define the field
connection_field = GraphQL::Relay::ConnectionField.create(field)

Mutations

Mutations allow Relay to mutate your system. They conform to a strict API which makes them predictable to the client.

Mutation root

To add mutations to your GraphQL schema, define a mutation type and pass it to your schema:

# Define the mutation type
MutationType = GraphQL::ObjectType.define do
  name "Mutation"
  # ...
end

# and pass it to the schema
MySchema = GraphQL::Schema.new(
  query: QueryType,
  mutation: MutationType
)

Like QueryType, MutationType is a root of the schema.

Mutation fields

Members of MutationType are mutation fields. For GraphQL in general, mutation fields are identical to query fields except that they have side-effects (which mutate application state, eg, update the database).

For Relay-compliant GraphQL, a mutation field must comply to a strict API. GraphQL::Relay includes a mutation definition helper (see below) to make it simple.

After defining a mutation (see below), add it to your mutation type:

MutationType = GraphQL::ObjectType.define do
  name "Mutation"
  # Add the mutation's derived field to the mutation type
  field :addComment, field: AddCommentMutation.field
  # ...
end

Relay mutations

To define a mutation, use GraphQL::Relay::Mutation.define. Inside the block, you should configure:

  • name, which will name the mutation field & derived types
  • input_fields, which will be applied to the derived InputObjectType
  • return_fields, which will be applied to the derived ObjectType
  • resolve(-> (inputs, ctx) { ... }), the mutation which will actually happen

For example:

AddCommentMutation = GraphQL::Relay::Mutation.define do
  # Used to name derived types:
  name "AddComment"

  # Accessible from `input` in the resolve function:
  input_field :postId, !types.ID
  input_field :authorId, !types.ID
  input_field :content, !types.String

  # The result has access to these fields,
  # resolve must return a hash with these keys
  return_field :post, PostType
  return_field :comment, CommentType

  # The resolve proc is where you alter the system state.
  resolve -> (inputs, ctx) {
    post = Post.find(inputs[:postId])
    comment = post.comments.create!(author_id: inputs[:authorId], content: inputs[:content])

    {comment: comment, post: post}
  }
end


Under the hood, GraphQL creates:

  • A field for your schema's mutation root
  • A derived InputObjectType for input values
  • A derived ObjectType for return values

The resolve proc:

  • Takes inputs, which is a hash whose keys are the ones defined by input_field
  • Takes ctx, which is the query context you passed with the context: keyword
  • Must return a hash with keys matching your defined return_fields

Tutorials

Todo

  • GlobalNodeIdentification.to_global_id should receive the type name and object, not id. (Or, maintain the "type_name, id in, type_name, id out" pattern?)
  • Reduce duplication in ArrayConnection / RelationConnection
  • Improve API for creating edges (better RANGE_ADD support)
  • If the new edge isn't a member of the connection's objects, raise a nice error
  • Rename Connection#object => Connection#collection with deprecation

More Resources

FAQs

Package last updated on 21 Jul 2016

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc