materialist 3.3.0 → 3.4.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 +4 -4
- data/README.md +3 -0
- data/RELEASE_NOTES.md +5 -0
- data/lib/materialist.rb +1 -1
- data/lib/materialist/materializer/internals/class_methods.rb +2 -3
- data/lib/materialist/materializer/internals/field_mapping.rb +2 -2
- data/lib/materialist/materializer/internals/link_href_mapping.rb +2 -2
- data/lib/materialist/materializer/internals/link_mapping.rb +6 -9
- data/lib/materialist/materializer/internals/materializer.rb +26 -26
- data/lib/materialist/materializer/internals/resources.rb +22 -0
- data/lib/materialist/materializer_factory.rb +1 -3
- data/spec/materialist/materializer/internals/link_mapping_spec.rb +86 -0
- data/spec/materialist/materializer_spec.rb +21 -3
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4b55d54d4aa9644e1fee1055286166521f97120
|
4
|
+
data.tar.gz: ed6c12846d2b859f49efdb7b5b9581184cafa4ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3a97dd8a5a618d2600d23d3259979dcf3698860c83794f0314387db616b6ca8635a15dbe7caf5bfd5082426292682241d343e7c9de39a2ab8128facf320d912
|
7
|
+
data.tar.gz: 0b61ede028064fcd5e4aa8fc2a4040e2beb8f04e7b3606f723a68fd1e064472eb57faf8f39219a95f7f8b274a666f41045a7630392cc262e8a3bc161680d5249
|
data/README.md
CHANGED
data/RELEASE_NOTES.md
CHANGED
data/lib/materialist.rb
CHANGED
@@ -4,9 +4,8 @@ module Materialist
|
|
4
4
|
module ClassMethods
|
5
5
|
attr_reader :__materialist_options, :__materialist_dsl_mapping_stack
|
6
6
|
|
7
|
-
def perform(url, action)
|
8
|
-
|
9
|
-
action == :delete ? materializer.destroy : materializer.upsert
|
7
|
+
def perform(url, action, *options)
|
8
|
+
Materializer.new(url, self, *options).perform(action)
|
10
9
|
end
|
11
10
|
|
12
11
|
def _sidekiq_options
|
@@ -2,13 +2,13 @@ module Materialist
|
|
2
2
|
module Materializer
|
3
3
|
module Internals
|
4
4
|
class FieldMapping
|
5
|
-
def initialize(key:, as:)
|
5
|
+
def initialize(key:, as: key)
|
6
6
|
@key = key
|
7
7
|
@as = as
|
8
8
|
end
|
9
9
|
|
10
10
|
def map(resource)
|
11
|
-
{ @as => resource.
|
11
|
+
{ @as => resource.dig(@key) }
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -2,25 +2,22 @@ module Materialist
|
|
2
2
|
module Materializer
|
3
3
|
module Internals
|
4
4
|
class LinkMapping
|
5
|
-
def initialize(key:, enable_caching: false)
|
5
|
+
def initialize(key:, mapping: [], enable_caching: false)
|
6
6
|
@key = key
|
7
|
+
@mapping = mapping
|
7
8
|
@enable_caching = enable_caching
|
8
|
-
@mapping = []
|
9
9
|
end
|
10
10
|
|
11
|
+
attr_reader :mapping
|
12
|
+
|
11
13
|
def map(resource)
|
12
14
|
return unless linked_resource = linked_resource(resource)
|
13
15
|
mapping.map{ |m| m.map(linked_resource) }.compact.reduce(&:merge)
|
14
16
|
end
|
15
17
|
|
16
|
-
attr_reader :mapping
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
18
|
def linked_resource(resource)
|
21
|
-
return unless resource.
|
22
|
-
|
23
|
-
sub_resource.show(enable_caching: @enable_caching)
|
19
|
+
return unless href = resource.dig(:_links, @key, :href)
|
20
|
+
resource.client.get(href, options: { enable_caching: @enable_caching })
|
24
21
|
rescue Routemaster::Errors::ResourceNotFound
|
25
22
|
nil
|
26
23
|
end
|
@@ -1,18 +1,29 @@
|
|
1
1
|
require 'routemaster/api_client'
|
2
2
|
require_relative '../../workers/event'
|
3
|
+
require_relative './resources'
|
3
4
|
|
4
5
|
module Materialist
|
5
6
|
module Materializer
|
6
7
|
module Internals
|
7
8
|
class Materializer
|
8
|
-
def initialize(url, klass)
|
9
|
+
def initialize(url, klass, resource_payload: nil, api_client: nil)
|
9
10
|
@url = url
|
10
11
|
@instance = klass.new
|
11
12
|
@options = klass.__materialist_options
|
13
|
+
@api_client = api_client || Routemaster::APIClient.new(response_class: HateoasResource)
|
14
|
+
if resource_payload
|
15
|
+
@resource = PayloadResource.new(resource_payload, client: @api_client)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform(action)
|
20
|
+
action.to_sym == :delete ? destroy : upsert
|
12
21
|
end
|
13
22
|
|
23
|
+
private
|
24
|
+
|
14
25
|
def upsert(retry_on_race_condition: true)
|
15
|
-
return unless
|
26
|
+
return unless resource
|
16
27
|
|
17
28
|
if materialize_self?
|
18
29
|
upsert_record.tap do |entity|
|
@@ -42,9 +53,7 @@ module Materialist
|
|
42
53
|
end
|
43
54
|
end
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
attr_reader :url, :instance, :options
|
56
|
+
attr_reader :url, :instance, :options, :api_client
|
48
57
|
|
49
58
|
def materialize_self?
|
50
59
|
options.include? :model_class
|
@@ -53,7 +62,7 @@ module Materialist
|
|
53
62
|
def upsert_record
|
54
63
|
model_class.find_or_initialize_by(source_lookup(url)).tap do |entity|
|
55
64
|
send_messages(before_upsert, entity) unless before_upsert.nil?
|
56
|
-
entity.update_attributes!
|
65
|
+
entity.update_attributes!(attributes)
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
@@ -63,16 +72,11 @@ module Materialist
|
|
63
72
|
end
|
64
73
|
|
65
74
|
def materialize_link(key, opts)
|
66
|
-
return unless
|
75
|
+
return unless link = resource.dig(:_links, key)
|
76
|
+
return unless materializer_class = MaterializerFactory.class_from_topic(opts.fetch(:topic))
|
67
77
|
|
68
|
-
# this
|
69
|
-
|
70
|
-
# :(
|
71
|
-
::Materialist::Workers::Event.new.perform({
|
72
|
-
'topic' => opts[:topic],
|
73
|
-
'url' => root_resource.body._links[key].href,
|
74
|
-
'type' => 'noop'
|
75
|
-
})
|
78
|
+
# TODO: perhaps consider doing this asynchronously some how?
|
79
|
+
materializer_class.perform(link[:href], :noop)
|
76
80
|
end
|
77
81
|
|
78
82
|
def mappings
|
@@ -112,21 +116,17 @@ module Materialist
|
|
112
116
|
end
|
113
117
|
|
114
118
|
def attributes
|
115
|
-
mappings.map{ |m| m.map(
|
119
|
+
mappings.map{ |m| m.map(resource) }.compact.reduce(&:merge) || {}
|
116
120
|
end
|
117
121
|
|
118
|
-
def
|
119
|
-
@
|
120
|
-
api_client.get(url, options: { enable_caching: false })
|
121
|
-
rescue Routemaster::Errors::ResourceNotFound
|
122
|
-
nil
|
123
|
-
end
|
122
|
+
def resource
|
123
|
+
@resource ||= fetch_resource
|
124
124
|
end
|
125
125
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
126
|
+
def fetch_resource
|
127
|
+
api_client.get(url, options: { enable_caching: false })
|
128
|
+
rescue Routemaster::Errors::ResourceNotFound
|
129
|
+
nil
|
130
130
|
end
|
131
131
|
|
132
132
|
def send_messages(messages, arguments)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Materialist
|
2
|
+
module Materializer
|
3
|
+
module Internals
|
4
|
+
class PayloadResource
|
5
|
+
attr_reader :client
|
6
|
+
|
7
|
+
delegate :dig, to: :@payload
|
8
|
+
|
9
|
+
def initialize(payload, client:)
|
10
|
+
@payload = payload
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class HateoasResource < PayloadResource
|
16
|
+
def initialize(response, client:)
|
17
|
+
super(response.body, client: client)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'materialist/materializer/internals'
|
3
|
+
|
4
|
+
include Materialist::Materializer::Internals
|
5
|
+
|
6
|
+
RSpec.describe Materialist::Materializer::Internals::LinkMapping, type: :internals do
|
7
|
+
let(:key) { :details }
|
8
|
+
subject(:instance) { described_class.new(key: key, mapping: mappings) }
|
9
|
+
let(:mappings) { [ FieldMapping.new(key: :name) ] }
|
10
|
+
|
11
|
+
let(:url_sub) { 'https://deliveroo.co.uk/details/1001' }
|
12
|
+
let(:payload) { { _links: { details: { href: url_sub } } } }
|
13
|
+
let(:payload_sub) { { name: "jack", age: 20 } }
|
14
|
+
|
15
|
+
let(:client) { double }
|
16
|
+
let(:resource_class) { Materialist::Materializer::Internals::PayloadResource }
|
17
|
+
let(:resource) { resource_class.new(payload, client: client) }
|
18
|
+
let(:resource_sub) { resource_class.new(payload_sub, client: client) }
|
19
|
+
|
20
|
+
before do
|
21
|
+
allow(client).to receive(:get).with(url_sub, anything).and_return resource_sub
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#map' do
|
25
|
+
subject(:perform) { instance.map resource }
|
26
|
+
|
27
|
+
it 'returns a hash corresponding to the mapping' do
|
28
|
+
is_expected.to eq({ name: 'jack' })
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when multiple mappings given' do
|
32
|
+
let(:mappings) do
|
33
|
+
[
|
34
|
+
FieldMapping.new(key: :age),
|
35
|
+
LinkMapping.new(key: :wont_find_me),
|
36
|
+
FieldMapping.new(key: :name)
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns a hash corresponding to the mapping' do
|
41
|
+
is_expected.to eq({ age: 20, name: 'jack' })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'missing sub resource' do
|
46
|
+
context 'when given link is not present' do
|
47
|
+
let(:key) { :foo }
|
48
|
+
|
49
|
+
it { is_expected.to be nil }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when given link is malformed' do
|
53
|
+
let(:payload) { { _links: { details: { foo: url_sub } } } }
|
54
|
+
|
55
|
+
it { is_expected.to be nil }
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when client returns nil' do
|
59
|
+
let(:resource_sub) { nil }
|
60
|
+
|
61
|
+
it { is_expected.to be nil }
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when client throws not-found error' do
|
65
|
+
before do
|
66
|
+
allow(client).to receive(:get)
|
67
|
+
.with(url_sub, anything)
|
68
|
+
.and_raise Routemaster::Errors::ResourceNotFound.new(double(body: nil))
|
69
|
+
end
|
70
|
+
|
71
|
+
it { is_expected.to be nil }
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when caching enabled' do
|
75
|
+
let(:instance) { described_class.new(key: key, mapping: mappings, enable_caching: true) }
|
76
|
+
|
77
|
+
it 'passes on the option to client' do
|
78
|
+
expect(client).to receive(:get)
|
79
|
+
.with(url_sub, options: { enable_caching: true})
|
80
|
+
perform
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
require 'support/uses_redis'
|
3
3
|
require 'materialist/materializer'
|
4
4
|
|
5
|
-
RSpec.describe Materialist::Materializer do
|
5
|
+
RSpec.describe Materialist::Materializer::Internals::Materializer do
|
6
6
|
uses_redis
|
7
7
|
|
8
8
|
describe ".perform" do
|
@@ -60,8 +60,9 @@ RSpec.describe Materialist::Materializer do
|
|
60
60
|
)
|
61
61
|
end
|
62
62
|
|
63
|
+
let!(:source_stub) { stub_resource source_url, source_body }
|
64
|
+
|
63
65
|
before do
|
64
|
-
stub_resource source_url, source_body
|
65
66
|
stub_resource country_url, country_body
|
66
67
|
stub_resource city_url, city_body
|
67
68
|
stub_resource defined_source_url, defined_source_body
|
@@ -77,7 +78,7 @@ RSpec.describe Materialist::Materializer do
|
|
77
78
|
let(:actions_called) { materializer_class.class_variable_get(:@@actions_called) }
|
78
79
|
|
79
80
|
it "materializes record in db" do
|
80
|
-
expect{perform}.to change{Foobar.count}.by 1
|
81
|
+
expect{ perform }.to change{ Foobar.count }.by 1
|
81
82
|
inserted = Foobar.find_by(source_url: source_url)
|
82
83
|
expect(inserted.name).to eq source_body[:name]
|
83
84
|
expect(inserted.how_old).to eq source_body[:age]
|
@@ -95,6 +96,23 @@ RSpec.describe Materialist::Materializer do
|
|
95
96
|
expect(inserted.name).to eq city_body[:name]
|
96
97
|
end
|
97
98
|
|
99
|
+
context 'when materializing using payload' do
|
100
|
+
let(:perform) { materializer_class.perform(source_url, action, resource_payload: source_body) }
|
101
|
+
|
102
|
+
it 'does not fetch resource' do
|
103
|
+
perform
|
104
|
+
expect(source_stub).to_not have_been_requested
|
105
|
+
end
|
106
|
+
|
107
|
+
it "materializes record in db" do
|
108
|
+
expect{ perform }.to change{ Foobar.count }.by 1
|
109
|
+
end
|
110
|
+
|
111
|
+
it "materializes linked record in db" do
|
112
|
+
expect{ perform }.to change{ City.count }.by 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
98
116
|
context "when record already exists" do
|
99
117
|
let!(:record) { Foobar.create!(source_url: source_url, name: 'mo') }
|
100
118
|
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mo Valipour
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|
@@ -212,11 +212,13 @@ files:
|
|
212
212
|
- lib/materialist/materializer/internals/link_href_mapping.rb
|
213
213
|
- lib/materialist/materializer/internals/link_mapping.rb
|
214
214
|
- lib/materialist/materializer/internals/materializer.rb
|
215
|
+
- lib/materialist/materializer/internals/resources.rb
|
215
216
|
- lib/materialist/materializer_factory.rb
|
216
217
|
- lib/materialist/workers/event.rb
|
217
218
|
- materialist.gemspec
|
218
219
|
- spec/materialist/event_handler_spec.rb
|
219
220
|
- spec/materialist/materialized_record_spec.rb
|
221
|
+
- spec/materialist/materializer/internals/link_mapping_spec.rb
|
220
222
|
- spec/materialist/materializer_factory_spec.rb
|
221
223
|
- spec/materialist/materializer_spec.rb
|
222
224
|
- spec/materialist/workers/event_spec.rb
|
@@ -251,6 +253,7 @@ summary: Utilities to materialize routemaster topics
|
|
251
253
|
test_files:
|
252
254
|
- spec/materialist/event_handler_spec.rb
|
253
255
|
- spec/materialist/materialized_record_spec.rb
|
256
|
+
- spec/materialist/materializer/internals/link_mapping_spec.rb
|
254
257
|
- spec/materialist/materializer_factory_spec.rb
|
255
258
|
- spec/materialist/materializer_spec.rb
|
256
259
|
- spec/materialist/workers/event_spec.rb
|