materialist 3.0.0 → 3.1.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 +9 -2
- data/lib/materialist/event_handler.rb +20 -12
- data/lib/materialist/event_worker.rb +5 -35
- data/lib/materialist/materializer/internals/class_methods.rb +18 -0
- data/lib/materialist/materializer/internals/dsl.rb +56 -0
- data/lib/materialist/materializer/internals/field_mapping.rb +14 -0
- data/lib/materialist/materializer/internals/link_href_mapping.rb +14 -0
- data/lib/materialist/materializer/internals/link_mapping.rb +14 -0
- data/lib/materialist/materializer/internals/materializer.rb +166 -0
- data/lib/materialist/materializer/internals.rb +13 -0
- data/lib/materialist/materializer.rb +1 -248
- data/lib/materialist/materializer_factory.rb +9 -0
- data/lib/materialist/workers/event.rb +43 -0
- data/lib/materialist.rb +1 -1
- data/spec/materialist/event_handler_spec.rb +30 -32
- data/spec/materialist/materializer_factory_spec.rb +24 -0
- data/spec/materialist/materializer_spec.rb +49 -19
- data/spec/materialist/{event_worker_spec.rb → workers/event_spec.rb} +2 -2
- metadata +16 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99e85711c6de342809785430291c11d6a11b3379
|
|
4
|
+
data.tar.gz: d6b3f3bfecd9ce4b20ee699c3a75028294f7ab5f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 498ac330b0331c78ab2ce69aa71dbc0c3ddb0e5277711c0f9f1b4c836f4c810c9ae1f9df4e46c2a83ba1690817fe07cc97d694f10f3718728619c26719fd4ffe
|
|
7
|
+
data.tar.gz: d5e784ae7827bdc7defff614a3d47601efc4035f965e679c9ea4ad639ebc558440c1740556fead09f6788f66f3045b9ab9b037c822101ed95a41b6147263d986
|
data/README.md
CHANGED
|
@@ -118,7 +118,7 @@ map '/events' do
|
|
|
118
118
|
end
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
#### DSL
|
|
122
122
|
|
|
123
123
|
Next you would need to define a materializer for each of the topic. The name of
|
|
124
124
|
the materializer class should match the topic name (in singular)
|
|
@@ -131,6 +131,8 @@ require 'materialist/materializer'
|
|
|
131
131
|
class ZoneMaterializer
|
|
132
132
|
include Materialist::Materializer
|
|
133
133
|
|
|
134
|
+
sidekiq_options queue: :orderweb_service, retry: false
|
|
135
|
+
|
|
134
136
|
persist_to :zone
|
|
135
137
|
|
|
136
138
|
source_key :source_id do |url|
|
|
@@ -156,12 +158,17 @@ end
|
|
|
156
158
|
|
|
157
159
|
Here is what each part of the DSL mean:
|
|
158
160
|
|
|
161
|
+
#### `sidekiq_options <options>`
|
|
162
|
+
allows to override options for the Sidekiq job which does the materialization.
|
|
163
|
+
Typically it will specify which queue to put the job on or how many times
|
|
164
|
+
should the job try to retry. These options override the options specified in
|
|
165
|
+
`Materialist.configuration.sidekiq_options`.
|
|
166
|
+
|
|
159
167
|
#### `persist_to <model_name>`
|
|
160
168
|
describes the name of the active record model to be used.
|
|
161
169
|
If missing, materialist skips materialising the resource itself, but will continue
|
|
162
170
|
with any other functionality -- such as `materialize_link`.
|
|
163
171
|
|
|
164
|
-
|
|
165
172
|
#### `source_key <column> <url_parser_block> (default: url)`
|
|
166
173
|
describes the column used to persist the unique identifier parsed from the url_parser_block.
|
|
167
174
|
By default the column used is `:source_url` and the original `url` is used as the identifier.
|
|
@@ -1,36 +1,44 @@
|
|
|
1
1
|
require 'active_support/inflector'
|
|
2
|
-
require_relative './
|
|
2
|
+
require_relative './workers/event'
|
|
3
|
+
require_relative './materializer_factory'
|
|
3
4
|
|
|
4
5
|
module Materialist
|
|
5
6
|
class EventHandler
|
|
6
7
|
|
|
7
8
|
DEFAULT_SIDEKIQ_OPTIONS = { retry: 10 }.freeze
|
|
8
9
|
|
|
9
|
-
def initialize
|
|
10
|
-
end
|
|
11
|
-
|
|
12
10
|
def on_events_received(batch)
|
|
13
|
-
batch.each { |event| call(event) if should_materialize?(event
|
|
11
|
+
batch.each { |event| call(event) if should_materialize?(topic(event)) }
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def call(event)
|
|
17
|
-
worker.perform_async(event)
|
|
15
|
+
worker(topic(event)).perform_async(event)
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
private
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
def topic(event)
|
|
21
|
+
event['topic'].to_s
|
|
22
|
+
end
|
|
23
23
|
|
|
24
24
|
def should_materialize?(topic)
|
|
25
|
-
Materialist.configuration.topics.include?(topic
|
|
25
|
+
Materialist.configuration.topics.include?(topic)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sidekiq_options(topic)
|
|
29
|
+
[
|
|
30
|
+
DEFAULT_SIDEKIQ_OPTIONS,
|
|
31
|
+
Materialist.configuration.sidekiq_options,
|
|
32
|
+
materializer_sidekiq_options(topic)
|
|
33
|
+
].inject(:merge)
|
|
26
34
|
end
|
|
27
35
|
|
|
28
|
-
def
|
|
29
|
-
|
|
36
|
+
def worker(topic)
|
|
37
|
+
Materialist::Workers::Event.set(sidekiq_options(topic))
|
|
30
38
|
end
|
|
31
39
|
|
|
32
|
-
def
|
|
33
|
-
Materialist::
|
|
40
|
+
def materializer_sidekiq_options(topic)
|
|
41
|
+
Materialist::MaterializerFactory.class_from_topic(topic)._sidekiq_options
|
|
34
42
|
end
|
|
35
43
|
end
|
|
36
44
|
end
|
|
@@ -1,41 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
require 'active_support/inflector'
|
|
1
|
+
require_relative 'workers/event'
|
|
3
2
|
|
|
3
|
+
# This class is here for backwards compatibility with pre 3.1 versions. It can be removed with the
|
|
4
|
+
# next major version (4.0)
|
|
4
5
|
module Materialist
|
|
5
|
-
class EventWorker
|
|
6
|
-
include Sidekiq::Worker
|
|
7
|
-
|
|
6
|
+
class EventWorker < Workers::Event
|
|
8
7
|
def perform(event)
|
|
9
|
-
|
|
10
|
-
action = event['type'].to_sym
|
|
11
|
-
timestamp = event['t']
|
|
12
|
-
|
|
13
|
-
materializer = "#{topic.to_s.singularize.classify}Materializer".constantize
|
|
14
|
-
materializer.perform(event['url'], action)
|
|
15
|
-
|
|
16
|
-
report_latency(topic, timestamp) if timestamp
|
|
17
|
-
report_stats(topic, action, :success)
|
|
18
|
-
rescue
|
|
19
|
-
report_stats(topic, action, :failure)
|
|
20
|
-
raise
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def report_latency(topic, timestamp)
|
|
26
|
-
t = (Time.now.to_f - (timestamp.to_i / 1e3)).round(1)
|
|
27
|
-
Materialist.configuration.metrics_client.histogram(
|
|
28
|
-
"materialist.event_latency",
|
|
29
|
-
t,
|
|
30
|
-
tags: ["topic:#{topic}"]
|
|
31
|
-
)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def report_stats(topic, action, kind)
|
|
35
|
-
Materialist.configuration.metrics_client.increment(
|
|
36
|
-
"materialist.event_worker.#{kind}",
|
|
37
|
-
tags: ["action:#{action}", "topic:#{topic}"]
|
|
38
|
-
)
|
|
8
|
+
super(event)
|
|
39
9
|
end
|
|
40
10
|
end
|
|
41
11
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Materialist
|
|
2
|
+
module Materializer
|
|
3
|
+
module Internals
|
|
4
|
+
module ClassMethods
|
|
5
|
+
attr_reader :__materialist_options, :__materialist_dsl_mapping_stack
|
|
6
|
+
|
|
7
|
+
def perform(url, action)
|
|
8
|
+
materializer = Materializer.new(url, self)
|
|
9
|
+
action == :delete ? materializer.destroy : materializer.upsert
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def _sidekiq_options
|
|
13
|
+
__materialist_options[:sidekiq_options] || {}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Materialist
|
|
2
|
+
module Materializer
|
|
3
|
+
module Internals
|
|
4
|
+
module DSL
|
|
5
|
+
def materialize_link(key, topic: key)
|
|
6
|
+
__materialist_options[:links_to_materialize][key] = { topic: topic }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def capture(key, as: key)
|
|
10
|
+
__materialist_dsl_mapping_stack.last << FieldMapping.new(key: key, as: as)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def capture_link_href(key, as:)
|
|
14
|
+
__materialist_dsl_mapping_stack.last << LinkHrefMapping.new(key: key, as: as)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def link(key)
|
|
18
|
+
link_mapping = LinkMapping.new(key: key)
|
|
19
|
+
__materialist_dsl_mapping_stack.last << link_mapping
|
|
20
|
+
__materialist_dsl_mapping_stack << link_mapping.mapping
|
|
21
|
+
yield
|
|
22
|
+
__materialist_dsl_mapping_stack.pop
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def persist_to(klass)
|
|
26
|
+
__materialist_options[:model_class] = klass
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def sidekiq_options(options)
|
|
30
|
+
__materialist_options[:sidekiq_options] = options
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def source_key(key, &url_parser_block)
|
|
34
|
+
__materialist_options[:source_key] = key
|
|
35
|
+
__materialist_options[:url_parser] = url_parser_block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def before_upsert(*method_array)
|
|
39
|
+
__materialist_options[:before_upsert] = method_array
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def after_upsert(*method_array)
|
|
43
|
+
__materialist_options[:after_upsert] = method_array
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def after_destroy(*method_array)
|
|
47
|
+
__materialist_options[:after_destroy] = method_array
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def before_destroy(*method_array)
|
|
51
|
+
__materialist_options[:before_destroy] = method_array
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require 'routemaster/api_client'
|
|
2
|
+
require_relative '../../workers/event'
|
|
3
|
+
|
|
4
|
+
module Materialist
|
|
5
|
+
module Materializer
|
|
6
|
+
module Internals
|
|
7
|
+
class Materializer
|
|
8
|
+
def initialize(url, klass)
|
|
9
|
+
@url = url
|
|
10
|
+
@instance = klass.new
|
|
11
|
+
@options = klass.__materialist_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def upsert(retry_on_race_condition: true)
|
|
15
|
+
return unless root_resource
|
|
16
|
+
|
|
17
|
+
if materialize_self?
|
|
18
|
+
upsert_record.tap do |entity|
|
|
19
|
+
send_messages(after_upsert, entity) unless after_upsert.nil?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
materialize_links
|
|
24
|
+
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
|
|
25
|
+
# when there is a race condition and uniqueness of :source_url
|
|
26
|
+
# is enforced by database index, this error is raised
|
|
27
|
+
# so we simply try upsert again
|
|
28
|
+
# if error is due to another type of uniqueness constraint
|
|
29
|
+
# second call will also fail and error would bubble up
|
|
30
|
+
retry_on_race_condition ?
|
|
31
|
+
upsert(retry_on_race_condition: false) :
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
return unless materialize_self?
|
|
37
|
+
model_class.find_by(source_lookup(url)).tap do |entity|
|
|
38
|
+
send_messages(before_destroy, entity) unless before_destroy.nil?
|
|
39
|
+
entity.destroy!.tap do |entity|
|
|
40
|
+
send_messages(after_destroy, entity) unless after_destroy.nil?
|
|
41
|
+
end if entity
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
attr_reader :url, :instance, :options
|
|
48
|
+
|
|
49
|
+
def materialize_self?
|
|
50
|
+
options.include? :model_class
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def upsert_record
|
|
54
|
+
model_class.find_or_initialize_by(source_lookup(url)).tap do |entity|
|
|
55
|
+
send_messages(before_upsert, entity) unless before_upsert.nil?
|
|
56
|
+
entity.update_attributes! attributes
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def materialize_links
|
|
61
|
+
(options[:links_to_materialize] || [])
|
|
62
|
+
.each { |key, opts| materialize_link(key, opts) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def materialize_link(key, opts)
|
|
66
|
+
return unless root_resource.body._links.include?(key)
|
|
67
|
+
|
|
68
|
+
# this can't happen asynchronously
|
|
69
|
+
# because the handler options are unavailable in this context
|
|
70
|
+
# :(
|
|
71
|
+
::Materialist::Workers::Event.new.perform({
|
|
72
|
+
'topic' => opts[:topic],
|
|
73
|
+
'url' => root_resource.body._links[key].href,
|
|
74
|
+
'type' => 'noop'
|
|
75
|
+
})
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def mapping
|
|
79
|
+
options.fetch :mapping
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def before_upsert
|
|
83
|
+
options[:before_upsert]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def after_upsert
|
|
87
|
+
options[:after_upsert]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def before_destroy
|
|
91
|
+
options[:before_destroy]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def after_destroy
|
|
95
|
+
options[:after_destroy]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def model_class
|
|
99
|
+
options.fetch(:model_class).to_s.camelize.constantize
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def source_key
|
|
103
|
+
options.fetch(:source_key, :source_url)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def url_parser
|
|
107
|
+
options[:url_parser] || ->url { url }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def source_lookup(url)
|
|
111
|
+
@_source_lookup ||= { source_key => url_parser.call(url) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def attributes
|
|
115
|
+
build_attributes root_resource, mapping
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def root_resource
|
|
119
|
+
@_root_resource ||= resource_at(url)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_attributes(resource, mapping)
|
|
123
|
+
return {} unless resource
|
|
124
|
+
|
|
125
|
+
mapping.inject({}) do |result, m|
|
|
126
|
+
case m
|
|
127
|
+
when FieldMapping
|
|
128
|
+
result.tap { |r| r[m.as] = resource.body[m.key] }
|
|
129
|
+
when LinkHrefMapping
|
|
130
|
+
result.tap do |r|
|
|
131
|
+
if resource.body._links.include?(m.key)
|
|
132
|
+
r[m.as] = resource.body._links[m.key].href
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
when LinkMapping
|
|
136
|
+
resource.body._links.include?(m.key) ?
|
|
137
|
+
result.merge(build_attributes(resource_at(resource.send(m.key).url), m.mapping || [])) :
|
|
138
|
+
result
|
|
139
|
+
else
|
|
140
|
+
result
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def resource_at(url)
|
|
146
|
+
api_client.get(url, options: { enable_caching: false })
|
|
147
|
+
rescue Routemaster::Errors::ResourceNotFound
|
|
148
|
+
# this is due to a race condition between an upsert event
|
|
149
|
+
# and a :delete event
|
|
150
|
+
# when this happens we should silently ignore the case
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def api_client
|
|
155
|
+
@_api_client ||= Routemaster::APIClient.new(
|
|
156
|
+
response_class: Routemaster::Responses::HateoasResponse
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def send_messages(messages, arguments)
|
|
161
|
+
messages.each { |message| instance.send(message, arguments) }
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative './internals/field_mapping'
|
|
2
|
+
require_relative './internals/link_mapping'
|
|
3
|
+
require_relative './internals/link_href_mapping'
|
|
4
|
+
require_relative './internals/class_methods'
|
|
5
|
+
require_relative './internals/dsl'
|
|
6
|
+
require_relative './internals/materializer'
|
|
7
|
+
|
|
8
|
+
module Materialist
|
|
9
|
+
module Materializer
|
|
10
|
+
module Internals
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
require 'routemaster/api_client'
|
|
3
|
-
require_relative './event_worker'
|
|
1
|
+
require_relative './materializer/internals'
|
|
4
2
|
|
|
5
3
|
module Materialist
|
|
6
4
|
module Materializer
|
|
@@ -16,250 +14,5 @@ module Materialist
|
|
|
16
14
|
})
|
|
17
15
|
base.instance_variable_set(:@__materialist_dsl_mapping_stack, [root_mapping])
|
|
18
16
|
end
|
|
19
|
-
|
|
20
|
-
module Internals
|
|
21
|
-
class FieldMapping
|
|
22
|
-
def initialize(key:, as:)
|
|
23
|
-
@key = key
|
|
24
|
-
@as = as
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
attr_reader :key, :as
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
class LinkMapping
|
|
31
|
-
def initialize(key:)
|
|
32
|
-
@key = key
|
|
33
|
-
@mapping = []
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
attr_reader :key, :mapping
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
class LinkHrefMapping
|
|
40
|
-
def initialize(key:, as:)
|
|
41
|
-
@key = key
|
|
42
|
-
@as = as
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
attr_reader :key, :as
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
module ClassMethods
|
|
49
|
-
attr_reader :__materialist_options, :__materialist_dsl_mapping_stack
|
|
50
|
-
|
|
51
|
-
def perform(url, action)
|
|
52
|
-
materializer = Materializer.new(url, self)
|
|
53
|
-
action == :delete ? materializer.destroy : materializer.upsert
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
module DSL
|
|
58
|
-
|
|
59
|
-
def materialize_link(key, topic: key)
|
|
60
|
-
__materialist_options[:links_to_materialize][key] = { topic: topic }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def capture(key, as: key)
|
|
64
|
-
__materialist_dsl_mapping_stack.last << FieldMapping.new(key: key, as: as)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def capture_link_href(key, as:)
|
|
68
|
-
__materialist_dsl_mapping_stack.last << LinkHrefMapping.new(key: key, as: as)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def link(key)
|
|
72
|
-
link_mapping = LinkMapping.new(key: key)
|
|
73
|
-
__materialist_dsl_mapping_stack.last << link_mapping
|
|
74
|
-
__materialist_dsl_mapping_stack << link_mapping.mapping
|
|
75
|
-
yield
|
|
76
|
-
__materialist_dsl_mapping_stack.pop
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def persist_to(klass)
|
|
80
|
-
__materialist_options[:model_class] = klass
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def source_key(key, &url_parser_block)
|
|
84
|
-
__materialist_options[:source_key] = key
|
|
85
|
-
__materialist_options[:url_parser] = url_parser_block
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def before_upsert(*method_array)
|
|
89
|
-
__materialist_options[:before_upsert] = method_array
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def after_upsert(*method_array)
|
|
93
|
-
__materialist_options[:after_upsert] = method_array
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def after_destroy(*method_array)
|
|
97
|
-
__materialist_options[:after_destroy] = method_array
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def before_destroy(*method_array)
|
|
101
|
-
__materialist_options[:before_destroy] = method_array
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
class Materializer
|
|
106
|
-
|
|
107
|
-
def initialize(url, klass)
|
|
108
|
-
@url = url
|
|
109
|
-
@instance = klass.new
|
|
110
|
-
@options = klass.__materialist_options
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def upsert(retry_on_race_condition: true)
|
|
114
|
-
return unless root_resource
|
|
115
|
-
|
|
116
|
-
if materialize_self?
|
|
117
|
-
upsert_record.tap do |entity|
|
|
118
|
-
send_messages(after_upsert, entity) unless after_upsert.nil?
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
materialize_links
|
|
123
|
-
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
|
|
124
|
-
# when there is a race condition and uniqueness of :source_url
|
|
125
|
-
# is enforced by database index, this error is raised
|
|
126
|
-
# so we simply try upsert again
|
|
127
|
-
# if error is due to another type of uniqueness constraint
|
|
128
|
-
# second call will also fail and error would bubble up
|
|
129
|
-
retry_on_race_condition ?
|
|
130
|
-
upsert(retry_on_race_condition: false) :
|
|
131
|
-
raise
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def destroy
|
|
135
|
-
return unless materialize_self?
|
|
136
|
-
model_class.find_by(source_lookup(url)).tap do |entity|
|
|
137
|
-
send_messages(before_destroy, entity) unless before_destroy.nil?
|
|
138
|
-
entity.destroy!.tap do |entity|
|
|
139
|
-
send_messages(after_destroy, entity) unless after_destroy.nil?
|
|
140
|
-
end if entity
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
private
|
|
145
|
-
|
|
146
|
-
attr_reader :url, :instance, :options
|
|
147
|
-
|
|
148
|
-
def materialize_self?
|
|
149
|
-
options.include? :model_class
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def upsert_record
|
|
153
|
-
model_class.find_or_initialize_by(source_lookup(url)).tap do |entity|
|
|
154
|
-
send_messages(before_upsert, entity) unless before_upsert.nil?
|
|
155
|
-
entity.update_attributes! attributes
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def materialize_links
|
|
160
|
-
(options[:links_to_materialize] || [])
|
|
161
|
-
.each { |key, opts| materialize_link(key, opts) }
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def materialize_link(key, opts)
|
|
165
|
-
return unless root_resource.body._links.include?(key)
|
|
166
|
-
|
|
167
|
-
# this can't happen asynchronously
|
|
168
|
-
# because the handler options are unavailable in this context
|
|
169
|
-
# :(
|
|
170
|
-
::Materialist::EventWorker.new.perform({
|
|
171
|
-
'topic' => opts[:topic],
|
|
172
|
-
'url' => root_resource.body._links[key].href,
|
|
173
|
-
'type' => 'noop'
|
|
174
|
-
})
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def mapping
|
|
178
|
-
options.fetch :mapping
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def before_upsert
|
|
182
|
-
options[:before_upsert]
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def after_upsert
|
|
186
|
-
options[:after_upsert]
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def before_destroy
|
|
190
|
-
options[:before_destroy]
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def after_destroy
|
|
194
|
-
options[:after_destroy]
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def model_class
|
|
198
|
-
options.fetch(:model_class).to_s.camelize.constantize
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def source_key
|
|
202
|
-
options.fetch(:source_key, :source_url)
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def url_parser
|
|
206
|
-
options[:url_parser] || ->url { url }
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def source_lookup(url)
|
|
210
|
-
@_source_lookup ||= { source_key => url_parser.call(url) }
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def attributes
|
|
214
|
-
build_attributes root_resource, mapping
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def root_resource
|
|
218
|
-
@_root_resource ||= resource_at(url)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def build_attributes(resource, mapping)
|
|
222
|
-
return {} unless resource
|
|
223
|
-
|
|
224
|
-
mapping.inject({}) do |result, m|
|
|
225
|
-
case m
|
|
226
|
-
when FieldMapping
|
|
227
|
-
result.tap { |r| r[m.as] = resource.body[m.key] }
|
|
228
|
-
when LinkHrefMapping
|
|
229
|
-
result.tap do |r|
|
|
230
|
-
if resource.body._links.include?(m.key)
|
|
231
|
-
r[m.as] = resource.body._links[m.key].href
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
when LinkMapping
|
|
235
|
-
resource.body._links.include?(m.key) ?
|
|
236
|
-
result.merge(build_attributes(resource_at(resource.send(m.key).url), m.mapping || [])) :
|
|
237
|
-
result
|
|
238
|
-
else
|
|
239
|
-
result
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def resource_at(url)
|
|
245
|
-
api_client.get(url, options: { enable_caching: false })
|
|
246
|
-
rescue Routemaster::Errors::ResourceNotFound
|
|
247
|
-
# this is due to a race condition between an upsert event
|
|
248
|
-
# and a :delete event
|
|
249
|
-
# when this happens we should silently ignore the case
|
|
250
|
-
nil
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def api_client
|
|
254
|
-
@_api_client ||= Routemaster::APIClient.new(
|
|
255
|
-
response_class: Routemaster::Responses::HateoasResponse
|
|
256
|
-
)
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
def send_messages(messages, arguments)
|
|
260
|
-
messages.each { |message| instance.send(message, arguments) }
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
17
|
end
|
|
265
18
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'sidekiq'
|
|
2
|
+
require_relative '../materializer_factory'
|
|
3
|
+
|
|
4
|
+
module Materialist
|
|
5
|
+
module Workers
|
|
6
|
+
class Event
|
|
7
|
+
include Sidekiq::Worker
|
|
8
|
+
|
|
9
|
+
def perform(event)
|
|
10
|
+
topic = event['topic']
|
|
11
|
+
action = event['type'].to_sym
|
|
12
|
+
timestamp = event['t']
|
|
13
|
+
|
|
14
|
+
materializer = Materialist::MaterializerFactory.class_from_topic(topic)
|
|
15
|
+
materializer.perform(event['url'], action)
|
|
16
|
+
|
|
17
|
+
report_latency(topic, timestamp) if timestamp
|
|
18
|
+
report_stats(topic, action, :success)
|
|
19
|
+
rescue
|
|
20
|
+
report_stats(topic, action, :failure)
|
|
21
|
+
raise
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def report_latency(topic, timestamp)
|
|
27
|
+
t = (Time.now.to_f - (timestamp.to_i / 1e3)).round(1)
|
|
28
|
+
Materialist.configuration.metrics_client.histogram(
|
|
29
|
+
"materialist.event_latency",
|
|
30
|
+
t,
|
|
31
|
+
tags: ["topic:#{topic}"]
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def report_stats(topic, action, kind)
|
|
36
|
+
Materialist.configuration.metrics_client.increment(
|
|
37
|
+
"materialist.event_worker.#{kind}",
|
|
38
|
+
tags: ["action:#{action}", "topic:#{topic}"]
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/materialist.rb
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
require 'materialist/event_handler'
|
|
3
|
-
require 'materialist/
|
|
3
|
+
require 'materialist/workers/event'
|
|
4
4
|
|
|
5
5
|
RSpec.describe Materialist::EventHandler do
|
|
6
|
+
let(:configuration) do
|
|
7
|
+
OpenStruct.new({
|
|
8
|
+
sidekiq_options: sidekiq_options,
|
|
9
|
+
topics: topics
|
|
10
|
+
})
|
|
11
|
+
end
|
|
6
12
|
let(:topics) {[]}
|
|
7
13
|
let(:sidekiq_options) {{}}
|
|
14
|
+
let(:materializer_class) { double(_sidekiq_options: {}) }
|
|
8
15
|
let(:worker_double) { double() }
|
|
9
16
|
|
|
10
17
|
before do
|
|
11
|
-
Materialist.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
allow(Materialist::EventWorker).to receive(:set)
|
|
17
|
-
.and_return worker_double
|
|
18
|
+
allow(Materialist).to receive(:configuration).and_return(configuration)
|
|
19
|
+
allow(Materialist::MaterializerFactory).to receive(:class_from_topic).and_return(materializer_class)
|
|
20
|
+
allow(Materialist::Workers::Event).to receive(:set).and_return(worker_double)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
describe "#on_events_received" do
|
|
@@ -50,35 +53,30 @@ RSpec.describe Materialist::EventHandler do
|
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
describe "#call" do
|
|
53
|
-
|
|
54
|
-
let(:perform) { subject.call event }
|
|
56
|
+
subject { described_class.new.call(event) }
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
let(:configuration) do
|
|
59
|
+
OpenStruct.new({
|
|
60
|
+
sidekiq_options: { unique: false, retry: 5 }
|
|
61
|
+
})
|
|
59
62
|
end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
let(:event) { { "topic" => topic, "data" => "data" } }
|
|
64
|
+
let(:topic) { "foobar" }
|
|
65
|
+
let(:materializer_class) { double(_sidekiq_options: materializer_sidekiq_options) }
|
|
66
|
+
let(:materializer_sidekiq_options) { { queue: :dedicated, retry: 2 } }
|
|
67
|
+
let(:expected_event_options) { { queue: :dedicated, retry: 2, unique: false } }
|
|
68
|
+
let(:worker_class) { double(perform_async: nil) }
|
|
69
|
+
|
|
70
|
+
before do
|
|
71
|
+
allow(Materialist).to receive(:configuration).and_return(configuration)
|
|
72
|
+
allow(Materialist::MaterializerFactory).to receive(:class_from_topic).with(topic).and_return(materializer_class)
|
|
73
|
+
allow(Materialist::Workers::Event).to receive(:set).with(expected_event_options).and_return(worker_class)
|
|
71
74
|
end
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
it "enqueues the event worker with sidekiq options merged from configuration, default and the materializer" do
|
|
77
|
+
subject
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
expect(Materialist::EventWorker).to receive(:set)
|
|
78
|
-
.with(retry: false)
|
|
79
|
-
expect(worker_double).to receive(:perform_async).with(event)
|
|
80
|
-
perform
|
|
81
|
-
end
|
|
79
|
+
expect(worker_class).to have_received(:perform_async).with(event)
|
|
82
80
|
end
|
|
83
81
|
end
|
|
84
82
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'materialist/materializer_factory'
|
|
3
|
+
|
|
4
|
+
RSpec.describe Materialist::MaterializerFactory do
|
|
5
|
+
class BuffaloMozarellaMaterializer; end
|
|
6
|
+
|
|
7
|
+
describe '.class_from_topic' do
|
|
8
|
+
subject { described_class.class_from_topic(topic) }
|
|
9
|
+
|
|
10
|
+
context 'when the materializer class exists' do
|
|
11
|
+
let(:topic) { 'buffalo_mozarella'}
|
|
12
|
+
|
|
13
|
+
it 'returns the class' do
|
|
14
|
+
is_expected.to eql(BuffaloMozarellaMaterializer)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context 'when the materializer class does not exist' do
|
|
19
|
+
let(:topic) { 'cheddar' }
|
|
20
|
+
|
|
21
|
+
it { is_expected.to be nil }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -5,7 +5,7 @@ require 'materialist/materializer'
|
|
|
5
5
|
RSpec.describe Materialist::Materializer do
|
|
6
6
|
uses_redis
|
|
7
7
|
|
|
8
|
-
describe "
|
|
8
|
+
describe ".perform" do
|
|
9
9
|
let!(:materializer_class) do
|
|
10
10
|
FoobarMaterializer = Class.new do
|
|
11
11
|
include Materialist::Materializer
|
|
@@ -71,6 +71,7 @@ RSpec.describe Materialist::Materializer do
|
|
|
71
71
|
|
|
72
72
|
let(:action) { :create }
|
|
73
73
|
let(:perform) { materializer_class.perform(source_url, action) }
|
|
74
|
+
let(:actions_called) { materializer_class.class_variable_get(:@@actions_called) }
|
|
74
75
|
|
|
75
76
|
it "materializes record in db" do
|
|
76
77
|
expect{perform}.to change{Foobar.count}.by 1
|
|
@@ -168,18 +169,17 @@ RSpec.describe Materialist::Materializer do
|
|
|
168
169
|
|
|
169
170
|
context "when {after, before}_upsert is configured" do
|
|
170
171
|
let!(:record) { Foobar.create!(source_url: source_url, name: 'mo') }
|
|
171
|
-
let(:actions_called) { materializer_class.class_variable_get(:@@actions_called) }
|
|
172
172
|
let!(:materializer_class) do
|
|
173
173
|
FoobarMaterializer = Class.new do
|
|
174
174
|
include Materialist::Materializer
|
|
175
|
-
|
|
175
|
+
cattr_accessor(:actions_called) { {} }
|
|
176
176
|
|
|
177
177
|
persist_to :foobar
|
|
178
178
|
before_upsert :before_hook
|
|
179
179
|
after_upsert :after_hook
|
|
180
180
|
|
|
181
|
-
def before_hook(entity);
|
|
182
|
-
def after_hook(entity);
|
|
181
|
+
def before_hook(entity); self.actions_called[:before_hook] = true; end
|
|
182
|
+
def after_hook(entity); self.actions_called[:after_hook] = true; end
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
185
|
|
|
@@ -198,16 +198,16 @@ RSpec.describe Materialist::Materializer do
|
|
|
198
198
|
let(:materializer_class) do
|
|
199
199
|
FoobarMaterializer = Class.new do
|
|
200
200
|
include Materialist::Materializer
|
|
201
|
-
|
|
201
|
+
cattr_accessor(:actions_called) { {} }
|
|
202
202
|
|
|
203
203
|
persist_to :foobar
|
|
204
204
|
before_upsert :before_hook, :before_hook2
|
|
205
205
|
after_upsert :after_hook, :after_hook2
|
|
206
206
|
|
|
207
|
-
def before_hook(entity);
|
|
208
|
-
def before_hook2(entity);
|
|
209
|
-
def after_hook(entity);
|
|
210
|
-
def after_hook2(entity);
|
|
207
|
+
def before_hook(entity); self.actions_called[:before_hook] = true; end
|
|
208
|
+
def before_hook2(entity); self.actions_called[:before_hook2] = true; end
|
|
209
|
+
def after_hook(entity); self.actions_called[:after_hook] = true; end
|
|
210
|
+
def after_hook2(entity); self.actions_called[:after_hook2] = true; end
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
213
|
|
|
@@ -237,18 +237,17 @@ RSpec.describe Materialist::Materializer do
|
|
|
237
237
|
|
|
238
238
|
context "when {before, after}_destroy is configured" do
|
|
239
239
|
let!(:record) { Foobar.create!(source_url: source_url, name: 'mo') }
|
|
240
|
-
let(:actions_called) { materializer_class.class_variable_get(:@@actions_called) }
|
|
241
240
|
let!(:materializer_class) do
|
|
242
241
|
FoobarMaterializer = Class.new do
|
|
243
242
|
include Materialist::Materializer
|
|
244
|
-
|
|
243
|
+
cattr_accessor(:actions_called) { {} }
|
|
245
244
|
|
|
246
245
|
persist_to :foobar
|
|
247
246
|
before_destroy :before_hook
|
|
248
247
|
after_destroy :after_hook
|
|
249
248
|
|
|
250
|
-
def before_hook(entity);
|
|
251
|
-
def after_hook(entity);
|
|
249
|
+
def before_hook(entity); self.actions_called[:before_hook] = true; end
|
|
250
|
+
def after_hook(entity); self.actions_called[:after_hook] = true; end
|
|
252
251
|
end
|
|
253
252
|
end
|
|
254
253
|
|
|
@@ -280,16 +279,16 @@ RSpec.describe Materialist::Materializer do
|
|
|
280
279
|
let(:materializer_class) do
|
|
281
280
|
FoobarMaterializer = Class.new do
|
|
282
281
|
include Materialist::Materializer
|
|
283
|
-
|
|
282
|
+
cattr_accessor(:actions_called) { {} }
|
|
284
283
|
|
|
285
284
|
persist_to :foobar
|
|
286
285
|
before_destroy :before_hook, :before_hook2
|
|
287
286
|
after_destroy :after_hook, :after_hook2
|
|
288
287
|
|
|
289
|
-
def before_hook(entity);
|
|
290
|
-
def before_hook2(entity);
|
|
291
|
-
def after_hook(entity);
|
|
292
|
-
def after_hook2(entity);
|
|
288
|
+
def before_hook(entity); self.actions_called[:before_hook] = true; end
|
|
289
|
+
def before_hook2(entity); self.actions_called[:before_hook2] = true; end
|
|
290
|
+
def after_hook(entity); self.actions_called[:after_hook] = true; end
|
|
291
|
+
def after_hook2(entity); self.actions_called[:after_hook2] = true; end
|
|
293
292
|
end
|
|
294
293
|
end
|
|
295
294
|
|
|
@@ -397,4 +396,35 @@ RSpec.describe Materialist::Materializer do
|
|
|
397
396
|
end
|
|
398
397
|
end
|
|
399
398
|
end
|
|
399
|
+
|
|
400
|
+
describe "._sidekiq_options" do
|
|
401
|
+
subject { materializer_class._sidekiq_options }
|
|
402
|
+
|
|
403
|
+
context "when sidekiq options have been set" do
|
|
404
|
+
let(:materializer_class) do
|
|
405
|
+
Class.new do
|
|
406
|
+
include Materialist::Materializer
|
|
407
|
+
|
|
408
|
+
sidekiq_options queue: :dedicated, option: 'value'
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
it "returns the options" do
|
|
414
|
+
is_expected.to eql(queue: :dedicated, option: 'value')
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
context "when sidekiq options have not been set" do
|
|
419
|
+
let(:materializer_class) do
|
|
420
|
+
Class.new do
|
|
421
|
+
include Materialist::Materializer
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
it "returns empty hash" do
|
|
426
|
+
is_expected.to eql({})
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
400
430
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
-
require 'materialist/
|
|
2
|
+
require 'materialist/workers/event'
|
|
3
3
|
|
|
4
|
-
RSpec.describe Materialist::
|
|
4
|
+
RSpec.describe Materialist::Workers::Event do
|
|
5
5
|
describe "#perform" do
|
|
6
6
|
let(:source_url) { 'https://service.dev/foobars/1' }
|
|
7
7
|
let(:event) {{ 'topic' => :foobar, 'url' => source_url, 'type' => 'noop' }}
|
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.1.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-
|
|
11
|
+
date: 2018-04-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sidekiq
|
|
@@ -205,11 +205,21 @@ files:
|
|
|
205
205
|
- lib/materialist/event_worker.rb
|
|
206
206
|
- lib/materialist/materialized_record.rb
|
|
207
207
|
- lib/materialist/materializer.rb
|
|
208
|
+
- lib/materialist/materializer/internals.rb
|
|
209
|
+
- lib/materialist/materializer/internals/class_methods.rb
|
|
210
|
+
- lib/materialist/materializer/internals/dsl.rb
|
|
211
|
+
- lib/materialist/materializer/internals/field_mapping.rb
|
|
212
|
+
- lib/materialist/materializer/internals/link_href_mapping.rb
|
|
213
|
+
- lib/materialist/materializer/internals/link_mapping.rb
|
|
214
|
+
- lib/materialist/materializer/internals/materializer.rb
|
|
215
|
+
- lib/materialist/materializer_factory.rb
|
|
216
|
+
- lib/materialist/workers/event.rb
|
|
208
217
|
- materialist.gemspec
|
|
209
218
|
- spec/materialist/event_handler_spec.rb
|
|
210
|
-
- spec/materialist/event_worker_spec.rb
|
|
211
219
|
- spec/materialist/materialized_record_spec.rb
|
|
220
|
+
- spec/materialist/materializer_factory_spec.rb
|
|
212
221
|
- spec/materialist/materializer_spec.rb
|
|
222
|
+
- spec/materialist/workers/event_spec.rb
|
|
213
223
|
- spec/models.rb
|
|
214
224
|
- spec/schema.rb
|
|
215
225
|
- spec/spec_helper.rb
|
|
@@ -234,15 +244,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
234
244
|
version: '0'
|
|
235
245
|
requirements: []
|
|
236
246
|
rubyforge_project:
|
|
237
|
-
rubygems_version: 2.6.
|
|
247
|
+
rubygems_version: 2.6.13
|
|
238
248
|
signing_key:
|
|
239
249
|
specification_version: 4
|
|
240
250
|
summary: Utilities to materialize routemaster topics
|
|
241
251
|
test_files:
|
|
242
252
|
- spec/materialist/event_handler_spec.rb
|
|
243
|
-
- spec/materialist/event_worker_spec.rb
|
|
244
253
|
- spec/materialist/materialized_record_spec.rb
|
|
254
|
+
- spec/materialist/materializer_factory_spec.rb
|
|
245
255
|
- spec/materialist/materializer_spec.rb
|
|
256
|
+
- spec/materialist/workers/event_spec.rb
|
|
246
257
|
- spec/models.rb
|
|
247
258
|
- spec/schema.rb
|
|
248
259
|
- spec/spec_helper.rb
|