attr-gather 1.1.0 → 1.3.0
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.
- checksums.yaml +4 -4
- data/.github/mergify.yml +4 -1
- data/.github/workflows/deploy.yml +1 -1
- data/.github/workflows/ruby.yml +3 -0
- data/.rubocop.yml +2 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +68 -57
- data/README.md +141 -4
- data/attr-gather.gemspec +2 -0
- data/examples/post_enhancer.rb +1 -1
- data/lib/attr/gather/aggregators.rb +4 -4
- data/lib/attr/gather/aggregators/base.rb +11 -9
- data/lib/attr/gather/aggregators/deep_merge.rb +6 -3
- data/lib/attr/gather/aggregators/shallow_merge.rb +2 -4
- data/lib/attr/gather/concerns/registrable.rb +2 -2
- data/lib/attr/gather/filters/contract.rb +1 -1
- data/lib/attr/gather/version.rb +1 -1
- data/lib/attr/gather/workflow/callable.rb +16 -22
- data/lib/attr/gather/workflow/dot_serializer.rb +1 -2
- data/lib/attr/gather/workflow/dsl.rb +76 -22
- data/lib/attr/gather/workflow/task.rb +15 -2
- data/lib/attr/gather/workflow/task_graph.rb +14 -2
- metadata +6 -9
- data/lib/attr/gather/workflow/async_task_executor.rb +0 -17
- data/lib/attr/gather/workflow/task_execution_result.rb +0 -58
- data/lib/attr/gather/workflow/task_executor.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edfd2d7479248348551d31676b45459f6f8279f6ca5fdf6ed00540c791de86bd
|
|
4
|
+
data.tar.gz: 977ac39f355ded8101507de998a221354ba357bad66528f9efbbb50200d4d731
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 958601e4741c30c6707ee6808fb89a258dfa10bab373f623d63fed29faef5eaf0d54d8fecfba4e9949bd16afb7d23bd84aca80aa2d485243de84076e2087284a
|
|
7
|
+
data.tar.gz: 11d1ae7d926743a202413647e1fe0f75e3c47966c9444c83a25e706ab8b923fa37ea2cda565042634d0cf6abc900be5fa2e1d3b47c436e6efc950a5d6f3d8fde
|
data/.github/mergify.yml
CHANGED
|
@@ -13,7 +13,10 @@ pull_request_rules:
|
|
|
13
13
|
conditions:
|
|
14
14
|
- author~=^dependabot(|-preview)\[bot\]$
|
|
15
15
|
- "#approved-reviews-by>=1"
|
|
16
|
-
- "check-success
|
|
16
|
+
- "check-success~=build-test-lint (2.4.x)"
|
|
17
|
+
- "check-success~=build-test-lint (2.5.x)"
|
|
18
|
+
- "check-success~=build-test-lint (2.6.x)"
|
|
19
|
+
- "check-success~=build-test-lint (2.7.x)"
|
|
17
20
|
actions:
|
|
18
21
|
merge:
|
|
19
22
|
method: squash
|
data/.github/workflows/ruby.yml
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
attr-gather (1.
|
|
4
|
+
attr-gather (1.3.0)
|
|
5
5
|
dry-container (~> 0.7)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -9,123 +9,134 @@ GEM
|
|
|
9
9
|
specs:
|
|
10
10
|
addressable (2.7.0)
|
|
11
11
|
public_suffix (>= 2.0.2, < 5.0)
|
|
12
|
-
ast (2.4.
|
|
12
|
+
ast (2.4.1)
|
|
13
13
|
backport (1.1.2)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
benchmark (0.1.0)
|
|
15
|
+
coderay (1.1.3)
|
|
16
|
+
concurrent-ruby (1.1.7)
|
|
17
|
+
diff-lcs (1.4.4)
|
|
17
18
|
domain_name (0.5.20190701)
|
|
18
19
|
unf (>= 0.0.5, < 1.0.0)
|
|
19
|
-
dry-configurable (0.
|
|
20
|
+
dry-configurable (0.11.6)
|
|
20
21
|
concurrent-ruby (~> 1.0)
|
|
21
22
|
dry-core (~> 0.4, >= 0.4.7)
|
|
23
|
+
dry-equalizer (~> 0.2)
|
|
22
24
|
dry-container (0.7.2)
|
|
23
25
|
concurrent-ruby (~> 1.0)
|
|
24
26
|
dry-configurable (~> 0.1, >= 0.1.3)
|
|
25
27
|
dry-core (0.4.9)
|
|
26
28
|
concurrent-ruby (~> 1.0)
|
|
27
|
-
dry-equalizer (0.
|
|
29
|
+
dry-equalizer (0.3.0)
|
|
28
30
|
dry-inflector (0.2.0)
|
|
29
|
-
dry-initializer (3.0.
|
|
30
|
-
dry-logic (1.0.
|
|
31
|
+
dry-initializer (3.0.4)
|
|
32
|
+
dry-logic (1.0.8)
|
|
31
33
|
concurrent-ruby (~> 1.0)
|
|
32
34
|
dry-core (~> 0.2)
|
|
33
35
|
dry-equalizer (~> 0.2)
|
|
34
|
-
dry-schema (1.
|
|
36
|
+
dry-schema (1.5.5)
|
|
35
37
|
concurrent-ruby (~> 1.0)
|
|
36
38
|
dry-configurable (~> 0.8, >= 0.8.3)
|
|
37
39
|
dry-core (~> 0.4)
|
|
38
40
|
dry-equalizer (~> 0.2)
|
|
39
41
|
dry-initializer (~> 3.0)
|
|
40
42
|
dry-logic (~> 1.0)
|
|
41
|
-
dry-types (~> 1.
|
|
42
|
-
dry-types (1.
|
|
43
|
+
dry-types (~> 1.4)
|
|
44
|
+
dry-types (1.4.0)
|
|
43
45
|
concurrent-ruby (~> 1.0)
|
|
44
46
|
dry-container (~> 0.3)
|
|
45
47
|
dry-core (~> 0.4, >= 0.4.4)
|
|
46
|
-
dry-equalizer (~> 0.
|
|
48
|
+
dry-equalizer (~> 0.3)
|
|
47
49
|
dry-inflector (~> 0.1, >= 0.1.2)
|
|
48
50
|
dry-logic (~> 1.0, >= 1.0.2)
|
|
49
|
-
dry-validation (1.
|
|
51
|
+
dry-validation (1.5.6)
|
|
50
52
|
concurrent-ruby (~> 1.0)
|
|
51
53
|
dry-container (~> 0.7, >= 0.7.1)
|
|
52
54
|
dry-core (~> 0.4)
|
|
53
55
|
dry-equalizer (~> 0.2)
|
|
54
56
|
dry-initializer (~> 3.0)
|
|
55
|
-
dry-schema (~> 1.
|
|
56
|
-
|
|
57
|
+
dry-schema (~> 1.5, >= 1.5.2)
|
|
58
|
+
e2mmap (0.1.0)
|
|
59
|
+
ffi (1.13.1)
|
|
57
60
|
ffi-compiler (1.0.1)
|
|
58
61
|
ffi (>= 1.0.0)
|
|
59
62
|
rake
|
|
60
|
-
|
|
61
|
-
http (4.2.0)
|
|
63
|
+
http (4.4.1)
|
|
62
64
|
addressable (~> 2.3)
|
|
63
65
|
http-cookie (~> 1.0)
|
|
64
|
-
http-form_data (~> 2.
|
|
66
|
+
http-form_data (~> 2.2)
|
|
65
67
|
http-parser (~> 1.2.0)
|
|
66
68
|
http-cookie (1.0.3)
|
|
67
69
|
domain_name (~> 0.5)
|
|
68
|
-
http-form_data (2.
|
|
70
|
+
http-form_data (2.3.0)
|
|
69
71
|
http-parser (1.2.1)
|
|
70
72
|
ffi-compiler (>= 1.0, < 2.0)
|
|
71
|
-
jaro_winkler (1.5.
|
|
72
|
-
|
|
73
|
+
jaro_winkler (1.5.4)
|
|
74
|
+
maruku (0.7.3)
|
|
75
|
+
method_source (1.0.0)
|
|
73
76
|
mini_portile2 (2.4.0)
|
|
74
|
-
nokogiri (1.10.
|
|
77
|
+
nokogiri (1.10.10)
|
|
75
78
|
mini_portile2 (~> 2.4.0)
|
|
76
|
-
parallel (1.
|
|
77
|
-
parser (2.
|
|
78
|
-
ast (~> 2.4.
|
|
79
|
-
pry (0.
|
|
80
|
-
coderay (~> 1.1
|
|
81
|
-
method_source (~>
|
|
82
|
-
public_suffix (4.0.
|
|
79
|
+
parallel (1.20.1)
|
|
80
|
+
parser (2.7.2.0)
|
|
81
|
+
ast (~> 2.4.1)
|
|
82
|
+
pry (0.13.1)
|
|
83
|
+
coderay (~> 1.1)
|
|
84
|
+
method_source (~> 1.0)
|
|
85
|
+
public_suffix (4.0.6)
|
|
83
86
|
rainbow (3.0.0)
|
|
84
87
|
rake (13.0.1)
|
|
85
|
-
|
|
88
|
+
regexp_parser (2.0.0)
|
|
89
|
+
reverse_markdown (2.0.0)
|
|
86
90
|
nokogiri
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
rspec-
|
|
90
|
-
rspec-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
rexml (3.2.4)
|
|
92
|
+
rspec (3.10.0)
|
|
93
|
+
rspec-core (~> 3.10.0)
|
|
94
|
+
rspec-expectations (~> 3.10.0)
|
|
95
|
+
rspec-mocks (~> 3.10.0)
|
|
96
|
+
rspec-core (3.10.0)
|
|
97
|
+
rspec-support (~> 3.10.0)
|
|
98
|
+
rspec-expectations (3.10.0)
|
|
94
99
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
95
|
-
rspec-support (~> 3.
|
|
96
|
-
rspec-mocks (3.
|
|
100
|
+
rspec-support (~> 3.10.0)
|
|
101
|
+
rspec-mocks (3.10.0)
|
|
97
102
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
98
|
-
rspec-support (~> 3.
|
|
99
|
-
rspec-support (3.
|
|
100
|
-
rubocop (0.
|
|
101
|
-
jaro_winkler (~> 1.5.1)
|
|
103
|
+
rspec-support (~> 3.10.0)
|
|
104
|
+
rspec-support (3.10.0)
|
|
105
|
+
rubocop (0.93.1)
|
|
102
106
|
parallel (~> 1.10)
|
|
103
|
-
parser (>= 2.
|
|
107
|
+
parser (>= 2.7.1.5)
|
|
104
108
|
rainbow (>= 2.2.2, < 4.0)
|
|
109
|
+
regexp_parser (>= 1.8)
|
|
110
|
+
rexml
|
|
111
|
+
rubocop-ast (>= 0.6.0)
|
|
105
112
|
ruby-progressbar (~> 1.7)
|
|
106
|
-
unicode-display_width (>= 1.4.0, <
|
|
107
|
-
rubocop-
|
|
113
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
|
114
|
+
rubocop-ast (1.3.0)
|
|
115
|
+
parser (>= 2.7.1.5)
|
|
116
|
+
rubocop-performance (1.6.1)
|
|
108
117
|
rubocop (>= 0.71.0)
|
|
109
118
|
ruby-progressbar (1.10.1)
|
|
110
|
-
solargraph (0.
|
|
119
|
+
solargraph (0.39.17)
|
|
111
120
|
backport (~> 1.1)
|
|
121
|
+
benchmark
|
|
112
122
|
bundler (>= 1.17.2)
|
|
113
|
-
|
|
123
|
+
e2mmap
|
|
114
124
|
jaro_winkler (~> 1.5)
|
|
125
|
+
maruku (~> 0.7, >= 0.7.3)
|
|
115
126
|
nokogiri (~> 1.9, >= 1.9.1)
|
|
116
127
|
parser (~> 2.3)
|
|
117
|
-
reverse_markdown (
|
|
128
|
+
reverse_markdown (>= 1.0.5, < 3)
|
|
118
129
|
rubocop (~> 0.52)
|
|
119
|
-
thor (~>
|
|
130
|
+
thor (~> 1.0)
|
|
120
131
|
tilt (~> 2.0)
|
|
121
|
-
yard (~> 0.9)
|
|
122
|
-
thor (0.
|
|
132
|
+
yard (~> 0.9, >= 0.9.24)
|
|
133
|
+
thor (1.0.1)
|
|
123
134
|
tilt (2.0.10)
|
|
124
135
|
unf (0.1.4)
|
|
125
136
|
unf_ext
|
|
126
|
-
unf_ext (0.0.7.
|
|
127
|
-
unicode-display_width (1.
|
|
128
|
-
yard (0.9.
|
|
137
|
+
unf_ext (0.0.7.7)
|
|
138
|
+
unicode-display_width (1.7.0)
|
|
139
|
+
yard (0.9.25)
|
|
129
140
|
|
|
130
141
|
PLATFORMS
|
|
131
142
|
ruby
|
|
@@ -133,7 +144,7 @@ PLATFORMS
|
|
|
133
144
|
DEPENDENCIES
|
|
134
145
|
attr-gather!
|
|
135
146
|
bundler (~> 2.0)
|
|
136
|
-
dry-validation (~> 1.
|
|
147
|
+
dry-validation (~> 1.5)
|
|
137
148
|
http
|
|
138
149
|
pry
|
|
139
150
|
rake (~> 13.0)
|
data/README.md
CHANGED
|
@@ -1,10 +1,147 @@
|
|
|
1
1
|
# attr-gather
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
|
|
5
|
-
A gem for creating
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
A gem for creating workflows that "enhance" entities with extra attributes. At a high level, [attr-gather](https://github.com/ianks/attr-gather) provides a process to fetch information from many data sources (such as third party APIs, legacy databases, etc.) in a fully parallelized fashion.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### Defining your workflow
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# define a workflow
|
|
13
|
+
class EnhanceProfile
|
|
14
|
+
include Attr::Gather::Workflow
|
|
15
|
+
|
|
16
|
+
# contains all the task implementations
|
|
17
|
+
container TasksContainer
|
|
18
|
+
|
|
19
|
+
# filter out invalid data using a Dry::Validation::Contract
|
|
20
|
+
# anything that doesn't match this schema will be filtered out
|
|
21
|
+
filter_with_contract do
|
|
22
|
+
params do
|
|
23
|
+
required(:user_id).filled(:integer)
|
|
24
|
+
|
|
25
|
+
optional(:user).hash do
|
|
26
|
+
optional(:name).filled(:string)
|
|
27
|
+
optional(:email).filled(:string)
|
|
28
|
+
optional(:gravatar).filled(:string)
|
|
29
|
+
optional(:email_info).hash do
|
|
30
|
+
optional(:deliverable).filled(:bool?)
|
|
31
|
+
optional(:free).filled(:bool?)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# each task returns a hash of data that will be merged into the result
|
|
38
|
+
task :fetch_post do |t|
|
|
39
|
+
t.depends_on = []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# will run in parallel
|
|
43
|
+
task :fetch_user do |t|
|
|
44
|
+
t.depends_on = [:fetch_post]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# will run in parallel
|
|
48
|
+
task :fetch_email_info do |t|
|
|
49
|
+
t.depends_on = [:fetch_user]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Defining some tasks
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
class PostFetcher
|
|
58
|
+
def call(attrs)
|
|
59
|
+
res = HTTP.get("https://jsonplaceholder.typicode.com/posts/#{attrs[:id]}")
|
|
60
|
+
post = JSON.parse(res.to_s, symbolize_names: true)
|
|
61
|
+
|
|
62
|
+
{ title: post[:title], user_id: post[:userId], body: post[:body] }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
class UserFetcher
|
|
69
|
+
# will have access to the PostFetcher attributes here
|
|
70
|
+
def call(attrs)
|
|
71
|
+
res = HTTP.get("https://jsonplaceholder.typicode.com/users/#{attrs[:user_id]}")
|
|
72
|
+
user = JSON.parse(res.to_s, symbolize_names: true)
|
|
73
|
+
|
|
74
|
+
{ user: { name: user[:name], email: user[:email] } }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
class EmailInfoFetcher
|
|
81
|
+
# will have access to the PostFetcher attributes here
|
|
82
|
+
def call(user:)
|
|
83
|
+
res = HTTP.timeout(3).get("https://api.trumail.io/v2/lookups/json?email=#{user[:email]}")
|
|
84
|
+
info = JSON.parse(res.to_s, symbolize_names: true)
|
|
85
|
+
|
|
86
|
+
# will deep merge with the final result
|
|
87
|
+
{ user: { email_info: { deliverable: info[:deliverable], free: info[:free] } } }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Registering your tasks
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
class MyContainer
|
|
96
|
+
extend Dry::Container::Mixin
|
|
97
|
+
|
|
98
|
+
register :fetch_post, PostFetcher
|
|
99
|
+
register :fetch_user, UserFetcher
|
|
100
|
+
register :fetch_email_info, EmailInfoFetcher
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Run it!
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
enhancer = EnhanceUserProfile.new
|
|
108
|
+
enhancer.call(id: 12).value!
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
And this is the result...
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
{
|
|
115
|
+
:id => 12,
|
|
116
|
+
:user_id => 2,
|
|
117
|
+
:user => {
|
|
118
|
+
:email => "Shanna@melissa.tv",
|
|
119
|
+
:name => "Ervin Howell",
|
|
120
|
+
:email_info => { :deliverable => true, :free => true },
|
|
121
|
+
:gravatar => "https://www.gravatar.com/avatar/241af7d19a0a7438794aef21e4e19b79"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
You can even preview it as an SVG!
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
enhancer.to_dot(preview: true) # requires graphviz (brew install graphviz)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Features
|
|
133
|
+
|
|
134
|
+
- Offers DSL for defining workflows and merging the results from each task
|
|
135
|
+
- Execution engine optimally parallelizes the execution of the workflow dependency graph using [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) Promises
|
|
136
|
+
- Very easy to unit test
|
|
137
|
+
- Ability to filter out bad/junky data using [dry-validation](https://dry-rb.org/gems/dry-validation) contracts
|
|
138
|
+
|
|
139
|
+
## What are the main difference between this Ruby project and similar ones?
|
|
140
|
+
|
|
141
|
+
- Operates on a single entity rather than a list, so easily adoptable in existing systems
|
|
142
|
+
- Focuses on the "fetching" and filtering of data solely, and not transformation or storage
|
|
143
|
+
- Focuses on having a clean PORO interface to make testing simple
|
|
144
|
+
- Provides a declarative interface for merging results from many sources (APIs, legacy databases, etc.) which allows for prioritization
|
|
8
145
|
|
|
9
146
|
## Links
|
|
10
147
|
|
data/attr-gather.gemspec
CHANGED
|
@@ -15,6 +15,8 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.homepage = 'https://github.com/ianks/attr-gather'
|
|
16
16
|
spec.license = 'MIT'
|
|
17
17
|
|
|
18
|
+
spec.required_ruby_version = '>= 2.4'
|
|
19
|
+
|
|
18
20
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
|
19
21
|
|
|
20
22
|
spec.metadata['homepage_uri'] = spec.homepage
|
data/examples/post_enhancer.rb
CHANGED
|
@@ -28,7 +28,7 @@ class MyContainer
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
register :email_info do |user:, **_attrs|
|
|
31
|
-
res = HTTP.
|
|
31
|
+
res = HTTP.get("https://api.trumail.io/v2/lookups/json?email=#{user[:email]}")
|
|
32
32
|
info = JSON.parse(res.to_s, symbolize_names: true)
|
|
33
33
|
|
|
34
34
|
{ user: { email_info: { deliverable: info[:deliverable], free: info[:free] } } }
|
|
@@ -15,16 +15,16 @@ module Attr
|
|
|
15
15
|
@default = resolve(:deep_merge)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
register(:deep_merge) do |*args|
|
|
18
|
+
register(:deep_merge) do |*args, **opts|
|
|
19
19
|
require 'attr/gather/aggregators/deep_merge'
|
|
20
20
|
|
|
21
|
-
DeepMerge.new(*args)
|
|
21
|
+
DeepMerge.new(*args, **opts)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
register(:shallow_merge) do |*args|
|
|
24
|
+
register(:shallow_merge) do |*args, **opts|
|
|
25
25
|
require 'attr/gather/aggregators/shallow_merge'
|
|
26
26
|
|
|
27
|
-
ShallowMerge.new(*args)
|
|
27
|
+
ShallowMerge.new(*args, **opts)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'attr/gather/filters/noop'
|
|
4
|
+
|
|
3
5
|
module Attr
|
|
4
6
|
module Gather
|
|
5
7
|
module Aggregators
|
|
@@ -11,8 +13,14 @@ module Attr
|
|
|
11
13
|
class Base
|
|
12
14
|
attr_accessor :filter
|
|
13
15
|
|
|
16
|
+
NOOP_FILTER ||= Filters::Noop.new
|
|
17
|
+
|
|
14
18
|
def initialize(**opts)
|
|
15
|
-
@filter = opts.delete(:filter)
|
|
19
|
+
@filter = opts.delete(:filter) || NOOP_FILTER
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def with(**opts)
|
|
23
|
+
self.class.new(filter: @filter, **opts)
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def call(_original_input, _results_array)
|
|
@@ -21,16 +29,10 @@ module Attr
|
|
|
21
29
|
|
|
22
30
|
private
|
|
23
31
|
|
|
24
|
-
def wrap_result(result)
|
|
25
|
-
Concurrent::Promise.fulfill(result)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
32
|
def unwrap_result(res)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return unvalidated if filter.nil?
|
|
33
|
+
return res if filter.nil?
|
|
32
34
|
|
|
33
|
-
filter.call(
|
|
35
|
+
filter.call(res).value
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
end
|
|
@@ -21,16 +21,19 @@ module Attr
|
|
|
21
21
|
|
|
22
22
|
def call(input, execution_results)
|
|
23
23
|
execution_results = execution_results.reverse_each if reverse?
|
|
24
|
+
initial = unwrap_initial_input(input)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
execution_results.reduce(initial) do |memo, res|
|
|
26
27
|
deep_merge(memo, unwrap_result(res))
|
|
27
28
|
end
|
|
28
|
-
|
|
29
|
-
wrap_result(result)
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
private
|
|
33
32
|
|
|
33
|
+
def unwrap_initial_input(input)
|
|
34
|
+
input
|
|
35
|
+
end
|
|
36
|
+
|
|
34
37
|
def reverse?
|
|
35
38
|
@reverse
|
|
36
39
|
end
|
|
@@ -20,13 +20,11 @@ module Attr
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def call(input, execution_results)
|
|
23
|
-
|
|
23
|
+
execution_results = execution_results.reverse_each if reverse?
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
execution_results.reduce(input) do |memo, res|
|
|
26
26
|
memo.merge(unwrap_result(res))
|
|
27
27
|
end
|
|
28
|
-
|
|
29
|
-
wrap_result(result)
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
private
|
|
@@ -29,13 +29,13 @@ module Attr
|
|
|
29
29
|
# @param name [Symbol]
|
|
30
30
|
#
|
|
31
31
|
# @return [#call]
|
|
32
|
-
def resolve(name, *args)
|
|
32
|
+
def resolve(name, *args, **opts)
|
|
33
33
|
block = @__storage__.fetch(name) do
|
|
34
34
|
raise NotFoundError,
|
|
35
35
|
"no item with name #{name} registered"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
block.call(*args)
|
|
38
|
+
block.call(*args, **opts)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# @api private
|
data/lib/attr/gather/version.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'attr/gather/workflow/task_executor'
|
|
4
|
-
require 'attr/gather/workflow/async_task_executor'
|
|
5
|
-
|
|
6
3
|
module Attr
|
|
7
4
|
module Gather
|
|
8
5
|
module Workflow
|
|
@@ -21,42 +18,39 @@ module Attr
|
|
|
21
18
|
#
|
|
22
19
|
# @param input [Hash]
|
|
23
20
|
#
|
|
24
|
-
# @return [Concurrent::Promise]
|
|
21
|
+
# @return [Concurrent::Promise<Hash>]
|
|
25
22
|
#
|
|
26
23
|
# @note For more information, check out {https://dry-rb.org/gems/dry-monads/1.0/result}
|
|
27
24
|
#
|
|
28
25
|
# @api public
|
|
29
26
|
def call(input)
|
|
30
|
-
|
|
27
|
+
task_promises = {}
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
final_results << executor_results
|
|
35
|
-
aggregator.call(aggregated_input, executor_results).value!
|
|
29
|
+
final_results = self.class.tasks.to_a.map do |task|
|
|
30
|
+
task_promises[task] = execute_task(input, task, task_promises)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
Concurrent::Promise.zip(*final_results).then do |results|
|
|
34
|
+
aggregator.call(input, results)
|
|
35
|
+
end
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
private
|
|
42
39
|
|
|
43
|
-
# Enumator for task batches
|
|
44
|
-
#
|
|
45
|
-
# @return [Enumerator]
|
|
46
|
-
#
|
|
47
|
-
# @api private
|
|
48
|
-
def each_task_batch
|
|
49
|
-
self.class.tasks.each_batch
|
|
50
|
-
end
|
|
51
|
-
|
|
52
40
|
# Executes a batch of tasks
|
|
53
41
|
#
|
|
54
42
|
# @return [Array<TaskExecutionResult>]
|
|
55
43
|
#
|
|
56
44
|
# @api private
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
def execute_task(initial_input, task, task_promises)
|
|
46
|
+
task_proc = container.resolve(task.name)
|
|
47
|
+
dep_promises = task.depends_on.map { |t| task_promises[t] }
|
|
48
|
+
input_promise = Concurrent::Promise.zip(*dep_promises)
|
|
49
|
+
|
|
50
|
+
input_promise.then do |results|
|
|
51
|
+
dep_input = aggregator.call(initial_input, results)
|
|
52
|
+
task_proc.call(dep_input)
|
|
53
|
+
end
|
|
60
54
|
end
|
|
61
55
|
|
|
62
56
|
# @api private
|
|
@@ -33,8 +33,7 @@ module Attr
|
|
|
33
33
|
|
|
34
34
|
def serialize_row(task)
|
|
35
35
|
row = all_dependants_for_task(task).map { |dt| [task, dt] }
|
|
36
|
-
|
|
37
|
-
lines
|
|
36
|
+
row.map { |item| "#{item.map(&:name).join(' -> ')};" }
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
def all_dependants_for_task(input_task)
|
|
@@ -16,7 +16,7 @@ module Attr
|
|
|
16
16
|
#
|
|
17
17
|
# @example
|
|
18
18
|
# class EnhanceUserProfile
|
|
19
|
-
#
|
|
19
|
+
# include Attr::Gather::Workflow
|
|
20
20
|
#
|
|
21
21
|
# # ...
|
|
22
22
|
#
|
|
@@ -36,12 +36,62 @@ module Attr
|
|
|
36
36
|
#
|
|
37
37
|
# @api public
|
|
38
38
|
def task(task_name, opts = EMPTY_HASH)
|
|
39
|
-
|
|
40
|
-
yield
|
|
41
|
-
tasks <<
|
|
39
|
+
conf = OpenStruct.new
|
|
40
|
+
yield conf
|
|
41
|
+
tasks << Hash[name: task_name, **opts, **conf.to_h]
|
|
42
42
|
self
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Defines a task with name and options
|
|
46
|
+
#
|
|
47
|
+
# @param task_name [Symbol] the name of the task
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# class EnhanceUserProfile
|
|
51
|
+
# include Attr::Gather::Workflow
|
|
52
|
+
#
|
|
53
|
+
# # ...
|
|
54
|
+
#
|
|
55
|
+
# fetch :user_info do |t|
|
|
56
|
+
# t.depends_on = [:fetch_gravatar_info]
|
|
57
|
+
# end
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# Calling `fetch` will yield a task object which you can configure like
|
|
61
|
+
# a PORO. Tasks will be registered for execution in the workflow.
|
|
62
|
+
#
|
|
63
|
+
# @yield [Attr::Gather::Workflow::Task] A task to configure
|
|
64
|
+
#
|
|
65
|
+
# @api public
|
|
66
|
+
def fetch(task_name, opts = EMPTY_HASH)
|
|
67
|
+
task(task_name, opts)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Defines a task with name and options
|
|
71
|
+
#
|
|
72
|
+
# @param task_name [Symbol] the name of the task
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# class EnhanceUserProfile
|
|
76
|
+
# include Attr::Gather::Workflow
|
|
77
|
+
#
|
|
78
|
+
# # ...
|
|
79
|
+
#
|
|
80
|
+
# step :fetch_user_info do |t|
|
|
81
|
+
# t.depends_on = [:email_info]
|
|
82
|
+
# end
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
# Calling `step` will yield a task object which you can configure like
|
|
86
|
+
# a PORO. Tasks will be registered for execution in the workflow.
|
|
87
|
+
#
|
|
88
|
+
# @yield [Attr::Gather::Workflow::Task] A task to configure
|
|
89
|
+
#
|
|
90
|
+
# @api public
|
|
91
|
+
def step(task_name, opts = EMPTY_HASH)
|
|
92
|
+
task(task_name, opts)
|
|
93
|
+
end
|
|
94
|
+
|
|
45
95
|
# Defines a container for task dependencies
|
|
46
96
|
#
|
|
47
97
|
# Using a container makes it easy to re-use workflows with different
|
|
@@ -55,7 +105,7 @@ module Attr
|
|
|
55
105
|
# end
|
|
56
106
|
#
|
|
57
107
|
# class EnhanceUserProfile
|
|
58
|
-
#
|
|
108
|
+
# include Attr::Gather::Workflow
|
|
59
109
|
#
|
|
60
110
|
# container LegacySystem
|
|
61
111
|
# end
|
|
@@ -79,7 +129,7 @@ module Attr
|
|
|
79
129
|
#
|
|
80
130
|
# @example
|
|
81
131
|
# class EnhanceUserProfile
|
|
82
|
-
#
|
|
132
|
+
# include Attr::Gather::Workflow
|
|
83
133
|
#
|
|
84
134
|
# aggregator :deep_merge
|
|
85
135
|
# end
|
|
@@ -88,13 +138,13 @@ module Attr
|
|
|
88
138
|
#
|
|
89
139
|
# @api public
|
|
90
140
|
def aggregator(agg = nil, opts = EMPTY_HASH)
|
|
91
|
-
if agg.nil? && !defined?(@aggregator)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
141
|
+
@aggregator = if agg.nil? && !defined?(@aggregator)
|
|
142
|
+
Aggregators.default
|
|
143
|
+
elsif agg
|
|
144
|
+
Aggregators.resolve(agg, filter: filter, **opts)
|
|
145
|
+
else
|
|
146
|
+
@aggregator
|
|
147
|
+
end
|
|
98
148
|
end
|
|
99
149
|
|
|
100
150
|
# Defines a filter for filtering out invalid values
|
|
@@ -119,7 +169,7 @@ module Attr
|
|
|
119
169
|
# end
|
|
120
170
|
#
|
|
121
171
|
# class EnhanceUserProfile
|
|
122
|
-
#
|
|
172
|
+
# include Attr::Gather::Workflow
|
|
123
173
|
#
|
|
124
174
|
# # Any of the key/value pairs that had validation errors will be
|
|
125
175
|
# # filtered from the output.
|
|
@@ -130,12 +180,16 @@ module Attr
|
|
|
130
180
|
# @param args [Array<Object>] arguments for initializing the filter
|
|
131
181
|
#
|
|
132
182
|
# @api public
|
|
133
|
-
def filter(filt =
|
|
134
|
-
if filt
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
183
|
+
def filter(filt = nil, *args, **opts)
|
|
184
|
+
@filter = if filt.nil? && !defined?(@filter)
|
|
185
|
+
Filters.default
|
|
186
|
+
elsif filt
|
|
187
|
+
Filters.resolve(filt, *args, **opts)
|
|
188
|
+
else
|
|
189
|
+
@filter
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
aggregator.filter = @filter
|
|
139
193
|
|
|
140
194
|
@filter
|
|
141
195
|
end
|
|
@@ -147,7 +201,7 @@ module Attr
|
|
|
147
201
|
# @example
|
|
148
202
|
#
|
|
149
203
|
# class EnhanceUserProfile
|
|
150
|
-
#
|
|
204
|
+
# include Attr::Gather::Workflow
|
|
151
205
|
#
|
|
152
206
|
# # Any of the key/value pairs that had validation errors will be
|
|
153
207
|
# # filtered from the output.
|
|
@@ -169,7 +223,7 @@ module Attr
|
|
|
169
223
|
# @api public
|
|
170
224
|
def filter_with_contract(arg = nil, &blk)
|
|
171
225
|
contract = block_given? ? build_inline_contract_filter(&blk) : arg
|
|
172
|
-
|
|
226
|
+
filter(:contract, contract)
|
|
173
227
|
end
|
|
174
228
|
|
|
175
229
|
private
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'dry-equalizer'
|
|
4
|
+
|
|
3
5
|
module Attr
|
|
4
6
|
module Gather
|
|
5
7
|
module Workflow
|
|
6
8
|
# @api private
|
|
7
9
|
class Task
|
|
8
|
-
|
|
10
|
+
send :include, Dry::Equalizer(:name, :depends_on)
|
|
11
|
+
|
|
12
|
+
attr_accessor :name, :depends_on
|
|
9
13
|
|
|
14
|
+
# Initialize a new DeepMerge aggregator
|
|
15
|
+
#
|
|
16
|
+
# @param name [String] name of the task
|
|
17
|
+
# @param depends_on [Array<Task>] tasks needed before running this task
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
10
20
|
def initialize(name:, depends_on: [])
|
|
11
21
|
@name = name
|
|
12
22
|
@depends_on = depends_on
|
|
13
23
|
end
|
|
14
24
|
|
|
25
|
+
# Check if this task depends on a given task
|
|
26
|
+
#
|
|
27
|
+
# @param other_task [Task] task to check
|
|
15
28
|
def depends_on?(other_task)
|
|
16
|
-
depends_on.include?(other_task
|
|
29
|
+
depends_on.include?(other_task)
|
|
17
30
|
end
|
|
18
31
|
|
|
19
32
|
def fullfilled_given_remaining_tasks?(task_list)
|
|
@@ -20,7 +20,9 @@ module Attr
|
|
|
20
20
|
tasks.each { |t| self << t }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def <<(
|
|
23
|
+
def <<(hash)
|
|
24
|
+
name, depends_on = hash.values_at :name, :depends_on
|
|
25
|
+
task = build_task(name, depends_on)
|
|
24
26
|
validate_for_insert!(task)
|
|
25
27
|
|
|
26
28
|
registered_tasks.each do |t|
|
|
@@ -68,6 +70,16 @@ module Attr
|
|
|
68
70
|
|
|
69
71
|
private
|
|
70
72
|
|
|
73
|
+
def build_task(name, depends_on)
|
|
74
|
+
deps = depends_on.map do |dep_name|
|
|
75
|
+
registered_tasks.find do |task|
|
|
76
|
+
task.name == dep_name
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Task.new(name: name, depends_on: deps)
|
|
81
|
+
end
|
|
82
|
+
|
|
71
83
|
def tsort_each_child(node, &blk)
|
|
72
84
|
to_h[node].each(&blk)
|
|
73
85
|
end
|
|
@@ -99,7 +111,7 @@ module Attr
|
|
|
99
111
|
end
|
|
100
112
|
|
|
101
113
|
def depended_on_tasks_exist?(task)
|
|
102
|
-
task.depends_on.all? { |t| registered_tasks.
|
|
114
|
+
task.depends_on.all? { |t| registered_tasks.include?(t) }
|
|
103
115
|
end
|
|
104
116
|
end
|
|
105
117
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: attr-gather
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ian Ker-Seymer
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-12-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -115,14 +115,11 @@ files:
|
|
|
115
115
|
- lib/attr/gather/filters/result.rb
|
|
116
116
|
- lib/attr/gather/version.rb
|
|
117
117
|
- lib/attr/gather/workflow.rb
|
|
118
|
-
- lib/attr/gather/workflow/async_task_executor.rb
|
|
119
118
|
- lib/attr/gather/workflow/callable.rb
|
|
120
119
|
- lib/attr/gather/workflow/dot_serializer.rb
|
|
121
120
|
- lib/attr/gather/workflow/dsl.rb
|
|
122
121
|
- lib/attr/gather/workflow/graphable.rb
|
|
123
122
|
- lib/attr/gather/workflow/task.rb
|
|
124
|
-
- lib/attr/gather/workflow/task_execution_result.rb
|
|
125
|
-
- lib/attr/gather/workflow/task_executor.rb
|
|
126
123
|
- lib/attr/gather/workflow/task_graph.rb
|
|
127
124
|
homepage: https://github.com/ianks/attr-gather
|
|
128
125
|
licenses:
|
|
@@ -130,7 +127,7 @@ licenses:
|
|
|
130
127
|
metadata:
|
|
131
128
|
homepage_uri: https://github.com/ianks/attr-gather
|
|
132
129
|
source_code_uri: https://github.com/ianks/attr-gather
|
|
133
|
-
post_install_message:
|
|
130
|
+
post_install_message:
|
|
134
131
|
rdoc_options: []
|
|
135
132
|
require_paths:
|
|
136
133
|
- lib
|
|
@@ -138,7 +135,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
138
135
|
requirements:
|
|
139
136
|
- - ">="
|
|
140
137
|
- !ruby/object:Gem::Version
|
|
141
|
-
version: '
|
|
138
|
+
version: '2.4'
|
|
142
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
140
|
requirements:
|
|
144
141
|
- - ">="
|
|
@@ -146,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
146
143
|
version: '0'
|
|
147
144
|
requirements: []
|
|
148
145
|
rubygems_version: 3.0.3
|
|
149
|
-
signing_key:
|
|
146
|
+
signing_key:
|
|
150
147
|
specification_version: 4
|
|
151
148
|
summary: Write a short summary, because RubyGems requires one.
|
|
152
149
|
test_files: []
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'attr/gather/workflow/task_executor'
|
|
4
|
-
|
|
5
|
-
module Attr
|
|
6
|
-
module Gather
|
|
7
|
-
module Workflow
|
|
8
|
-
# @api private
|
|
9
|
-
class AsyncTaskExecutor < TaskExecutor
|
|
10
|
-
def initialize(*)
|
|
11
|
-
super
|
|
12
|
-
@executor = :io
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Attr
|
|
4
|
-
module Gather
|
|
5
|
-
module Workflow
|
|
6
|
-
# A wrapper containing information and results of a task execution
|
|
7
|
-
#
|
|
8
|
-
# @!attribute [r] started_at
|
|
9
|
-
# @return [Time] time which the execution occured
|
|
10
|
-
#
|
|
11
|
-
# @!attribute [r] task
|
|
12
|
-
# @return [Attr::Gather::Workflow::Task] task that was run
|
|
13
|
-
#
|
|
14
|
-
# @!attribute [r] result
|
|
15
|
-
# @return [Concurrent::Promise] the result promise of the the task
|
|
16
|
-
#
|
|
17
|
-
# @api public
|
|
18
|
-
class TaskExecutionResult
|
|
19
|
-
include Concerns::Identifiable
|
|
20
|
-
|
|
21
|
-
attr_reader :task, :result, :started_at, :uuid
|
|
22
|
-
|
|
23
|
-
def initialize(task, result)
|
|
24
|
-
@started_at = Time.now
|
|
25
|
-
@uuid = SecureRandom.uuid
|
|
26
|
-
@task = task
|
|
27
|
-
@result = result
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# @!attribute [r] state
|
|
31
|
-
# @return [:unscheduled, :pending, :processing, :rejected, :fulfilled]
|
|
32
|
-
def state
|
|
33
|
-
result.state
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Extracts the result, this is an unsafe operation that blocks the
|
|
37
|
-
# operation, and returns either the value or an exception.
|
|
38
|
-
#
|
|
39
|
-
# @note For more information, check out {https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Concern/Obligation.html#value!-instance_method}
|
|
40
|
-
def value!
|
|
41
|
-
result.value!
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Represents the TaskExecutionResult as a hash
|
|
45
|
-
#
|
|
46
|
-
# @return [Hash]
|
|
47
|
-
def as_json
|
|
48
|
-
value = result.value
|
|
49
|
-
|
|
50
|
-
{ started_at: started_at,
|
|
51
|
-
task: task.as_json,
|
|
52
|
-
state: state,
|
|
53
|
-
value: value }
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'concurrent'
|
|
4
|
-
require 'attr/gather/workflow/task_execution_result'
|
|
5
|
-
|
|
6
|
-
module Attr
|
|
7
|
-
module Gather
|
|
8
|
-
module Workflow
|
|
9
|
-
# @api private
|
|
10
|
-
class TaskExecutor
|
|
11
|
-
attr_reader :batch, :container, :executor
|
|
12
|
-
|
|
13
|
-
def initialize(batch, container:)
|
|
14
|
-
@batch = batch
|
|
15
|
-
@container = container
|
|
16
|
-
@executor = :immediate
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def call(input)
|
|
20
|
-
batch.map do |task|
|
|
21
|
-
task_proc = container.resolve(task.name)
|
|
22
|
-
result = Concurrent::Promise.execute(executor: executor) do
|
|
23
|
-
task_proc.call(input)
|
|
24
|
-
end
|
|
25
|
-
TaskExecutionResult.new(task, result)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|