strongman 1.0.0 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/strongman/version.rb +1 -1
  3. data/lib/wimp.rb +180 -0
  4. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 329a7cf887ef3a484d31e6c1f943d86857d045cf33daa6862e8c4720366d2273
4
- data.tar.gz: 1f4f60aebb814692098f07d5f1b20d520ebd7d6ad897dfec1b541f60b6f4efbc
3
+ metadata.gz: 07d29577fed62c5f82058d27afc01a59454115031a4b421fd932f8dc03689655
4
+ data.tar.gz: c85cb8d1d63b3889096e856160a31082dd10a8342ae8c9c3a5d89252b8c1cf78
5
5
  SHA512:
6
- metadata.gz: 1ca56b74b4bec3f25cfcbd51b5aea8d7eb75725d03ee51adb3e43dd00c01925eeb219b4071c204a61fc7ecad08882410bc466fc80a26bc8de90d70445f8993f2
7
- data.tar.gz: 2a986b767a76db4e1a5a087a0e05d9d9f8b632de1c6fd0288e622d2ebd25d926aa897d55ac510e45bf918649e9214611a38cbe895641eea270b1d5dcfcc49372
6
+ metadata.gz: 5760f359bfa016ec0eb414264418fbefbaa49381f85e8dd35fc33df4d31f35bab751d454b341bfb6684ebd943a75100d826c02551e6fb53697c74cced425c4c1
7
+ data.tar.gz: 79fe0d462a995d9d58d7f152519730921d80c4a816aa5054149665a2b263c7361cee8523fc0db231819fe6f4eca8eb07e4ca9247860df57e5875544f86d92324
@@ -1,4 +1,4 @@
1
1
  class Strongman
2
2
  # @!visibility private
3
- VERSION = "1.0.0".freeze
3
+ VERSION = "1.0.5".freeze
4
4
  end
@@ -0,0 +1,180 @@
1
+ require 'concurrent'
2
+
3
+ class DelayedResult
4
+ def initialize(&resolver)
5
+ @resolver = resolver
6
+ end
7
+
8
+ def then(&block)
9
+ DelayedResult.new do
10
+ block.(value!)
11
+ end
12
+ end
13
+
14
+ def self.zip(*results, &block)
15
+ DelayedResult.new do
16
+ results = results.map(&:value!)
17
+ block.(*results)
18
+ end
19
+ end
20
+
21
+ def value!
22
+ @value ||= @resolver.().yield_self do |val|
23
+ if val&.is_a?(DelayedResult)
24
+ val.value!
25
+ else
26
+ val
27
+ end
28
+ end
29
+ end
30
+
31
+ def value
32
+ value!
33
+ end
34
+ end
35
+
36
+ class Wimp
37
+ class NoCache
38
+ def compute_if_absent(_key)
39
+ yield
40
+ end
41
+ end
42
+
43
+ class Batch
44
+ attr_accessor :name
45
+ attr_accessor :fulfilled
46
+
47
+ def initialize(loader_block, name: nil, max_batch_size: Float::INFINITY)
48
+ @name = name
49
+ @queue = Concurrent::Array.new
50
+ @lock = Concurrent::ReadWriteLock.new
51
+ @loader_block = loader_block
52
+ @max_batch_size = max_batch_size
53
+ @fulfilled = false
54
+ @results = nil
55
+ end
56
+
57
+ def queue(key)
58
+ @queue << key
59
+
60
+ DelayedResult.new do
61
+ results = if @fulfilled
62
+ @lock.with_read_lock do
63
+ @results
64
+ end
65
+ else
66
+ @lock.with_write_lock do
67
+ if @fulfilled
68
+ @results
69
+ else
70
+ @fulfilled = true
71
+ r = @loader_block.(@queue)
72
+ @results = if r.is_a?(DelayedResult)
73
+ normalize_results(r.value!)
74
+ else
75
+ normalize_results(r)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ unless results.key?(key)
82
+ raise StandardError, "Batch loader didn't resolve a key: #{key}. Resolved keys: #{results.keys}"
83
+ end
84
+
85
+ results[key]
86
+ end
87
+ end
88
+
89
+ def fulfilled?
90
+ @fulfilled
91
+ end
92
+
93
+ private
94
+
95
+ def normalize_results(results)
96
+ unless results.is_a?(Array) || results.is_a?(Hash)
97
+ raise TypeError, "Batch loader must return an Array or Hash, but returned: #{results.class.name}"
98
+ end
99
+
100
+ if @queue.size != results.size
101
+ raise StandardError, "Batch loader must be instantiated with function that returns Array or Hash " \
102
+ "of the same size as provided to it Array of keys" \
103
+ "\n\nProvided keys:\n#{@queue}" \
104
+ "\n\nReturned values:\n#{results}"
105
+ end
106
+
107
+ if results.is_a?(Array)
108
+ Hash[@queue.zip(results)]
109
+ elsif results.is_a?(Hash)
110
+ results
111
+ end
112
+ end
113
+ end
114
+
115
+ attr_accessor :cache
116
+
117
+ def initialize(**options, &block)
118
+ unless block_given?
119
+ raise TypeError, "Dataloader must be constructed with a block which accepts " \
120
+ "Array and returns either Array or Hash of the same size (or Promise)"
121
+ end
122
+
123
+ @name = options.delete(:name)
124
+ @cache = if options.has_key?(:cache)
125
+ options.delete(:cache) || NoCache.new
126
+ else
127
+ Concurrent::Map.new
128
+ end
129
+ @max_batch_size = options.fetch(:max_batch_size, Float::INFINITY)
130
+
131
+ @interceptor = options.delete(:interceptor) || -> (n) {
132
+ -> (ids) {
133
+ n.call(ids)
134
+ }
135
+ }
136
+
137
+ @loader_block = @interceptor.call(block)
138
+ end
139
+
140
+ def load(key)
141
+ if key.nil?
142
+ raise TypeError, "#load must be called with a key, but got: nil"
143
+ end
144
+
145
+ result = retrieve_from_cache(key) do
146
+ batch.queue(key)
147
+ end
148
+
149
+ if result.is_a?(DelayedResult)
150
+ result
151
+ else
152
+ DelayedResult.new { result }
153
+ end
154
+ end
155
+
156
+ def load_many(keys)
157
+ unless keys.is_a?(Array)
158
+ raise TypeError, "#load_many must be called with an Array, but got: #{keys.class.name}"
159
+ end
160
+
161
+ delayed_results = keys.map(&method(:load))
162
+ DelayedResult.new do
163
+ delayed_results.map(&:value!)
164
+ end
165
+ end
166
+
167
+ def batch
168
+ if @batch.nil? || @batch.fulfilled?
169
+ @batch = Batch.new(@loader_block, name: @name, max_batch_size: @max_batch_size)
170
+ else
171
+ @batch
172
+ end
173
+ end
174
+
175
+ def retrieve_from_cache(key)
176
+ @cache.compute_if_absent(key) do
177
+ yield
178
+ end
179
+ end
180
+ end
metadata CHANGED
@@ -1,22 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strongman
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Caleb Land
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-19 00:00:00.000000000 Z
11
+ date: 2020-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: concurrent-ruby
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
18
  version: '1.1'
19
+ name: concurrent-ruby
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -36,6 +36,7 @@ files:
36
36
  - README.md
37
37
  - lib/strongman.rb
38
38
  - lib/strongman/version.rb
39
+ - lib/wimp.rb
39
40
  homepage: https://github.com/caleb/strongman
40
41
  licenses:
41
42
  - MIT
@@ -55,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
56
  - !ruby/object:Gem::Version
56
57
  version: '0'
57
58
  requirements: []
58
- rubygems_version: 3.1.2
59
+ rubygems_version: 3.0.6
59
60
  signing_key:
60
61
  specification_version: 4
61
62
  summary: Batch data loading, works great with graphql