materialist 3.0.0 → 3.1.0

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
2
  SHA1:
3
- metadata.gz: 6c126eb6aa8d3e7fd91c9628f1a9a4a16e6476e8
4
- data.tar.gz: 3d20acfd6c6ae59358dcfffb0b6270d438d6f7e4
3
+ metadata.gz: 99e85711c6de342809785430291c11d6a11b3379
4
+ data.tar.gz: d6b3f3bfecd9ce4b20ee699c3a75028294f7ab5f
5
5
  SHA512:
6
- metadata.gz: 39310ca655d95cd3b51b6765da39f5e7ccb6479ca8df8680c5b6f27100cd7fd5479458d36b5baca80d80b1c8b29dec266453b2e2bd26c98dd4b65e01044b5351
7
- data.tar.gz: df9c7c8f9c59a6a1b640f011deb7e848ac2cb9e705b288bbb6d8238c1c08e3db5a4cca8014ddfa061236d52a13d60d6a1d239758a414e352adc09e9b1a6d8b64
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
- ### DSL
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 './event_worker'
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['topic']) }
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
- attr_reader :topics
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.to_s)
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 sidekiq_options
29
- DEFAULT_SIDEKIQ_OPTIONS.merge(Materialist.configuration.sidekiq_options)
36
+ def worker(topic)
37
+ Materialist::Workers::Event.set(sidekiq_options(topic))
30
38
  end
31
39
 
32
- def worker
33
- Materialist::EventWorker.set(sidekiq_options)
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
- require 'sidekiq'
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
- topic = event['topic']
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,14 @@
1
+ module Materialist
2
+ module Materializer
3
+ module Internals
4
+ class FieldMapping
5
+ def initialize(key:, as:)
6
+ @key = key
7
+ @as = as
8
+ end
9
+
10
+ attr_reader :key, :as
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Materialist
2
+ module Materializer
3
+ module Internals
4
+ class LinkHrefMapping
5
+ def initialize(key:, as:)
6
+ @key = key
7
+ @as = as
8
+ end
9
+
10
+ attr_reader :key, :as
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Materialist
2
+ module Materializer
3
+ module Internals
4
+ class LinkMapping
5
+ def initialize(key:)
6
+ @key = key
7
+ @mapping = []
8
+ end
9
+
10
+ attr_reader :key, :mapping
11
+ end
12
+ end
13
+ end
14
+ 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
- require 'active_support/inflector'
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,9 @@
1
+ module Materialist
2
+ class MaterializerFactory
3
+ def self.class_from_topic(topic)
4
+ "#{topic.to_s.singularize.classify}Materializer".constantize
5
+ rescue NameError
6
+ nil
7
+ end
8
+ end
9
+ 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,5 +1,5 @@
1
1
  require_relative './configuration'
2
2
 
3
3
  module Materialist
4
- VERSION = '3.0.0'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -1,20 +1,23 @@
1
1
  require 'spec_helper'
2
2
  require 'materialist/event_handler'
3
- require 'materialist/event_worker'
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.configure do |config|
12
- config.sidekiq_options = sidekiq_options
13
- config.topics = topics
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
- let(:event) { { "topic" => :foobar } }
54
- let(:perform) { subject.call event }
56
+ subject { described_class.new.call(event) }
55
57
 
56
- it "enqueues the event" do
57
- expect(worker_double).to receive(:perform_async).with(event)
58
- perform
58
+ let(:configuration) do
59
+ OpenStruct.new({
60
+ sidekiq_options: { unique: false, retry: 5 }
61
+ })
59
62
  end
60
-
61
- context "if queue name is privided" do
62
- let(:queue_name) { :some_queue_name }
63
- let(:sidekiq_options) {{ queue: queue_name }}
64
-
65
- it "enqueues the event in the given queue" do
66
- expect(Materialist::EventWorker).to receive(:set)
67
- .with(queue: queue_name, retry: 10)
68
- expect(worker_double).to receive(:perform_async).with(event)
69
- perform
70
- end
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
- context "when a retry is specified in options" do
74
- let(:sidekiq_options) {{ retry: false }}
76
+ it "enqueues the event worker with sidekiq options merged from configuration, default and the materializer" do
77
+ subject
75
78
 
76
- it "uses the given retry option for sidekiq" do
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 "#perform" do
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
- @@actions_called = {}
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); @@actions_called[:before_hook] = true; end
182
- def after_hook(entity); @@actions_called[:after_hook] = true; end
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
- @@actions_called = {}
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); @@actions_called[:before_hook] = true; end
208
- def before_hook2(entity); @@actions_called[:before_hook2] = true; end
209
- def after_hook(entity); @@actions_called[:after_hook] = true; end
210
- def after_hook2(entity); @@actions_called[:after_hook2] = true; end
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
- @@actions_called = {}
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); @@actions_called[:before_hook] = true; end
251
- def after_hook(entity); @@actions_called[:after_hook] = true; end
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
- @@actions_called = {}
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); @@actions_called[:before_hook] = true; end
290
- def before_hook2(entity); @@actions_called[:before_hook2] = true; end
291
- def after_hook(entity); @@actions_called[:after_hook] = true; end
292
- def after_hook2(entity); @@actions_called[:after_hook2] = true; end
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/event_worker'
2
+ require 'materialist/workers/event'
3
3
 
4
- RSpec.describe Materialist::EventWorker do
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.0.0
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-02-26 00:00:00.000000000 Z
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.8
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