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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ad5a7f5ef0961f9e8cf3317baaf07cbb53d8e021
4
- data.tar.gz: 394077c4817dd29106c78f02124f6c145ab6dafd
2
+ SHA256:
3
+ metadata.gz: a087fb6cf53741a69d823d1ba2e6c95213584bd7c32e608b997f7a18cfbd04de
4
+ data.tar.gz: 4ed4006703f538a5bf28b09c9a88818a274fc3d0de0c41a8c48222f2463a0960
5
5
  SHA512:
6
- metadata.gz: 1fc7dd6e7563ec9205631a5f8520c6928b8ca10c269c3c8f74cb2093080e500878b8a5220ae5fabab1a985090d8f66ffcd55dac2fdaed4253a6de679601ade13
7
- data.tar.gz: 4d293933436137b9daf41d3de5117eeb707653d0ce73f7865356d5fe1bfad6b8354a4073ae1ecfeec3adae7ad1c4b7ad38a2713f6668ca7a7b9d77e956e1fcfa
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> <url_parser_block> (default: url)`
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
@@ -1,6 +1,13 @@
1
1
  ## Next
2
2
 
3
- _description of next release_
3
+ _list pending release notes here..._
4
+ ## 3.8.1
5
+
6
+ - ActiveSupport >= 6
7
+
8
+ ## 3.6.0
9
+
10
+ - Add support for parsing value when using `capture`
4
11
 
5
12
  ## 3.5.0
6
13
 
@@ -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(key: key, as: as)
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, &url_parser_block)
41
+ def source_key(key, &source_key_parser)
38
42
  __materialist_options[:source_key] = key
39
- __materialist_options[:url_parser] = url_parser_block
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 url_parser
110
- options[:url_parser] || ->url { url }
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 => url_parser.call(url) }
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
@@ -1,3 +1,3 @@
1
1
  module Materialist
2
- VERSION = '3.5.0'
2
+ VERSION = '3.8.1'
3
3
  end
@@ -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
- subject do
364
- Class.new do
365
- include Materialist::Materializer
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
- persist_to :defined_source
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
- capture :name
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
- context "when creating" do
378
- let(:perform) { subject.perform(defined_source_url, action) }
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
- it "creates based on source_key" do
381
- expect{perform}.to change{DefinedSource.count}.by 1
382
- end
404
+ it "updates based on source_key" do
405
+ perform
406
+ expect(DefinedSource.count).to eq 1
407
+ end
383
408
 
384
- it "sets the correct source key" do
385
- perform
386
- inserted = DefinedSource.find_by(source_id: defined_source_id)
387
- expect(inserted.source_id).to eq defined_source_id
388
- expect(inserted.name).to eq defined_source_body[:name]
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 "when updating" do
393
- let(:action) { :update }
394
- let!(:record) { DefinedSource.create!(source_id: defined_source_id, name: 'mo') }
395
- let(:perform) { subject.perform(defined_source_url, action) }
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
- it "updates based on source_key" do
398
- perform
399
- expect(DefinedSource.count).to eq 1
429
+ capture :name
430
+ end
400
431
  end
401
432
 
402
- it "updates the existing record" do
403
- perform
404
- inserted = DefinedSource.find_by(source_id: defined_source_id)
405
- expect(inserted.source_id).to eq defined_source_id
406
- expect(inserted.name).to eq defined_source_body[:name]
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 "when deleting" do
411
- let(:action) { :delete }
412
- let!(:record) { DefinedSource.create!(source_id: defined_source_id, name: 'mo') }
413
- let(:perform) { subject.perform(defined_source_url, action) }
447
+ context 'with resource source key parser' do
448
+ subject do
449
+ Class.new do
450
+ include Materialist::Materializer
414
451
 
415
- it "deletes based on source_key" do
416
- perform
417
- expect(DefinedSource.count).to eq 0
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.5.0
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: 2018-10-18 00:00:00.000000000 Z
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
- rubyforge_project:
250
- rubygems_version: 2.6.14.1
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