class APIQL

Attributes

context[R]

Public Class Methods

cache(params) click to toggle source
# File lib/apiql.rb, line 103
def cache(params)
  request_id = params[:apiql]
  request = params[:apiql_request]

  if request.present?
    request = compile(request)
    redis&.set("api-ql-cache-#{request_id}", request.to_json)
    @@cache[request_id] = request
    @@cache = {} if(@@cache.count > 1000)
  else
    request = @@cache[request_id]
    request ||= JSON.parse(redis.get("api-ql-cache-#{request_id}")) rescue nil
    raise CacheMissed unless request.present? && request.is_a?(::Array)
  end

  request
end
eager_loads(schema) click to toggle source
# File lib/apiql.rb, line 129
def eager_loads(schema)
  result = []

  schema&.each do |call|
    if call.is_a? Hash
      call.each do |function, sub_schema|
        next if function.include? '('
        function = function.split(':').last.strip if function.include? ':'
        function = function.split('.').first if function.include? '.'

        sub = eager_loads(sub_schema)
        if sub.present?
          result.push(function => sub)
        else
          result.push function
        end
      end
    end
  end

  result
end
mount(klass, as: nil) click to toggle source
# File lib/apiql.rb, line 89
def mount(klass, as: nil)
  as ||= klass.name.split('::').last.underscore
  as += '.' if as.present?

  klass.instance_methods(false).each do |method|
    klass.alias_method("#{as}#{method}", method)
    klass.remove_method(method) if as.present?
  end

  include klass
end
new(binder, *fields) click to toggle source
# File lib/apiql.rb, line 239
def initialize(binder, *fields)
  @context = ::APIQL::Context.new(binder, *fields)
  @context.inject_delegators(self)
end
simple_class?(value) click to toggle source
# File lib/apiql.rb, line 121
def simple_class?(value)
  value.nil? ||
    value.is_a?(TrueClass) || value.is_a?(FalseClass) ||
    value.is_a?(Symbol) || value.is_a?(String) ||
    value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(BigDecimal) ||
    value.is_a?(Hash)
end

Private Class Methods

compile(schema) click to toggle source
# File lib/apiql.rb, line 163
def compile(schema)
  result = []

  ptr = result
  pool = []

  push_key = lambda do |key, subtree = false|
    ptr.each_with_index do |e, index|
      if e.is_a?(::Hash)
        return e[key] if e[key]
      elsif e == key
        if subtree
          ptr[index] = { key => (p = []) }
          return p
        end
        return
      end
    end

    if subtree
      ptr.push(key => (p = []))
      return p
    else
      ptr.push(key)
    end
  end

  last_key = nil

  while schema.present? do
    if reg = schema.match(/\A\s*\{(?<rest>.*)\z/m) # {
      schema = reg[:rest]

      pool.push(ptr)

      ptr = push_key.call(last_key, true)
    elsif reg = schema.match(/\A\s*\}(?<rest>.*)\z/m) # }
      schema = reg[:rest]

      ptr = pool.pop
    elsif pool.any?
      if reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s*)?(?<name>[\w\.]+)(\((?<params>.*?)\))?(?<rest>.*)\z/m)
        schema = reg[:rest]

        key = reg[:alias].present? ? "#{reg[:alias]}: #{reg[:name]}" : reg[:name]
        key += "(#{reg[:params]})" unless reg[:params].nil?

        push_key.call(key)

        last_key = key
      else
        raise Error, schema
      end
    elsif reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s*)?(?<name>[\w\.]+)(\((?<params>((\w+)(\s*\,\s*\w+)*))?\))?\s*\{(?<rest>.*)\z/m)
      schema = reg[:rest]

      key = "#{reg[:alias] || reg[:name]}: #{reg[:name]}(#{reg[:params]})"

      pool.push(ptr)

      ptr = push_key.call(key, true)
    elsif reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s*)?(?<name>[\w\.]+)(\((?<params>((\w+)(\s*\,\s*\w+)*))?\))?\s*\n?(?<rest>.*)\z/m)
      schema = reg[:rest]

      key = "#{reg[:alias] || reg[:name]}: #{reg[:name]}(#{reg[:params]})"

      push_key.call(key)
    else
      raise Error, schema
    end
  end

  result
end
redis() click to toggle source
# File lib/apiql.rb, line 154
def redis
  @redis ||=
    begin
      ::Redis.new(host: 'localhost')
    rescue
      nil
    end
end

Public Instance Methods

eager_load() click to toggle source
# File lib/apiql.rb, line 244
def eager_load
  result = @eager_load

  @eager_load = []

  result
end
render(schema) click to toggle source
# File lib/apiql.rb, line 252
def render(schema)
  result = {}

  schema.each do |call|
    if call.is_a? ::Hash
      call.each do |function, sub_schema|
        reg = function.match(/\A((?<alias>[\w\.\!]+):\s*)?(?<name>[\w\.\!]+)(\((?<params>.*?)\))?\z/)
        raise Error, function unless reg.present?

        name = reg[:alias] || reg[:name]
        function = reg[:name]
        params = @context.parse_params(reg[:params].presence)

        @eager_load = APIQL::eager_loads(sub_schema)
        data = public_send(function, *params)
        if @eager_load.present? && !data.is_a?(::Hash) && !data.is_a?(::Array)
          if data.respond_to?(:eager_load)
            data = data.includes(eager_load)
          elsif data.respond_to?(:id)
            data = data.class.includes(eager_load).find(data.id)
          end
        end

        if result[name].is_a? ::Hash
          result = result.deep_merge({
            name => @context.render_value(data, sub_schema)
          })
        else
          result[name] = @context.render_value(data, sub_schema)
        end
      end
    else
      reg = call.match(/\A((?<alias>[\w\.\!]+):\s*)?(?<name>[\w\.\!]+)(\((?<params>.*?)\))?\z/)
      raise Error, call unless reg.present?

      name = reg[:alias] || reg[:name]
      function = reg[:name]
      params = @context.parse_params(reg[:params].presence)

      @eager_load = []
      data = public_send(function, *params)
      if data.is_a? Array
        if data.any? { |item| !APIQL::simple_class?(item) }
          data = nil
        end
      elsif !APIQL::simple_class?(data)
        data = nil
      end

      if result[name].is_a? ::Hash
        result = result.deep_merge({
          name => data
        })
      else
        result[name] = data
      end
    end
  end

  result
end