materialist 3.5.0 → 3.8.1

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
- 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