Class: Graphiti::Scope

Inherits:
Object show all
Defined in:
lib/graphiti/scope.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, resource, query, opts = {}) ⇒ Scope

Returns a new instance of Scope.



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

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

Instance Attribute Details

#objectObject

Returns the value of attribute object.



3
4
5
# File 'lib/graphiti/scope.rb', line 3

def object
  @object
end

#paginationObject (readonly)

Returns the value of attribute pagination.



4
5
6
# File 'lib/graphiti/scope.rb', line 4

def pagination
  @pagination
end

#unpaginated_objectObject

Returns the value of attribute unpaginated_object.



3
4
5
# File 'lib/graphiti/scope.rb', line 3

def unpaginated_object
  @unpaginated_object
end

Class Method Details

.global_thread_pool_executorObject



24
25
26
# File 'lib/graphiti/scope.rb', line 24

def self.global_thread_pool_executor
  GLOBAL_THREAD_POOL_EXECUTOR.value!
end

.global_thread_pool_statsObject



28
29
30
31
32
# File 'lib/graphiti/scope.rb', line 28

def self.global_thread_pool_stats
  GLOBAL_THREAD_POOL_EXECUTOR_BROADCAST_STATS.each_with_object({}) do |key, memo|
    memo[key] = global_thread_pool_executor.send(key)
  end
end

.marking_pool_threadObject

Restores rather than clears because :caller_runs may have run the task on a request thread.



48
49
50
51
52
53
54
# File 'lib/graphiti/scope.rb', line 48

def self.marking_pool_thread
  previous = Thread.current[POOL_THREAD]
  Thread.current[POOL_THREAD] = true
  yield
ensure
  Thread.current[POOL_THREAD] = previous
end

.on_pool_thread?Boolean

TODO: move to Fiber once the floor is Ruby 3.2

Returns:

  • (Boolean)


43
44
45
# File 'lib/graphiti/scope.rb', line 43

def self.on_pool_thread?
  Thread.current[POOL_THREAD] == true
end

.resolve_synchronously?Boolean

A pool thread that waits on the pool deadlocks, since the task it waits for cannot start until the waiting thread frees its slot.

Returns:

  • (Boolean)


38
39
40
# File 'lib/graphiti/scope.rb', line 38

def self.resolve_synchronously?
  !Graphiti.config.concurrency || on_pool_thread?
end

Instance Method Details

#cache_keyObject



106
107
108
109
110
111
112
113
114
# File 'lib/graphiti/scope.rb', line 106

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_versionObject



116
117
118
119
120
121
122
123
124
# File 'lib/graphiti/scope.rb', line 116

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

#future_resolve(&blk) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/graphiti/scope.rb', line 88

def future_resolve(&blk)
  return Concurrent::Promises.fulfilled_future([], self.class.global_thread_pool_executor) if @query.zero_results?

  resolved = resolve_primary_data(&blk)
  sideloaded = @query.parents.any?
  close_adapter = Graphiti.config.concurrency && sideloaded
  if close_adapter
    @resource.adapter.close
  end

  future_resolve_sideloads(resolved)
    .then_on(self.class.global_thread_pool_executor, resolved) { resolved }
end

#parent_resourceObject



102
103
104
# File 'lib/graphiti/scope.rb', line 102

def parent_resource
  @resource
end

#resolve(&blk) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/graphiti/scope.rb', line 67

def resolve(&blk)
  # The caller blocks on .value! either way, so concurrency only benefits parallel sideloads
  # See https://github.com/graphiti-api/graphiti/issues/505
  if self.class.resolve_synchronously? || !applicable_sideloads?
    sync_resolve(&blk)
  else
    future_resolve(&blk).value!
  end
end

#resolve_sideloads(results) ⇒ Object



77
78
79
80
81
82
83
84
85
86
# File 'lib/graphiti/scope.rb', line 77

def resolve_sideloads(results)
  if self.class.resolve_synchronously?
    sync_resolve_sideloads(results)
  else
    future_resolve_sideloads(results).value!
  end

  # Never return the sideloads hash, a caller mutating it would mess up the cache key
  nil
end

#updated_atObject Also known as: last_modified_at



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/graphiti/scope.rb', line 126

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

  updated_time || Time.now
end