class Graphiti::Scope

Attributes

object[RW]
pagination[R]
unpaginated_object[RW]

Public Class Methods

new(object, resource, query, opts = {}) click to toggle source
# File lib/graphiti/scope.rb, line 5
def initialize(object, resource, query, opts = {})
  @object = object
  @resource = resource
  @query = query
  @opts = opts

  @object = @resource.around_scoping(@object, @query.hash) { |scope|
    apply_scoping(scope, opts)
  }
end

Public Instance Methods

cache_key() click to toggle source
# File lib/graphiti/scope.rb, line 74
def cache_key
  # This is the combined cache key for the base query and the query for all sideloads
  # Changing the query will yield a different cache key

  cache_keys = sideload_resource_proxies.map { |proxy| proxy.try(:cache_key) }

  cache_keys << @object.try(:cache_key) # this is what calls into the ORM (ActiveRecord, most likely)
  ActiveSupport::Cache.expand_cache_key(cache_keys.flatten.compact)
end
cache_key_with_version() click to toggle source
# File lib/graphiti/scope.rb, line 84
def cache_key_with_version
  # This is the combined and versioned cache key for the base query and the query for all sideloads
  # If any returned model's updated_at changes, this key will change

  cache_keys = sideload_resource_proxies.map { |proxy| proxy.try(:cache_key_with_version) }

  cache_keys << @object.try(:cache_key_with_version) # this is what calls into ORM (ActiveRecord, most likely)
  ActiveSupport::Cache.expand_cache_key(cache_keys.flatten.compact)
end
last_modified_at()
Alias for: updated_at
parent_resource() click to toggle source
# File lib/graphiti/scope.rb, line 70
def parent_resource
  @resource
end
resolve() { |resolved| ... } click to toggle source
# File lib/graphiti/scope.rb, line 16
def resolve
  if @query.zero_results?
    []
  else
    resolved = broadcast_data { |payload|
      @object = @resource.before_resolve(@object, @query)
      payload[:results] = @resource.resolve(@object)
      payload[:results]
    }
    resolved.compact!
    assign_serializer(resolved)
    yield resolved if block_given?
    @opts[:after_resolve]&.call(resolved)
    resolve_sideloads(resolved) unless @query.sideloads.empty?
    resolved
  end
end
resolve_sideloads(results) click to toggle source
# File lib/graphiti/scope.rb, line 34
def resolve_sideloads(results)
  return if results == []

  concurrent = Graphiti.config.concurrency
  promises = []

  @query.sideloads.each_pair do |name, q|
    sideload = @resource.class.sideload(name)
    next if sideload.nil? || sideload.shared_remote?
    parent_resource = @resource
    graphiti_context = Graphiti.context
    resolve_sideload = -> {
      Graphiti.config.before_sideload&.call(graphiti_context)
      Graphiti.context = graphiti_context
      sideload.resolve(results, q, parent_resource)
      @resource.adapter.close if concurrent
    }
    if concurrent
      promises << Concurrent::Promise.execute(&resolve_sideload)
    else
      resolve_sideload.call
    end
  end

  if concurrent
    # Wait for all promises to finish
    sleep 0.01 until promises.all? { |p| p.fulfilled? || p.rejected? }
    # Re-raise the error with correct stacktrace
    # OPTION** to avoid failing here?? if so need serializable patch
    # to avoid loading data when association not loaded
    if (rejected = promises.find(&:rejected?))
      raise rejected.reason
    end
  end
end
updated_at() click to toggle source
# File lib/graphiti/scope.rb, line 94
def updated_at
  updated_time = nil
  begin
    updated_ats = sideload_resource_proxies.map(&:updated_at)
    updated_ats << @object.maximum(:updated_at)
    updated_time = updated_ats.compact.max
  rescue => e
    Graphiti.log(["error calculating last_modified_at for #{@resource.class}", :red])
    Graphiti.log(e)
  end

  return updated_time || Time.now
end
Also aliased as: last_modified_at

Private Instance Methods

add_scoping(key, scoping_class, opts, default = {}) click to toggle source
# File lib/graphiti/scope.rb, line 166
def add_scoping(key, scoping_class, opts, default = {})
  @object = scoping_class.new(@resource, @query.hash, @object, opts).apply
  @unpaginated_object = @object unless key == :paginate
end
apply_scoping(scope, opts) click to toggle source
# File lib/graphiti/scope.rb, line 152
def apply_scoping(scope, opts)
  @object = scope

  unless @resource.remote?
    opts[:default_paginate] = false unless @query.paginate?
    add_scoping(nil, Graphiti::Scoping::DefaultFilter, opts)
    add_scoping(:filter, Graphiti::Scoping::Filter, opts)
    add_scoping(:sort, Graphiti::Scoping::Sort, opts)
    add_scoping(:paginate, Graphiti::Scoping::Paginate, opts)
  end

  @object
end
assign_serializer(records) click to toggle source

Used to ensure the resource’s serializer is used Not one derived through the usual jsonapi-rb logic

# File lib/graphiti/scope.rb, line 146
def assign_serializer(records)
  records.each_with_index do |r, index|
    @resource.decorate_record(r, index)
  end
end
broadcast_data() { |payload| ... } click to toggle source
# File lib/graphiti/scope.rb, line 129
def broadcast_data
  opts = {
    resource: @resource,
    params: @opts[:params] || @query.params,
    sideload: @opts[:sideload],
    parent: @opts[:parent],
    action: @query.action
    # Set once data is resolved within block
    #   results: ...
  }
  Graphiti.broadcast(:resolve, opts) do |payload|
    yield payload
  end
end
sideload_resource_proxies() click to toggle source
# File lib/graphiti/scope.rb, line 111
def sideload_resource_proxies
  @sideload_resource_proxies ||= begin
    @object = @resource.before_resolve(@object, @query)
    results = @resource.resolve(@object)

    [].tap do |proxies|
      unless @query.sideloads.empty?
        @query.sideloads.each_pair do |name, q|
          sideload = @resource.class.sideload(name)
          next if sideload.nil? || sideload.shared_remote?

          proxies << sideload.build_resource_proxy(results, q, parent_resource)
        end
      end
    end.flatten
  end
end