Skip to content

desofto/apiql

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

APIQL

Implementation of the API language similar to GraphQL for Ruby on Rails.

It compiles requests into Hashes for faster rendering.

It automatically detects nested entities and eager-loads them for faster DB access!

In controller or Grape API endpoint, handler of POST /apiql method (or any other, see below APIQL.endpoint):

def apiql
  schema = APIQL.cache(params)
  APIQL.new(self, :session, :current_user, :params).render(schema)
end

variables session, current_user and params (you can list any you need in your presenters/responders) will be stored into context you can use in presenters and handlers

Define presenters for your models:

class User < ApplicationRecord
  after_commit { APIQL.drop_cache(self) }

  class Entity < ::APIQL::Entity
    attributes :full_name, :email, :token, :role, :roles # any attributes, methods or associations

    def token # if defined, method will be called instead of attribute
      object.token if object == current_user # directly access to current_user from context
    end

    def roles
      APIQL.cacheable([object, roles], :roles, expires_in: 3600) do
        roles.pluck(:name).join(', ')
      end
    end

    class << self
      def me
        authorize! :show, ::User

        current_user
      end

      def authenticate(email, password)
        user = ::User.find_by(email)
        user.authenticate(password)

        user
      end

      def logout
        current_user&.logout

        :ok
      end
    end
  end

  has_many :roles

  ...
end

class Role < ApplicationRecord
  after_commit { APIQL.drop_cache(self) }

  class Entity < ::APIQL::Entity
    attributes :id, :name
  end
end

JS:

assets/javascripts/application.js:

//= require apiql
APIQL.endpoint = "/apiql"
// function apiql(schema, params = {}, form = null) -- schema is cached, so entire request is passed only for first time, later - short hashes only

authenticate(email, password) {
  apiql(`
    User.logout()

    User.authenticate(email, password) {
      token
    }

    User.me {
      email full_name role token

      roles {
        id title
      }
   }
  `, {
    email: email, // these vars will be passed into methods on the server side
    password: password
  })
  .then(response => { // response contains results of called methods
    let user = response.me
  })
}

logout() {
  apiql(`
    User.logout
  `)
  .then(response => {
  })
}

you can call methods on entities:

  apiql(`
    User.authenticate(email, password) {
      token
    }

    user: User.me.reload {
      email full_name role token

      roles(filter) {
        id title
      }

      contacts.primary {
        full_name
        email
      }
   }
  `, {
    email: email,
    filter: 'all',
    password: password
  })
  .then(response => {
    let user = response.user
  })
}

You can use initializer like this for authorization using cancancan gem:

config/initializers/apiql.rb:

class APIQL
  class Context
    def authorize!(*args)
      ability.authorize!(*args)
    end

    private

    def ability
      @ability ||= ::Ability::Factory.build_ability_for(current_user)
    end
  end
end

CRUD methods available for all models:

  apiql(`
    User.create(user)
  `, {
    user: {
      email: this.email,
      full_name: this.full_name
    }
  })
  .then(response => {
    ...
  })

  apiql(`
    User.find(id) {
      id email full_name
    }
  `, {
    id: this.id
  })
  .then(response => {
    ...
  })

  apiql(`
    User.all {
      id email full_name
    }
  `)
  .then(response => {
    ...
  })

  apiql(`
    User.update(id, user)
  `, {
    id: this.id,
    user: {
      email: this.email,
      full_name: this.full_name
    }
  })
  .then(response => {
    ...
  })

  apiql(`
    User.destroy(id)
  `, {
    id: this.id
  })
  .then(response => {
    ...
  })

or mount methods from external modules:

class APIQL < ::APIQL
  mount ::Services::User # all methods could be called with "user" prefix like "user.logout()"
  mount ::Services::Common, as: '' # all methods could be called without prefixes
  mount ::Services::Employer, as: 'dashboard" # all methods could be called with specified prefix
end

About

Implementation of the API language similar to GraphQL for Ruby on Rails

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published