materialist 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|