materialist 3.5.0 → 3.8.1
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 +5 -5
- data/README.md +43 -5
- data/RELEASE_NOTES.md +8 -1
- data/lib/materialist/materializer/internals/dsl.rb +17 -4
- data/lib/materialist/materializer/internals/field_mapping.rb +3 -2
- data/lib/materialist/materializer/internals/materializer.rb +10 -6
- data/lib/materialist/version.rb +1 -1
- data/spec/materialist/materializer/internals/field_mapping_spec.rb +36 -0
- data/spec/materialist/materializer_spec.rb +84 -39
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a087fb6cf53741a69d823d1ba2e6c95213584bd7c32e608b997f7a18cfbd04de
|
4
|
+
data.tar.gz: 4ed4006703f538a5bf28b09c9a88818a274fc3d0de0c41a8c48222f2463a0960
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68164c5054c6fb24c21b05740eb4e252b51d9140da4f41338b20f4c9f7fa795057648ef1c97c9fdc8712828e705ed40d066021518c3510c8f15955f7cb93b025
|
7
|
+
data.tar.gz: 3a06570c0102381dd6415784cfada797bc2e9434a69b91df10549ade261f1286bc5caab3cdb47c323a11361a884914536a7704dbd3adbbe5ae671e7bc27e287d
|
data/README.md
CHANGED
@@ -25,6 +25,20 @@ Then do
|
|
25
25
|
bundle
|
26
26
|
```
|
27
27
|
|
28
|
+
### Release
|
29
|
+
|
30
|
+
After merging all of your PRs:
|
31
|
+
|
32
|
+
1. Bump the version in `lib/materialist/version.rb` -- let's say `x.y.z`
|
33
|
+
1. Build the gem: `gem build materialist.gemspec`
|
34
|
+
1. Push the gem: `gem push materialist-x.y.z.gem`
|
35
|
+
1. Commit changes: `git commit -am "Bump version"`
|
36
|
+
1. Create a tag: `git tag -a vx.y.z`
|
37
|
+
1. Push changes: `git push origin master`
|
38
|
+
1. Push the new: `git push origin --tags`
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
28
42
|
### Entity
|
29
43
|
|
30
44
|
Your materialised entity need to have a **unique** `source_url` column, alongside any other field you wish to materialise.
|
@@ -77,7 +91,7 @@ Materialist.configure do |config|
|
|
77
91
|
end
|
78
92
|
```
|
79
93
|
|
80
|
-
- `topics` (only when using in `.subscribe`): A string array of topics to be used.
|
94
|
+
- `topics` (only when using in `.subscribe`): A string array of topics to be used.
|
81
95
|
If not provided nothing would be materialized.
|
82
96
|
- `sidekiq_options` (optional, default: `{ retry: 10 }`) -- See [Sidekiq docs](https://github.com/mperham/sidekiq/wiki/Advanced-Options#workers) for list of options
|
83
97
|
- `api_client` (optional) -- You can pass your `Routemaster::APIClient` instance
|
@@ -141,8 +155,8 @@ class ZoneMaterializer
|
|
141
155
|
|
142
156
|
persist_to :zone
|
143
157
|
|
144
|
-
source_key :source_id do |url|
|
145
|
-
/(\d+)\/?$/.match(url)[1]
|
158
|
+
source_key :source_id do |url, response|
|
159
|
+
/(\d+)\/?$/.match(url)[1] # or response.dig(:some_attr)
|
146
160
|
end
|
147
161
|
|
148
162
|
capture :id, as: :orderweb_id
|
@@ -175,14 +189,20 @@ describes the name of the active record model to be used.
|
|
175
189
|
If missing, materialist skips materialising the resource itself, but will continue
|
176
190
|
with any other functionality -- such as `materialize_link`.
|
177
191
|
|
178
|
-
#### `source_key <column> <
|
192
|
+
#### `source_key <column> <parser_block> (default: url, resource response body[create, update action only])`
|
179
193
|
describes the column used to persist the unique identifier parsed from the url_parser_block.
|
180
194
|
By default the column used is `:source_url` and the original `url` is used as the identifier.
|
181
|
-
Passing an optional block allows you to extract an identifier from the URL.
|
195
|
+
Passing an optional block allows you to extract an identifier from the URL and captured attributes.
|
182
196
|
|
183
197
|
#### `capture <key>, as: <column> (default: key)`
|
184
198
|
describes mapping a resource key to a database column.
|
185
199
|
|
200
|
+
You can optionally provide a block for parsing the value:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
capture(:location, as: :latitude) { |location| location[:latitude] }
|
204
|
+
```
|
205
|
+
|
186
206
|
#### `capture_link_href <key>, as: <column>`
|
187
207
|
describes mapping a link href (as it appears on the hateous response) to a database column.
|
188
208
|
|
@@ -223,6 +243,24 @@ class ZoneMaterializer
|
|
223
243
|
end
|
224
244
|
```
|
225
245
|
|
246
|
+
#### `before_upsert_with_payload <method> (, <method>(, ...))`
|
247
|
+
describes the name of the instance method(s) to be invoked before a record is
|
248
|
+
materialized, with the record as it exists in the database, or nil if it has
|
249
|
+
not been created yet. The function will get as a second argument the `payload`
|
250
|
+
of the HTTP response, this can be used to add additional information/persist
|
251
|
+
other objects.
|
252
|
+
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
class ZoneMaterializer
|
256
|
+
include Materialist::Materializer
|
257
|
+
|
258
|
+
before_upsert_with_payload :my_method
|
259
|
+
|
260
|
+
def my_method(record, payload); end
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
226
264
|
|
227
265
|
#### `after_upsert <method> (, <method>(, ...))` -- also `after_destroy`
|
228
266
|
describes the name of the instance method(s) to be invoked after a record was materialized, with the updated record as a parameter. See above for a similar example implementation.
|
data/RELEASE_NOTES.md
CHANGED
@@ -6,8 +6,12 @@ module Materialist
|
|
6
6
|
__materialist_options[:links_to_materialize][key] = { topic: topic }
|
7
7
|
end
|
8
8
|
|
9
|
-
def capture(key, as: key)
|
10
|
-
__materialist_dsl_mapping_stack.last << FieldMapping.new(
|
9
|
+
def capture(key, as: key, &value_parser_block)
|
10
|
+
__materialist_dsl_mapping_stack.last << FieldMapping.new(
|
11
|
+
key: key,
|
12
|
+
as: as,
|
13
|
+
value_parser: value_parser_block
|
14
|
+
)
|
11
15
|
end
|
12
16
|
|
13
17
|
def capture_link_href(key, as:, &url_parser_block)
|
@@ -34,9 +38,18 @@ module Materialist
|
|
34
38
|
__materialist_options[:sidekiq_options] = options
|
35
39
|
end
|
36
40
|
|
37
|
-
def source_key(key, &
|
41
|
+
def source_key(key, &source_key_parser)
|
38
42
|
__materialist_options[:source_key] = key
|
39
|
-
__materialist_options[:
|
43
|
+
__materialist_options[:source_key_parser] = source_key_parser
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method is meant to be used for cases when the application needs
|
47
|
+
# to have access to the `payload` that is returned on the HTTP call.
|
48
|
+
# Such an example would be if the application logic requires all
|
49
|
+
# relationships to be present before the `resource` is saved in the
|
50
|
+
# database. Introduced in https://github.com/deliveroo/materialist/pull/47
|
51
|
+
def before_upsert_with_payload(*method_array)
|
52
|
+
__materialist_options[:before_upsert_with_payload] = method_array
|
40
53
|
end
|
41
54
|
|
42
55
|
def before_upsert(*method_array)
|
@@ -2,13 +2,14 @@ module Materialist
|
|
2
2
|
module Materializer
|
3
3
|
module Internals
|
4
4
|
class FieldMapping
|
5
|
-
def initialize(key:, as: key)
|
5
|
+
def initialize(key:, as: key, value_parser: nil)
|
6
6
|
@key = key
|
7
7
|
@as = as
|
8
|
+
@value_parser = value_parser || ->value { value }
|
8
9
|
end
|
9
10
|
|
10
11
|
def map(resource)
|
11
|
-
{ @as => resource.dig(@key) }
|
12
|
+
{ @as => @value_parser.call(resource.dig(@key)) }
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -59,8 +59,9 @@ module Materialist
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def upsert_record
|
62
|
-
model_class.find_or_initialize_by(source_lookup(url)).tap do |entity|
|
62
|
+
model_class.find_or_initialize_by(source_lookup(url, resource)).tap do |entity|
|
63
63
|
send_messages(before_upsert, entity) unless before_upsert.nil?
|
64
|
+
before_upsert_with_payload&.each { |m| instance.send(m, entity, resource) }
|
64
65
|
entity.update_attributes!(attributes)
|
65
66
|
end
|
66
67
|
end
|
@@ -74,7 +75,6 @@ module Materialist
|
|
74
75
|
return unless link = resource.dig(:_links, key)
|
75
76
|
return unless materializer_class = MaterializerFactory.class_from_topic(opts.fetch(:topic))
|
76
77
|
|
77
|
-
# TODO: perhaps consider doing this asynchronously some how?
|
78
78
|
materializer_class.perform(link[:href], :noop)
|
79
79
|
end
|
80
80
|
|
@@ -86,6 +86,10 @@ module Materialist
|
|
86
86
|
options[:before_upsert]
|
87
87
|
end
|
88
88
|
|
89
|
+
def before_upsert_with_payload
|
90
|
+
options[:before_upsert_with_payload]
|
91
|
+
end
|
92
|
+
|
89
93
|
def after_upsert
|
90
94
|
options[:after_upsert]
|
91
95
|
end
|
@@ -106,12 +110,12 @@ module Materialist
|
|
106
110
|
options.fetch(:source_key, :source_url)
|
107
111
|
end
|
108
112
|
|
109
|
-
def
|
110
|
-
options[:
|
113
|
+
def source_key_parser
|
114
|
+
options[:source_key_parser] || ->(url, data) { url }
|
111
115
|
end
|
112
116
|
|
113
|
-
def source_lookup(url)
|
114
|
-
@_source_lookup ||= { source_key =>
|
117
|
+
def source_lookup(url, resource={})
|
118
|
+
@_source_lookup ||= { source_key => source_key_parser.call(url, resource) }
|
115
119
|
end
|
116
120
|
|
117
121
|
def attributes
|
data/lib/materialist/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'materialist/materializer/internals'
|
3
|
+
|
4
|
+
include Materialist::Materializer::Internals
|
5
|
+
|
6
|
+
RSpec.describe Materialist::Materializer::Internals::FieldMapping, type: :internals do
|
7
|
+
let(:instance) { described_class.new(key: key, as: as, value_parser: value_parser_block) }
|
8
|
+
|
9
|
+
describe '#map' do
|
10
|
+
let(:key) { :b }
|
11
|
+
let(:as) { :z }
|
12
|
+
let(:value_parser_block) { nil }
|
13
|
+
let(:resource) do
|
14
|
+
{
|
15
|
+
a: 1,
|
16
|
+
b: {
|
17
|
+
c: 2
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
let(:map) { instance.map(resource) }
|
22
|
+
|
23
|
+
context 'when no parse block is passed' do
|
24
|
+
let(:expected_result) { { z: { c: 2 } } }
|
25
|
+
|
26
|
+
it { expect(map).to eq(expected_result) }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when a value parse block is passed' do
|
30
|
+
let(:value_parser_block) { ->value { value[:c] } }
|
31
|
+
let(:expected_result) { { z: 2 } }
|
32
|
+
|
33
|
+
it { expect(map).to eq(expected_result) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -50,7 +50,7 @@ RSpec.describe Materialist::Materializer::Internals::Materializer do
|
|
50
50
|
let(:source_body) {{ _links: { city: { href: city_url }}, name: 'jack', age: 30 }}
|
51
51
|
let(:defined_source_id) { 65 }
|
52
52
|
let(:defined_source_url) { "https://service.dev/defined_sources/#{defined_source_id}" }
|
53
|
-
let(:defined_source_body) {{ name: 'ben' }}
|
53
|
+
let(:defined_source_body) {{ name: 'ben', id: defined_source_id }}
|
54
54
|
|
55
55
|
def stub_resource(url, body)
|
56
56
|
stub_request(:get, url).to_return(
|
@@ -199,8 +199,13 @@ RSpec.describe Materialist::Materializer::Internals::Materializer do
|
|
199
199
|
|
200
200
|
persist_to :foobar
|
201
201
|
before_upsert :before_hook
|
202
|
+
before_upsert_with_payload :before_hook_with_payload
|
202
203
|
after_upsert :after_hook
|
203
204
|
|
205
|
+
def before_hook_with_payload(entity, payload)
|
206
|
+
self.actions_called[:before_hook_with_payload] = true
|
207
|
+
end
|
208
|
+
|
204
209
|
def before_hook(entity); self.actions_called[:before_hook] = true; end
|
205
210
|
def after_hook(entity); self.actions_called[:after_hook] = true; end
|
206
211
|
end
|
@@ -209,6 +214,11 @@ RSpec.describe Materialist::Materializer::Internals::Materializer do
|
|
209
214
|
%i(create update noop).each do |action_name|
|
210
215
|
context "when action is :#{action_name}" do
|
211
216
|
let(:action) { action_name }
|
217
|
+
|
218
|
+
it "calls before_upsert_with_payload method" do
|
219
|
+
expect{ perform }.to change { actions_called[:before_hook_with_payload] }
|
220
|
+
end
|
221
|
+
|
212
222
|
it "calls before_upsert method" do
|
213
223
|
expect{ perform }.to change { actions_called[:before_hook] }
|
214
224
|
end
|
@@ -225,8 +235,16 @@ RSpec.describe Materialist::Materializer::Internals::Materializer do
|
|
225
235
|
|
226
236
|
persist_to :foobar
|
227
237
|
before_upsert :before_hook, :before_hook2
|
238
|
+
before_upsert_with_payload :before_hook_with_payload, :before_hook_with_payload2
|
228
239
|
after_upsert :after_hook, :after_hook2
|
229
240
|
|
241
|
+
def before_hook_with_payload(entity, payload)
|
242
|
+
self.actions_called[:before_hook_with_payload] = true
|
243
|
+
end
|
244
|
+
def before_hook_with_payload2(entity, payload)
|
245
|
+
self.actions_called[:before_hook_with_payload2] = true
|
246
|
+
end
|
247
|
+
|
230
248
|
def before_hook(entity); self.actions_called[:before_hook] = true; end
|
231
249
|
def before_hook2(entity); self.actions_called[:before_hook2] = true; end
|
232
250
|
def after_hook(entity); self.actions_called[:after_hook] = true; end
|
@@ -239,6 +257,8 @@ RSpec.describe Materialist::Materializer::Internals::Materializer do
|
|
239
257
|
.and change { actions_called[:before_hook2] }
|
240
258
|
.and change { actions_called[:after_hook] }
|
241
259
|
.and change { actions_called[:after_hook2] }
|
260
|
+
.and change { actions_called[:before_hook_with_payload] }
|
261
|
+
.and change { actions_called[:before_hook_with_payload2] }
|
242
262
|
end
|
243
263
|
end
|
244
264
|
end
|
@@ -360,62 +380,87 @@ RSpec.describe Materialist::Materializer::Internals::Materializer do
|
|
360
380
|
end
|
361
381
|
|
362
382
|
context "entity based on the source_key column" do
|
363
|
-
|
364
|
-
|
365
|
-
|
383
|
+
shared_examples 'an upsert materialization event' do
|
384
|
+
context "when creating" do
|
385
|
+
let(:perform) { subject.perform(defined_source_url, action) }
|
366
386
|
|
367
|
-
|
368
|
-
|
369
|
-
source_key :source_id do |url|
|
370
|
-
url.split('/').last.to_i
|
387
|
+
it "creates based on source_key" do
|
388
|
+
expect{perform}.to change{DefinedSource.count}.by 1
|
371
389
|
end
|
372
390
|
|
373
|
-
|
391
|
+
it "sets the correct source key" do
|
392
|
+
perform
|
393
|
+
inserted = DefinedSource.find_by(source_id: defined_source_id)
|
394
|
+
expect(inserted.source_id).to eq defined_source_id
|
395
|
+
expect(inserted.name).to eq defined_source_body[:name]
|
396
|
+
end
|
374
397
|
end
|
375
|
-
end
|
376
398
|
|
377
|
-
|
378
|
-
|
399
|
+
context "when updating" do
|
400
|
+
let(:action) { :update }
|
401
|
+
let!(:record) { DefinedSource.create!(source_id: defined_source_id, name: 'mo') }
|
402
|
+
let(:perform) { subject.perform(defined_source_url, action) }
|
379
403
|
|
380
|
-
|
381
|
-
|
382
|
-
|
404
|
+
it "updates based on source_key" do
|
405
|
+
perform
|
406
|
+
expect(DefinedSource.count).to eq 1
|
407
|
+
end
|
383
408
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
409
|
+
it "updates the existing record" do
|
410
|
+
perform
|
411
|
+
inserted = DefinedSource.find_by(source_id: defined_source_id)
|
412
|
+
expect(inserted.source_id).to eq defined_source_id
|
413
|
+
expect(inserted.name).to eq defined_source_body[:name]
|
414
|
+
end
|
389
415
|
end
|
390
416
|
end
|
391
417
|
|
392
|
-
context
|
393
|
-
|
394
|
-
|
395
|
-
|
418
|
+
context 'with url source key parser' do
|
419
|
+
subject do
|
420
|
+
Class.new do
|
421
|
+
include Materialist::Materializer
|
422
|
+
|
423
|
+
persist_to :defined_source
|
424
|
+
|
425
|
+
source_key :source_id do |url|
|
426
|
+
url.split('/').last.to_i
|
427
|
+
end
|
396
428
|
|
397
|
-
|
398
|
-
|
399
|
-
expect(DefinedSource.count).to eq 1
|
429
|
+
capture :name
|
430
|
+
end
|
400
431
|
end
|
401
432
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
433
|
+
context "when deleting" do
|
434
|
+
let(:action) { :delete }
|
435
|
+
let!(:record) { DefinedSource.create!(source_id: defined_source_id, name: 'mo') }
|
436
|
+
let(:perform) { subject.perform(defined_source_url, action) }
|
437
|
+
|
438
|
+
it "deletes based on source_key" do
|
439
|
+
perform
|
440
|
+
expect(DefinedSource.count).to eq 0
|
441
|
+
end
|
407
442
|
end
|
443
|
+
|
444
|
+
it_behaves_like 'an upsert materialization event'
|
408
445
|
end
|
409
446
|
|
410
|
-
context
|
411
|
-
|
412
|
-
|
413
|
-
|
447
|
+
context 'with resource source key parser' do
|
448
|
+
subject do
|
449
|
+
Class.new do
|
450
|
+
include Materialist::Materializer
|
414
451
|
|
415
|
-
|
416
|
-
|
417
|
-
|
452
|
+
persist_to :defined_source
|
453
|
+
|
454
|
+
source_key :source_id do |_, resource|
|
455
|
+
resource.dig(:id)
|
456
|
+
end
|
457
|
+
|
458
|
+
capture :name
|
459
|
+
capture :id
|
460
|
+
end
|
418
461
|
end
|
462
|
+
|
463
|
+
it_behaves_like 'an upsert materialization event'
|
419
464
|
end
|
420
465
|
end
|
421
466
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: materialist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mo Valipour
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|
@@ -178,7 +178,7 @@ dependencies:
|
|
178
178
|
- - ">="
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
|
-
description:
|
181
|
+
description:
|
182
182
|
email:
|
183
183
|
- valipour@gmail.com
|
184
184
|
executables: []
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- materialist.gemspec
|
220
220
|
- spec/materialist/event_handler_spec.rb
|
221
221
|
- spec/materialist/materialized_record_spec.rb
|
222
|
+
- spec/materialist/materializer/internals/field_mapping_spec.rb
|
222
223
|
- spec/materialist/materializer/internals/link_mapping_spec.rb
|
223
224
|
- spec/materialist/materializer_factory_spec.rb
|
224
225
|
- spec/materialist/materializer_spec.rb
|
@@ -231,7 +232,7 @@ homepage: http://github.com/deliveroo/materialist
|
|
231
232
|
licenses:
|
232
233
|
- MIT
|
233
234
|
metadata: {}
|
234
|
-
post_install_message:
|
235
|
+
post_install_message:
|
235
236
|
rdoc_options: []
|
236
237
|
require_paths:
|
237
238
|
- lib
|
@@ -246,14 +247,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
246
247
|
- !ruby/object:Gem::Version
|
247
248
|
version: '0'
|
248
249
|
requirements: []
|
249
|
-
|
250
|
-
|
251
|
-
signing_key:
|
250
|
+
rubygems_version: 3.0.9
|
251
|
+
signing_key:
|
252
252
|
specification_version: 4
|
253
253
|
summary: Utilities to materialize routemaster topics
|
254
254
|
test_files:
|
255
255
|
- spec/materialist/event_handler_spec.rb
|
256
256
|
- spec/materialist/materialized_record_spec.rb
|
257
|
+
- spec/materialist/materializer/internals/field_mapping_spec.rb
|
257
258
|
- spec/materialist/materializer/internals/link_mapping_spec.rb
|
258
259
|
- spec/materialist/materializer_factory_spec.rb
|
259
260
|
- spec/materialist/materializer_spec.rb
|