deimos-ruby 1.8.7 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -58,6 +58,11 @@ module Deimos
58
58
  attrs.each do |k, v|
59
59
  record.send("#{k}=", v)
60
60
  end
61
+ save_record(record)
62
+ end
63
+
64
+ # @param record [ActiveRecord::Base]
65
+ def save_record(record)
61
66
  record.created_at ||= Time.zone.now if record.respond_to?(:created_at)
62
67
  record.updated_at = Time.zone.now if record.respond_to?(:updated_at)
63
68
  record.save!
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fig_tree'
3
4
  require_relative 'phobos_config'
4
- require_relative 'configurable'
5
5
  require_relative '../metrics/mock'
6
6
  require_relative '../tracing/mock'
7
7
  require 'active_support/core_ext/numeric'
8
8
 
9
9
  # :nodoc:
10
10
  module Deimos
11
- include Configurable
11
+ include FigTree
12
12
 
13
13
  # :nodoc:
14
- class Configurable::ConfigStruct
14
+ class FigTree::ConfigStruct
15
15
  include Deimos::PhobosConfig
16
16
  end
17
17
 
@@ -60,7 +60,7 @@ module Deimos
60
60
  end
61
61
  end
62
62
 
63
- # @param kafka_config [Configurable::ConfigStruct]
63
+ # @param kafka_config [FigTree::ConfigStruct]
64
64
  def self.configure_producer_or_consumer(kafka_config)
65
65
  klass = kafka_config.class_name.constantize
66
66
  klass.class_eval do
@@ -327,19 +327,19 @@ module Deimos
327
327
  # These are the phobos "listener" configs. See CONFIGURATION.md for more
328
328
  # info.
329
329
  setting :group_id
330
- setting :max_concurrency
331
- setting :start_from_beginning
330
+ setting :max_concurrency, 1
331
+ setting :start_from_beginning, true
332
332
  setting :max_bytes_per_partition, 500.kilobytes
333
- setting :min_bytes
334
- setting :max_wait_time
333
+ setting :min_bytes, 1
334
+ setting :max_wait_time, 5
335
335
  setting :force_encoding
336
- setting :delivery
336
+ setting :delivery, :batch
337
337
  setting :backoff
338
- setting :session_timeout
339
- setting :offset_commit_interval
340
- setting :offset_commit_threshold
338
+ setting :session_timeout, 300
339
+ setting :offset_commit_interval, 10
340
+ setting :offset_commit_threshold, 0
341
341
  setting :offset_retention_time
342
- setting :heartbeat_interval
342
+ setting :heartbeat_interval, 10
343
343
  end
344
344
 
345
345
  setting_object :db_poller do
@@ -6,6 +6,9 @@ module Deimos
6
6
  module KafkaSource
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ DEPRECATION_WARNING = 'The kafka_producer interface will be deprecated ' \
10
+ 'in future releases. Please use kafka_producers instead.'
11
+
9
12
  included do
10
13
  after_create(:send_kafka_event_on_create)
11
14
  after_update(:send_kafka_event_on_update)
@@ -64,19 +67,12 @@ module Deimos
64
67
 
65
68
  # @return [Array<Deimos::ActiveRecordProducer>] the producers to run.
66
69
  def kafka_producers
67
- raise NotImplementedError if self.method(:kafka_producer).
68
- owner == Deimos::KafkaSource
69
-
70
- [self.kafka_producer]
71
- end
72
-
73
- # Deprecated - use #kafka_producers instead.
74
- # @return [Deimos::ActiveRecordProducer] the producer to use.
75
- def kafka_producer
76
- raise NotImplementedError if self.method(:kafka_producers).
77
- owner == Deimos::KafkaSource
70
+ if self.respond_to?(:kafka_producer)
71
+ Deimos.config.logger.warn(message: DEPRECATION_WARNING)
72
+ return [self.kafka_producer]
73
+ end
78
74
 
79
- self.kafka_producers.first
75
+ raise NotImplementedError
80
76
  end
81
77
 
82
78
  # This is an internal method, part of the activerecord_import gem. It's
@@ -40,8 +40,9 @@ module Deimos
40
40
  # Decode a payload with a schema. Public method.
41
41
  # @param payload [String]
42
42
  # @param schema [Symbol|String]
43
- # @return [Hash]
43
+ # @return [Hash,nil]
44
44
  def decode(payload, schema: nil)
45
+ return nil if payload.nil?
45
46
  decode_payload(payload, schema: schema || @schema)
46
47
  end
47
48
 
@@ -34,7 +34,7 @@ module Deimos
34
34
  end
35
35
  end
36
36
 
37
- config.before(:each) do
37
+ config.prepend_before(:each) do
38
38
  client = double('client').as_null_object
39
39
  allow(client).to receive(:time) do |*_args, &block|
40
40
  block.call
@@ -68,7 +68,7 @@ module Deimos
68
68
 
69
69
  # Grab the PollInfo or create if it doesn't exist.
70
70
  def retrieve_poll_info
71
- ActiveRecord::Base.connection.reconnect!
71
+ ActiveRecord::Base.connection.reconnect! unless ActiveRecord::Base.connection.open_transactions.positive?
72
72
  new_time = @config.start_from_beginning ? Time.new(0) : Time.zone.now
73
73
  @info = Deimos::PollInfo.find_by_producer(@config.producer_class) ||
74
74
  Deimos::PollInfo.create!(producer: @config.producer_class,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '1.8.7'
4
+ VERSION = '1.10.1'
5
5
  end
@@ -74,34 +74,34 @@ describe Deimos, 'configuration' do
74
74
  {
75
75
  topic: 'my_consume_topic',
76
76
  group_id: 'my_group_id',
77
- max_concurrency: nil,
78
- start_from_beginning: nil,
77
+ max_concurrency: 1,
78
+ start_from_beginning: true,
79
79
  max_bytes_per_partition: 524_288,
80
- min_bytes: nil,
81
- max_wait_time: nil,
80
+ min_bytes: 1,
81
+ max_wait_time: 5,
82
82
  force_encoding: nil,
83
- delivery: nil,
84
- session_timeout: nil,
85
- offset_commit_interval: nil,
86
- offset_commit_threshold: nil,
83
+ delivery: 'batch',
84
+ session_timeout: 300,
85
+ offset_commit_interval: 10,
86
+ offset_commit_threshold: 0,
87
87
  offset_retention_time: nil,
88
- heartbeat_interval: nil,
88
+ heartbeat_interval: 10,
89
89
  handler: 'ConsumerTest::MyConsumer'
90
90
  }, {
91
91
  topic: 'my_batch_consume_topic',
92
92
  group_id: 'my_batch_group_id',
93
- max_concurrency: nil,
94
- start_from_beginning: nil,
93
+ max_concurrency: 1,
94
+ start_from_beginning: true,
95
95
  max_bytes_per_partition: 500.kilobytes,
96
- min_bytes: nil,
97
- max_wait_time: nil,
96
+ min_bytes: 1,
97
+ max_wait_time: 5,
98
98
  force_encoding: nil,
99
99
  delivery: 'inline_batch',
100
- session_timeout: nil,
101
- offset_commit_interval: nil,
102
- offset_commit_threshold: nil,
100
+ session_timeout: 300,
101
+ offset_commit_interval: 10,
102
+ offset_commit_threshold: 0,
103
103
  offset_retention_time: nil,
104
- heartbeat_interval: nil,
104
+ heartbeat_interval: 10,
105
105
  handler: 'ConsumerTest::MyBatchConsumer'
106
106
  }
107
107
  ],
@@ -308,5 +308,74 @@ module KafkaSourceSpec
308
308
  }, widgets[1].id)
309
309
  end
310
310
  end
311
+
312
+ context 'with AR models that implement the kafka_producer interface' do
313
+ before(:each) do
314
+ # Dummy class we can include the mixin in. Has a backing table created
315
+ # earlier and has the import hook disabled
316
+ deprecated_class = Class.new(ActiveRecord::Base) do
317
+ include Deimos::KafkaSource
318
+ self.table_name = 'widgets'
319
+
320
+ # :nodoc:
321
+ def self.kafka_config
322
+ {
323
+ update: true,
324
+ delete: true,
325
+ import: false,
326
+ create: true
327
+ }
328
+ end
329
+
330
+ # :nodoc:
331
+ def self.kafka_producer
332
+ WidgetProducer
333
+ end
334
+ end
335
+ stub_const('WidgetDeprecated', deprecated_class)
336
+ WidgetDeprecated.reset_column_information
337
+ end
338
+
339
+ it 'logs a warning and sends the message as usual' do
340
+ expect(Deimos.config.logger).to receive(:warn).with({ message: WidgetDeprecated::DEPRECATION_WARNING })
341
+ widget = WidgetDeprecated.create(widget_id: 1, name: 'Widget 1')
342
+ expect('my-topic').to have_sent({
343
+ widget_id: 1,
344
+ name: 'Widget 1',
345
+ id: widget.id,
346
+ created_at: anything,
347
+ updated_at: anything
348
+ }, widget.id)
349
+ end
350
+ end
351
+
352
+ context 'with AR models that do not implement any producer interface' do
353
+ before(:each) do
354
+ # Dummy class we can include the mixin in. Has a backing table created
355
+ # earlier and has the import hook disabled
356
+ buggy_class = Class.new(ActiveRecord::Base) do
357
+ include Deimos::KafkaSource
358
+ self.table_name = 'widgets'
359
+
360
+ # :nodoc:
361
+ def self.kafka_config
362
+ {
363
+ update: true,
364
+ delete: true,
365
+ import: false,
366
+ create: true
367
+ }
368
+ end
369
+ end
370
+ stub_const('WidgetBuggy', buggy_class)
371
+ WidgetBuggy.reset_column_information
372
+ end
373
+
374
+ it 'raises a NotImplementedError exception' do
375
+ expect {
376
+ WidgetBuggy.create(widget_id: 1, name: 'Widget 1')
377
+ }.to raise_error(NotImplementedError)
378
+ end
379
+ end
311
380
  end
312
381
  end
@@ -26,4 +26,8 @@ describe Deimos::SchemaBackends::Base do
26
26
  backend.decode(payload, schema: 'schema2')
27
27
  end
28
28
 
29
+ it 'should return nil if passed nil' do
30
+ expect(backend.decode(nil, schema: 'schema2')).to be_nil
31
+ end
32
+
29
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.7
4
+ version: 1.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-14 00:00:00.000000000 Z
11
+ date: 2021-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro_turf
@@ -48,30 +48,44 @@ dependencies:
48
48
  name: ruby-kafka
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - "<"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.7'
53
+ version: '2'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - "<"
59
59
  - !ruby/object:Gem::Version
60
- version: '0.7'
60
+ version: '2'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: sigurd
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - '='
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: 0.0.1
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - '='
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: 0.0.1
75
+ - !ruby/object:Gem::Dependency
76
+ name: fig_tree
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 0.0.2
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 0.0.2
75
89
  - !ruby/object:Gem::Dependency
76
90
  name: activerecord-import
77
91
  requirement: !ruby/object:Gem::Requirement
@@ -370,7 +384,6 @@ files:
370
384
  - lib/deimos/backends/kafka_async.rb
371
385
  - lib/deimos/backends/test.rb
372
386
  - lib/deimos/batch_consumer.rb
373
- - lib/deimos/config/configurable.rb
374
387
  - lib/deimos/config/configuration.rb
375
388
  - lib/deimos/config/phobos_config.rb
376
389
  - lib/deimos/consume/batch_consumption.rb
@@ -386,7 +399,6 @@ files:
386
399
  - lib/deimos/metrics/provider.rb
387
400
  - lib/deimos/monkey_patches/phobos_cli.rb
388
401
  - lib/deimos/monkey_patches/phobos_producer.rb
389
- - lib/deimos/monkey_patches/ruby_kafka_heartbeat.rb
390
402
  - lib/deimos/poll_info.rb
391
403
  - lib/deimos/producer.rb
392
404
  - lib/deimos/railtie.rb
@@ -429,7 +441,6 @@ files:
429
441
  - spec/backends/kafka_async_spec.rb
430
442
  - spec/backends/kafka_spec.rb
431
443
  - spec/batch_consumer_spec.rb
432
- - spec/config/configurable_spec.rb
433
444
  - spec/config/configuration_spec.rb
434
445
  - spec/consumer_spec.rb
435
446
  - spec/deimos_spec.rb
@@ -513,7 +524,6 @@ test_files:
513
524
  - spec/backends/kafka_async_spec.rb
514
525
  - spec/backends/kafka_spec.rb
515
526
  - spec/batch_consumer_spec.rb
516
- - spec/config/configurable_spec.rb
517
527
  - spec/config/configuration_spec.rb
518
528
  - spec/consumer_spec.rb
519
529
  - spec/deimos_spec.rb
@@ -1,278 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/concern'
4
- require 'active_support/callbacks'
5
-
6
- module Deimos
7
- # Module to allow configuration. Loosely based off of the dry-configuration
8
- # gem but with several advantages:
9
- # - Works with Ruby 2.3.
10
- # - More succinct syntax using method_missing so you do not need to write
11
- # "config.whatever" and can just write "whatever".
12
- # - Use nested blocks:
13
- # Deimos.configure do
14
- # config.kafka.ssl do
15
- # enabled true
16
- # ca_cert_file 'my_file'
17
- # end
18
- # config.kafka do
19
- # ssl do
20
- # enabled true
21
- # end
22
- # end
23
- # end
24
- # - Allows for arrays of configurations:
25
- # Deimos.configure do |config|
26
- # config.producer do
27
- # class_name 'MyProducer'
28
- # topic 'MyTopic'
29
- # end
30
- # end
31
- # - Allows to call `configure` multiple times without crashing.
32
- # - Allows to lazy-set default values by passing a proc as a default:
33
- # Deimos.define_settings do |config|
34
- # setting :my_val, default_proc: proc { MyDefault.calculated_value }
35
- # end
36
- # - Support for setting up and automatically calling deprecated configurations.
37
- # - Support for configuration callbacks.
38
- module Configurable
39
- extend ActiveSupport::Concern
40
-
41
- ConfigValue = Struct.new(:value, :default_value, :default_proc, :deprecation) do
42
-
43
- # Reset value back to default.
44
- def reset!
45
- if self.value.is_a?(ConfigStruct)
46
- self.value.reset!
47
- else
48
- self.value = self.default_value
49
- end
50
- end
51
-
52
- # :nodoc:
53
- def clone_and_reset
54
- setting = ConfigValue.new(self.value, self.default_value,
55
- self.default_proc, self.deprecation)
56
- setting.reset!
57
- setting
58
- end
59
-
60
- end
61
-
62
- # Class that defines and keeps the configuration values.
63
- class ConfigStruct
64
- include ActiveSupport::Callbacks
65
-
66
- define_callbacks :configure
67
-
68
- # @param name [String]
69
- def initialize(name)
70
- @name = name
71
- @settings = {}
72
- @setting_objects = {}
73
- @setting_templates = {}
74
- end
75
-
76
- # Reset config back to default values.
77
- def reset!
78
- @setting_objects = @setting_templates.map { |k, _| [k, []] }.to_h
79
- @settings.values.each(&:reset!)
80
- end
81
-
82
- # Mark a configuration as deprecated and replaced with the new config.
83
- # @param old_config [String]
84
- # @param new_config [String]
85
- def deprecate(old_config, new_config)
86
- @settings[old_config.to_sym] ||= ConfigValue.new
87
- @settings[old_config.to_sym].deprecation = new_config
88
- end
89
-
90
- # :nodoc:
91
- def inspect
92
- "#{@name}: #{@settings.inspect} #{@setting_objects.inspect}"
93
- end
94
-
95
- # @return [Hash]
96
- def to_h
97
- @settings.map { |k, v| [k, v.value] }.to_h
98
- end
99
-
100
- # :nodoc:
101
- def clone_and_reset
102
- new_config = self.clone
103
- new_config.setting_objects = new_config.setting_objects.clone
104
- new_config.settings = new_config.settings.map { |k, v| [k, v.clone_and_reset] }.to_h
105
- new_config
106
- end
107
-
108
- # Define a setting template for an array of objects via a block:
109
- # setting_object :producer do
110
- # setting :topic
111
- # setting :class_name
112
- # end
113
- # This will create the `producer` method to define these values as well
114
- # as the `producer_objects` method to retrieve them.
115
- # @param name [Symbol]
116
- def setting_object(name, &block)
117
- new_config = ConfigStruct.new("#{@name}.#{name}")
118
- @setting_objects[name] = []
119
- @setting_templates[name] = new_config
120
- new_config.instance_eval(&block)
121
- end
122
-
123
- # Define a setting with the given name.
124
- # @param name [Symbol]
125
- # @param default_value [Object]
126
- # @param default_proc [Proc]
127
- def setting(name, default_value=nil, default_proc: nil, &block)
128
- if block_given?
129
- # Create a nested setting
130
- setting_config = @settings[name]&.value || ConfigStruct.new("#{@name}.#{name}")
131
- setting = ConfigValue.new
132
- setting.value = setting_config
133
- @settings[name] = setting
134
- setting_config.instance_eval(&block)
135
- else
136
- setting = ConfigValue.new
137
- setting.default_proc = default_proc
138
- setting.default_value = default_value
139
- setting.reset!
140
- @settings[name] = setting
141
- end
142
- end
143
-
144
- # :nodoc:
145
- def respond_to_missing?(method, include_all=true)
146
- method = method.to_s.sub(/=$/, '')
147
- method.ends_with?('objects') ||
148
- @setting_templates.key?(method.to_sym) ||
149
- @settings.key?(method.to_sym) ||
150
- super
151
- end
152
-
153
- # :nodoc:
154
- def method_missing(method, *args, &block)
155
- config_key = method.to_s.sub(/=$/, '').to_sym
156
-
157
- # Return the list of setting objects with the given name
158
- if config_key.to_s.end_with?('objects')
159
- return _setting_object_method(config_key)
160
- end
161
-
162
- # Define a new setting object with the given name
163
- if @setting_templates.key?(config_key) && block_given?
164
- return _new_setting_object_method(config_key, &block)
165
- end
166
-
167
- setting = @settings[config_key]
168
-
169
- if setting&.deprecation
170
- return _deprecated_config_method(method, *args)
171
- end
172
-
173
- return super unless setting
174
-
175
- if block_given?
176
- return _block_config_method(config_key, &block)
177
- end
178
-
179
- _default_config_method(config_key, *args)
180
- end
181
-
182
- protected
183
-
184
- # Only for the clone method
185
- attr_accessor :settings, :setting_objects
186
-
187
- private
188
-
189
- def _deprecated_config_method(method, *args)
190
- config_key = method.to_s.sub(/=$/, '').to_sym
191
- new_config = @settings[config_key].deprecation
192
- equals = method.to_s.end_with?('=') ? '=' : ''
193
- ActiveSupport::Deprecation.warn("config.#{config_key}#{equals} is deprecated - use config.#{new_config}#{equals}")
194
- obj = self
195
- messages = new_config.split('.')
196
- messages[0..-2].each do |message|
197
- obj = obj.send(message)
198
- end
199
- if args.length.positive?
200
- obj.send(messages[-1], args[0])
201
- else
202
- obj.send(messages[-1])
203
- end
204
- end
205
-
206
- # Get or set a value.
207
- def _default_config_method(config_key, *args)
208
- if args.length.positive?
209
- # Set the value
210
- @settings[config_key].value = args[0]
211
- else
212
- # Get the value
213
- setting = @settings[config_key]
214
- if setting.default_proc && setting.value.nil?
215
- setting.value = setting.default_proc.call
216
- end
217
- setting.value
218
- end
219
- end
220
-
221
- # Define a new setting object and use the passed block to define values.
222
- def _new_setting_object_method(config_key, &block)
223
- new_config = @setting_templates[config_key].clone_and_reset
224
- new_config.instance_eval(&block)
225
- @setting_objects[config_key] << new_config
226
- end
227
-
228
- # Return a setting object.
229
- def _setting_object_method(config_key)
230
- key = config_key.to_s.sub(/_objects$/, '').to_sym
231
- @setting_objects[key]
232
- end
233
-
234
- # Define new values inside a block.
235
- def _block_config_method(config_key, &block)
236
- unless @settings[config_key].value.is_a?(ConfigStruct)
237
- raise "Block called for #{config_key} but it is not a nested config!"
238
- end
239
-
240
- @settings[config_key].value.instance_eval(&block)
241
- end
242
- end
243
-
244
- # :nodoc:
245
- module ClassMethods
246
- # Define and redefine settings.
247
- def define_settings(&block)
248
- config.instance_eval(&block)
249
- end
250
-
251
- # Configure the settings with values.
252
- def configure(&block)
253
- if defined?(Rake) && defined?(Rake.application)
254
- tasks = Rake.application.top_level_tasks
255
- if tasks.any? { |t| %w(assets webpacker yarn).include?(t.split(':').first) }
256
- puts 'Skipping Deimos configuration since we are in JS/CSS compilation'
257
- return
258
- end
259
- end
260
- config.run_callbacks(:configure) do
261
- config.instance_eval(&block)
262
- end
263
- end
264
-
265
- # @return [ConfigStruct]
266
- def config
267
- @config ||= ConfigStruct.new('config')
268
- end
269
-
270
- # Pass a block to run after configuration is done.
271
- def after_configure(&block)
272
- mod = self
273
- config.class.set_callback(:configure, :after,
274
- proc { mod.instance_eval(&block) })
275
- end
276
- end
277
- end
278
- end