Class: APIQL

Inherits:
Object
  • Object
show all
Defined in:
lib/apiql.rb

Defined Under Namespace

Modules: Rails Classes: CacheMissed, Context, Entity, Error, HashEntity

Constant Summary collapse

@@cache =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(binder, *fields) ⇒ APIQL

Returns a new instance of APIQL.



181
182
183
184
# File 'lib/apiql.rb', line 181

def initialize(binder, *fields)
  @context = ::APIQL::Context.new(binder, *fields)
  @context.inject_delegators(self)
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



20
21
22
# File 'lib/apiql.rb', line 20

def context
  @context
end

Class Method Details

.cache(params) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/apiql.rb', line 38

def cache(params)
  request_id = params[:apiql]
  request = params[:apiql_request]

  if request.present?
    request = compile(request)
    ::Rails.cache.write("api-ql-cache-#{request_id}", JSON.generate(request), expires_in: 31*24*60*60)
    @@cache[request_id] = request
    @@cache = {} if(@@cache.count > 1000)
  else
    request = @@cache[request_id]
    request ||= JSON.parse(::Rails.cache.fetch("api-ql-cache-#{request_id}")) rescue nil
    raise CacheMissed unless request.present? && request.is_a?(::Array)
  end

  request
end

.cacheable(dependencies, attr, expires_in: 31*24*60*60) ⇒ Object



56
57
58
59
60
61
62
63
64
65
# File 'lib/apiql.rb', line 56

def cacheable(dependencies, attr, expires_in: 31*24*60*60)
  dependencies = dependencies.flatten.map { |obj| [obj.class.name, obj.id] }
  name = ['api-ql-cache', dependencies, attr].flatten.join('-')
  begin
    raise if expires_in <= 0
    JSON.parse(::Rails.cache.fetch(name))
  rescue
    (block_given? ? yield : nil).tap { |data| expires_in > 0 && ::Rails.cache.write(name, JSON.generate(data), expires_in: expires_in) }
  end
end

.drop_cache(obj) ⇒ Object



67
68
69
70
# File 'lib/apiql.rb', line 67

def drop_cache(obj)
  return unless ::Rails.cache.respond_to? :delete_if
  ::Rails.cache.delete_if { |k, v| k =~ /api-ql.*-#{obj.class.name}-#{obj.id}-.*/ }
end

.eager_loads(schema) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/apiql.rb', line 80

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) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
# File 'lib/apiql.rb', line 24

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

.simple_class?(value) ⇒ Boolean

Returns:

  • (Boolean)


72
73
74
75
76
77
78
# File 'lib/apiql.rb', line 72

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

Instance Method Details

#eager_loadObject



186
187
188
189
190
191
192
# File 'lib/apiql.rb', line 186

def eager_load
  result = @eager_load

  @eager_load = []

  result
end

#render(schema) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/apiql.rb', line 194

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 = call_function(function, *params)

        if @eager_load.present? && !data.is_a?(::Hash) && !data.is_a?(::Array)
          if data.respond_to?(:includes)
            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 = call_function(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