attr-gather 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee9b30fd44bd8931964b53b904815224fa6a5a592ac1f22d19605a982a326d42
4
- data.tar.gz: 7954a71a61dc89266358909ce189312b3b3164e4092d62ae4fae973712efca9d
3
+ metadata.gz: edfd2d7479248348551d31676b45459f6f8279f6ca5fdf6ed00540c791de86bd
4
+ data.tar.gz: 977ac39f355ded8101507de998a221354ba357bad66528f9efbbb50200d4d731
5
5
  SHA512:
6
- metadata.gz: 040b490aba781f8f851b617a365d2aea061f9d2afb189bcdf4e0e086effebc2135109a648b0f116d4be7000cbe4d3e10e369fadcb38d9e2910114c36957cce68
7
- data.tar.gz: 59a93d0d6e16f9b2433822c4faf7ef4cda25f3e9e37da8a5fae816e313e1d6e0b86906a1d652b3c64125eae888607c9117ade38fc17ff11b84c361add21f4c62
6
+ metadata.gz: 958601e4741c30c6707ee6808fb89a258dfa10bab373f623d63fed29faef5eaf0d54d8fecfba4e9949bd16afb7d23bd84aca80aa2d485243de84076e2087284a
7
+ data.tar.gz: 11d1ae7d926743a202413647e1fe0f75e3c47966c9444c83a25e706ab8b923fa37ea2cda565042634d0cf6abc900be5fa2e1d3b47c436e6efc950a5d6f3d8fde
@@ -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=build-test-lint"
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
@@ -4,6 +4,9 @@ on: [push]
4
4
  jobs:
5
5
  build-test-lint:
6
6
  runs-on: ubuntu-latest
7
+ strategy:
8
+ matrix:
9
+ ruby: ["2.4.x", "2.5.x", "2.6.x", "2.7.x"]
7
10
  steps:
8
11
  - uses: actions/checkout@v1
9
12
  - name: Set up Ruby 2.6
@@ -4,7 +4,8 @@ require:
4
4
  AllCops:
5
5
  UseCache: true
6
6
  CacheRootDirectory: './tmp/cache'
7
- TargetRubyVersion: 2.5
7
+ TargetRubyVersion: 2.4
8
+ NewCops: enable
8
9
 
9
10
  Metrics/BlockLength:
10
11
  Exclude:
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- attr-gather (1.2.0)
4
+ attr-gather (1.3.0)
5
5
  dry-container (~> 0.7)
6
6
 
7
7
  GEM
@@ -14,7 +14,7 @@ GEM
14
14
  benchmark (0.1.0)
15
15
  coderay (1.1.3)
16
16
  concurrent-ruby (1.1.7)
17
- diff-lcs (1.3)
17
+ diff-lcs (1.4.4)
18
18
  domain_name (0.5.20190701)
19
19
  unf (>= 0.0.5, < 1.0.0)
20
20
  dry-configurable (0.11.6)
@@ -76,7 +76,7 @@ GEM
76
76
  mini_portile2 (2.4.0)
77
77
  nokogiri (1.10.10)
78
78
  mini_portile2 (~> 2.4.0)
79
- parallel (1.19.2)
79
+ parallel (1.20.1)
80
80
  parser (2.7.2.0)
81
81
  ast (~> 2.4.1)
82
82
  pry (0.13.1)
@@ -85,29 +85,35 @@ GEM
85
85
  public_suffix (4.0.6)
86
86
  rainbow (3.0.0)
87
87
  rake (13.0.1)
88
+ regexp_parser (2.0.0)
88
89
  reverse_markdown (2.0.0)
89
90
  nokogiri
90
- rspec (3.9.0)
91
- rspec-core (~> 3.9.0)
92
- rspec-expectations (~> 3.9.0)
93
- rspec-mocks (~> 3.9.0)
94
- rspec-core (3.9.0)
95
- rspec-support (~> 3.9.0)
96
- rspec-expectations (3.9.0)
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)
97
99
  diff-lcs (>= 1.2.0, < 2.0)
98
- rspec-support (~> 3.9.0)
99
- rspec-mocks (3.9.0)
100
+ rspec-support (~> 3.10.0)
101
+ rspec-mocks (3.10.0)
100
102
  diff-lcs (>= 1.2.0, < 2.0)
101
- rspec-support (~> 3.9.0)
102
- rspec-support (3.9.0)
103
- rubocop (0.75.1)
104
- jaro_winkler (~> 1.5.1)
103
+ rspec-support (~> 3.10.0)
104
+ rspec-support (3.10.0)
105
+ rubocop (0.93.1)
105
106
  parallel (~> 1.10)
106
- parser (>= 2.6)
107
+ parser (>= 2.7.1.5)
107
108
  rainbow (>= 2.2.2, < 4.0)
109
+ regexp_parser (>= 1.8)
110
+ rexml
111
+ rubocop-ast (>= 0.6.0)
108
112
  ruby-progressbar (~> 1.7)
109
- unicode-display_width (>= 1.4.0, < 1.7)
110
- rubocop-performance (1.5.1)
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)
111
117
  rubocop (>= 0.71.0)
112
118
  ruby-progressbar (1.10.1)
113
119
  solargraph (0.39.17)
@@ -129,7 +135,7 @@ GEM
129
135
  unf (0.1.4)
130
136
  unf_ext
131
137
  unf_ext (0.0.7.7)
132
- unicode-display_width (1.6.1)
138
+ unicode-display_width (1.7.0)
133
139
  yard (0.9.25)
134
140
 
135
141
  PLATFORMS
data/README.md CHANGED
@@ -1,10 +1,147 @@
1
1
  # attr-gather
2
2
 
3
- [![Actions Status](https://github.com/ianks/attr-gather/workflows/.github/workflows/ruby.yml/badge.svg)](https://github.com/ianks/attr-gather/actions)
3
+ ![Actions Status](https://github.com/ianks/attr-gather/workflows/Build%20+%20Test%20+%20Lint/badge.svg)
4
4
 
5
- A gem for creating simple workflows to enhance entities with extra attributes.
6
- At a high level, `attr-gather` provides a process to sync attributes from many
7
- sources (third party APIs, legacy databases, etc).
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
 
@@ -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
@@ -28,7 +28,7 @@ class MyContainer
28
28
  end
29
29
 
30
30
  register :email_info do |user:, **_attrs|
31
- res = HTTP.timeout(3).get("https://api.trumail.io/v2/lookups/json?email=#{user[:email]}")
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] } } }
@@ -14,8 +14,8 @@ module Attr
14
14
  # @param dry_contract [Dry::Contract]
15
15
  def initialize(dry_contract)
16
16
  validate_dry_contract!(dry_contract)
17
-
18
17
  @dry_contract = dry_contract
18
+ super()
19
19
  end
20
20
 
21
21
  def call(input)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Attr
4
4
  module Gather
5
- VERSION = '1.2.0'
5
+ VERSION = '1.3.0'
6
6
  end
7
7
  end
@@ -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
- lines = row.map { |item| item.map(&:name).join(' -> ') + ';' }
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
- # extend Attr::Gather::Workflow
19
+ # include Attr::Gather::Workflow
20
20
  #
21
21
  # # ...
22
22
  #
@@ -42,6 +42,56 @@ module Attr
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
- # extend Attr::Gather::Workflow
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
- # extend Attr::Gather::Workflow
132
+ # include Attr::Gather::Workflow
83
133
  #
84
134
  # aggregator :deep_merge
85
135
  # end
@@ -119,7 +169,7 @@ module Attr
119
169
  # end
120
170
  #
121
171
  # class EnhanceUserProfile
122
- # extend Attr::Gather::Workflow
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.
@@ -151,7 +201,7 @@ module Attr
151
201
  # @example
152
202
  #
153
203
  # class EnhanceUserProfile
154
- # extend Attr::Gather::Workflow
204
+ # include Attr::Gather::Workflow
155
205
  #
156
206
  # # Any of the key/value pairs that had validation errors will be
157
207
  # # filtered from the output.
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.2.0
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-10-22 00:00:00.000000000 Z
11
+ date: 2020-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -127,7 +127,7 @@ licenses:
127
127
  metadata:
128
128
  homepage_uri: https://github.com/ianks/attr-gather
129
129
  source_code_uri: https://github.com/ianks/attr-gather
130
- post_install_message:
130
+ post_install_message:
131
131
  rdoc_options: []
132
132
  require_paths:
133
133
  - lib
@@ -135,7 +135,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: '2.4'
139
139
  required_rubygems_version: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - ">="
@@ -143,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
143
  version: '0'
144
144
  requirements: []
145
145
  rubygems_version: 3.0.3
146
- signing_key:
146
+ signing_key:
147
147
  specification_version: 4
148
148
  summary: Write a short summary, because RubyGems requires one.
149
149
  test_files: []