-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathbatch_loader.rb
More file actions
171 lines (133 loc) · 4.32 KB
/
Copy pathbatch_loader.rb
File metadata and controls
171 lines (133 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# frozen_string_literal: true
require "set"
require_relative "./batch_loader/version"
require_relative "./batch_loader/executor_proxy"
require_relative "./batch_loader/middleware"
require_relative "./batch_loader/graphql"
class BatchLoader
IMPLEMENTED_INSTANCE_METHODS = %i[object_id __id__ __send__ singleton_method_added __sync respond_to? batch inspect lazy_eval __accepting_lazy_chain?].freeze
REPLACABLE_INSTANCE_METHODS = %i[batch inspect lazy_eval].freeze
LEFT_INSTANCE_METHODS = (IMPLEMENTED_INSTANCE_METHODS - REPLACABLE_INSTANCE_METHODS).freeze
NoBatchError = Class.new(StandardError)
def self.for(item)
new(item: item)
end
def initialize(item:, executor_proxy: nil)
@item = item
@__executor_proxy = executor_proxy
end
def batch(default_value: nil, cache: true, replace_methods: nil, key: nil, &batch_block)
@default_value = default_value
@cache = cache
@replace_methods = replace_methods.nil? ? cache : replace_methods
@key = key
@batch_block = batch_block
__executor_proxy.add(item: @item)
__singleton_class.class_eval { undef_method(:batch) }
self
end
def lazy_eval
@accepting_lazy_chain = true
@lazy_chain = []
__singleton_class.class_eval { undef_method(:lazy_eval) }
self
end
def respond_to?(method_name, include_private = false)
return true if LEFT_INSTANCE_METHODS.include?(method_name)
__loaded_value.respond_to?(method_name, include_private)
end
def inspect
"#<BatchLoader:0x#{(object_id << 1)}>"
end
def __sync
return @loaded_value if @synced
@accepting_lazy_chain = false
__ensure_batched
@loaded_value = __executor_proxy.loaded_value(item: @item)
(@lazy_chain || []).each do |method_name, args, kwargs, block|
@loaded_value = @loaded_value.public_send(method_name, *args, **kwargs, &block)
end
if @cache
@synced = true
else
__purge_cache
end
@loaded_value
end
def __accepting_lazy_chain?
@accepting_lazy_chain
end
private
def __loaded_value
result = __sync!
@cache ? @loaded_value : result
end
def method_missing(method_name, *args, **kwargs, &block)
return __sync!.public_send(method_name, *args, **kwargs, &block) if !__accepting_lazy_chain?
return __sync! if method_name == :force
if method_name == :eager
@accepting_lazy_chain = false
else
@lazy_chain << [method_name, args, kwargs, block]
end
self
end
def __sync!
loaded_value = __sync
if @replace_methods
__replace_with!(loaded_value)
else
loaded_value
end
end
def __ensure_batched
return if __executor_proxy.value_loaded?(item: @item)
items = __executor_proxy.list_items
loader = __loader
args = {default_value: @default_value, cache: @cache, replace_methods: @replace_methods, key: @key}
@batch_block.call(items, loader, args)
items.each do |item|
next if __executor_proxy.value_loaded?(item: item)
loader.call(item, @default_value)
end
__executor_proxy.delete(items: items)
end
def __loader
mutex = Mutex.new
-> (item, value = (no_value = true; nil), &block) do
if no_value && !block
raise ArgumentError, "Please pass a value or a block"
elsif block && !no_value
raise ArgumentError, "Please pass a value or a block, not both"
end
mutex.synchronize do
next_value = block ? block.call(__executor_proxy.loaded_value(item: item)) : value
__executor_proxy.load(item: item, value: next_value)
end
end
end
def __singleton_class
class << self ; self ; end
end
def __replace_with!(value)
__singleton_class.class_eval do
(value.methods - LEFT_INSTANCE_METHODS).each do |method_name|
define_method(method_name) do |*args, **kwargs, &block|
value.public_send(method_name, *args, **kwargs, &block)
end
end
end
self
end
def __purge_cache
__executor_proxy.unload_value(item: @item)
__executor_proxy.add(item: @item)
end
def __executor_proxy
@__executor_proxy ||= begin
raise NoBatchError.new("Please provide a batch block first") unless @batch_block
BatchLoader::ExecutorProxy.new(@default_value, @key, &@batch_block)
end
end
(instance_methods - IMPLEMENTED_INSTANCE_METHODS).each { |method_name| undef_method(method_name) }
end