Prerequisites

Graphoid depends on the GraphQL implementation for Ruby. To have that gem installed you can visit graphql-ruby to install it or:

Add graphql to your Gemfile
gem "graphql"
$ bundle install
$ rails generate graphql:install

You can optionally install GraphQL Playground, a visual client to start querying the server

GraphQL Playground
$ brew cask install graphql-playground

Installation

Add graphoid to your Gemfile
gem "graphoid"
$ bundle install

Database

Graphoid works trasparently with ActiveRecord or Mongoid

Create the configuration file config/initializers/graphoid.rb

If you use ActiveRecord
Graphoid.configure do |config|
  config.driver = :active_record
end
If you use Mongoid
Graphoid.configure do |config|
  config.driver = :mongoid
end

Usage

By adding the concerns, you can tell graphoid which of your models will be visible in the API.
You can include either the Queries or Mutations models or both.
The Queries concern exposes read access by exposing query resolvers in the API.
The Mutations concern exposes write access by exposing create/update/delete resolvers in the API.

class Person < ApplicationRecord
  include Graphoid::Queries
  include Graphoid::Mutations

  # columns are 
  # id: integer
  # name: string
  # money: float
  # feet_size: string

  has_many :accounts
  has_one :horse
  belongs_to :country
end

If you want to play with the autogenerated API at this point you can install GraphiQL
Run your server and visit http://localhost:3000/graphiql

There is a live demo where you can execute the queries of this document

Live Demo

Queries

The queries to fetch one or many records from the rails model will be autogerated.
You can make use of autogenerated filters too, see filters in the filters section
Model associations (has_many, belongs_to, etc) can be fetched and filtered too within the same query.

Query a single object by ID
query {
  person(id: 1) {
    id
    name
    feetSize
    accounts {
      id
    }
  }
}

Attribute Names

All of the attributes that were using snake case syntax (snake_case) in the ruby models are exposed in the queries/mutations as camel cased (camelCase).

Query the first match
query {
  person(where: { name: "max" }) {
    id
    name
    horse {
      id
      name
    }
  }
}
Query many records
query {
  people(where: { name_contains: "max" }) {
    id
    name
    country {
      id
      name
    }
  }
}

Mutations

Mutations to create, update and delete one or many records will be autogerated.
You can make use of autogenerated filters too in order to select what to update or delete.

Create

Create a single record
mutation {
  createPerson(data: { name: "bob" }) {
    id
  }
}
Query many records
mutation {
  createManyPeople(data: [{ name: "max" }, { name: "bob" }]) {
    id
  }
}
Create related records
mutation {
  createPerson(data: { name: "max", horse: { name: "unicorn" } }) {
    id
  }
}

Update

Update a single record
mutation {
  updatePerson(id: 1, data: { name: "bob" }) {
    id
  }
}
Update many records
mutation {
  updateManyPeople(where: { FILTER }, data: [{ name: "max" }]) {
    id
  }
}

Delete

Delete a single record
mutation {
  deletePerson(id: 1) {
    id
  }
}
Delete many records
mutation {
  deleteManyPeople(where: { FILTER }) {
    id
  }
}

Filters

You can make use of the autogenerated filters to execute and get the result you want by properly applying them.
The available filters you can use are the ones listed below:

Equal

The field is equal to the value passed as parameter

Get all people named bob

query {
  people(where: { name: "bob" }) {
    id
    name
  }
}

Not Equal (_not)

The field is different from the value passed as parameter

Get all people who are not named bob

query {
  people(where: { name_not: "bob" }) {
    id
    name
  }
}

Contains (_contains)

The field contains the value passed as parameter

Get all people that has a "b" on their name

query {
  people(where: { name_contains: "b" }) {
    id
    name
  }
}

Regex (_regex)

The field matches the regex passed as parameter

Get all people that have a name with a letter between "a" and "g"

query {
  people(where: { name_regex: "[a-g]/i" }) {
    id
    name
  }
}

Mongoid Only

The Regex filter is only available if you are using mongoid.

In (_in)

The field is one of the values of the array

Get all people who are named bob or maxi

query {
  people(where: { name_in: ["max", "bob"] }) {
    id
    name
    money
  }
}

Not In (_nin)

The field is NOT one of the values of the array

Get all people who are not named bob or maxi

query {
  people(where: { name_nin: ["max", "bob"] }) {
    id
    name
  }
}

GTE (_gte)

The field is greater than or equal than the value

Get all people with $30.30 or more money

query {
  people(where: { money_gte: 30.3 }) {
    id
    name
    money
  }
}
LTE (_lte): Less Than or Equal (<=)
GT (_gt): Greater Than (>)
LT (_lt): Less Than (<)

AND

Combines different filters in a logical AND operation.

Get all people named "max" and with more than $30

query {
  people(where: { 
    AND: [
      { name: "max" },
      { money_gte: 30 }
    ]
  }) {
    id
    name
    money
  }
}
The same result is obtained by passing an object with many elements.
query {
  people(where: { name: "max", money_gte: 30 }) {
    id
    name
    money
  }
}

OR

Combines different filters in a logical OR operation.

Get all people named "max" or with more than $30

query {
  people(where: { 
    OR: [
      { name: "max" },
      { money_gte: 30 }
    ]
  }) {
    id
    name
    money
  }
}

Associations

Graphoid will automatically inpect the relations that your model has and provide the corresponding API to query information on those relations too

Has One | Belongs To

Fetch all people with a white horse

query {
  people(where: { horse: { color: "white" } }){
    id
    name
    horse {
      id
      color
    }
  }
}

Has Many | Has And Belongs To Many

_some : fetch all people with a at least 1 google account

query {
  people(where: { accounts_some: { name: "google" } }){
    id
    name
    accounts {
      id
      name
    }
  }
}

_none : fetch all people without a google account

query {
  people(where: { accounts_none: { name: "google" } }){
    id
    name
    accounts {
      id
      name
    }
  }
}

_every: fetch all people that has only google accounts and no other accounts

query {
  people(where: { accounts_every: { name: "google" } }){
    id
    name
    accounts {
      id
      name
    }
  }
}

Nested Filters

You can make use of the same filters described above to filter model associations

Fetch all people with all their google accounts. If a person had a facebook account, that account wont appear.
It does not affect the person model, only the accounts.

query {
  people(){
    id
    name
    accounts(where: { name: "google" }) {
      id
      name
    }
  }
}

Order

Graphoid provides the "order" feature by any field that is present on the model

Fetch all people ordered descendently by name

query {
  people(order: { name: DESC }){
    id
    name
  }
}

Attributes

The Graphield concern can be used to create new virtual attributes .
A virtual attribute always needs a method with the same name, in order to be resolved.

class Person
  include Graphoid::Graphield

  graphield :full_name, String

  def full_name
    "#{first_name} #{last_name}"
  end
end

After defining how full_name is resolved you can use it on your queries.

query {
  people(){
    id
    name
    fullName
  }
}

Permissions

Graphield concern is also used to forbid access to existing fields.
One use would be not to expose the password field.

class Person
  include Graphoid::Graphield

  graphorbid :password # will not be exposed in the API
end

Field Level Permission is Comming !

I am working very hard to include a dynamic field level permission system that is going to be highly configurable.

Aggregations

Meta queries are used to get data about the data.

Count

Counts all the records that matches the filter
query {
  xMetaPeople(where: { name: "max" }) {
    count
  }
}

Sum

Sums all the records that matches the filter.
query {
  xMetaPeople(where: { name: "max" }) {
    id
    name
    sum(data: ["money"])
  }
}

Sum Is Under Development !

Aggregations is a feature that is still under development. Ideas for the syntax or implementation are very welcomed.