attr-gather 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: []