deimos-ruby 1.10.2 → 1.12.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/CHANGELOG.md +19 -0
- data/Gemfile.lock +8 -8
- data/README.md +150 -16
- data/deimos-ruby.gemspec +1 -1
- data/docs/CONFIGURATION.md +4 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +1 -1
- data/lib/deimos/active_record_consume/message_consumption.rb +4 -3
- data/lib/deimos/active_record_consumer.rb +2 -2
- data/lib/deimos/active_record_producer.rb +3 -0
- data/lib/deimos/config/configuration.rb +29 -0
- data/lib/deimos/consume/batch_consumption.rb +2 -2
- data/lib/deimos/consume/message_consumption.rb +2 -2
- data/lib/deimos/consumer.rb +10 -0
- data/lib/deimos/producer.rb +4 -3
- data/lib/deimos/schema_backends/avro_base.rb +64 -1
- data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -1
- data/lib/deimos/schema_backends/base.rb +18 -2
- data/lib/deimos/schema_class/base.rb +67 -0
- data/lib/deimos/schema_class/enum.rb +24 -0
- data/lib/deimos/schema_class/record.rb +59 -0
- data/lib/deimos/shared_config.rb +5 -0
- data/lib/deimos/test_helpers.rb +70 -17
- data/lib/deimos/utils/schema_class.rb +29 -0
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +3 -0
- data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +15 -0
- data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +21 -0
- data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +65 -0
- data/lib/generators/deimos/schema_class_generator.rb +247 -0
- data/lib/tasks/deimos.rake +8 -0
- data/spec/active_record_batch_consumer_spec.rb +120 -110
- data/spec/active_record_consumer_spec.rb +97 -88
- data/spec/active_record_producer_spec.rb +38 -27
- data/spec/batch_consumer_spec.rb +37 -28
- data/spec/config/configuration_spec.rb +10 -3
- data/spec/consumer_spec.rb +94 -83
- data/spec/generators/active_record_generator_spec.rb +1 -0
- data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +206 -0
- data/spec/generators/schema_class_generator_spec.rb +186 -0
- data/spec/producer_spec.rb +110 -0
- data/spec/schema_classes/generated.rb +156 -0
- data/spec/schema_classes/my_nested_schema.rb +114 -0
- data/spec/schema_classes/my_schema.rb +53 -0
- data/spec/schema_classes/my_schema_key.rb +35 -0
- data/spec/schema_classes/my_schema_with_complex_types.rb +172 -0
- data/spec/schemas/com/my-namespace/Generated.avsc +6 -0
- data/spec/schemas/com/my-namespace/MySchemaWithComplexTypes.avsc +95 -0
- data/spec/spec_helper.rb +6 -1
- metadata +28 -4
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'deimos'
|
5
|
+
require 'deimos/schema_backends/avro_base'
|
6
|
+
require 'deimos/config/configuration'
|
7
|
+
|
8
|
+
# Generates new schema classes.
|
9
|
+
module Deimos
|
10
|
+
module Generators
|
11
|
+
# Generator for Schema Classes used for the IDE and consumer/producer interfaces
|
12
|
+
class SchemaClassGenerator < Rails::Generators::Base
|
13
|
+
|
14
|
+
SPECIAL_TYPES = %i(record enum).freeze
|
15
|
+
INITIALIZE_WHITESPACE = "\n#{' ' * 19}"
|
16
|
+
IGNORE_DEFAULTS = %w(message_id timestamp).freeze
|
17
|
+
SCHEMA_CLASS_FILE = 'schema_class.rb'
|
18
|
+
SCHEMA_RECORD_PATH = File.expand_path('schema_class/templates/schema_record.rb.tt', __dir__).freeze
|
19
|
+
SCHEMA_ENUM_PATH = File.expand_path('schema_class/templates/schema_enum.rb.tt', __dir__).freeze
|
20
|
+
|
21
|
+
source_root File.expand_path('schema_class/templates', __dir__)
|
22
|
+
|
23
|
+
no_commands do
|
24
|
+
# Retrieve the fields from this Avro Schema
|
25
|
+
# @param schema [Avro::Schema::NamedSchema]
|
26
|
+
# @return [Array<SchemaField>]
|
27
|
+
def fields(schema)
|
28
|
+
schema.fields.map do |field|
|
29
|
+
Deimos::SchemaField.new(field.name, field.type, [], field.default)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Converts Deimos::SchemaField's to String form for generated YARD docs
|
34
|
+
# @param schema_field [Deimos::SchemaField]
|
35
|
+
# @return [String] A string representation of the Type of this SchemaField
|
36
|
+
def deimos_field_type(schema_field)
|
37
|
+
_field_type(schema_field.type)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Generate a Schema Model Class and all of its Nested Records from a
|
41
|
+
# Deimos Consumer or Producer Configuration object
|
42
|
+
# @param schema_name [String]
|
43
|
+
# @param namespace [String]
|
44
|
+
# @param key_schema_name [String]
|
45
|
+
def generate_classes(schema_name, namespace, key_schema_name)
|
46
|
+
schema_base = Deimos.schema_backend(schema: schema_name, namespace: namespace)
|
47
|
+
schema_base.load_schema
|
48
|
+
if key_schema_name.present?
|
49
|
+
key_schema_base = Deimos.schema_backend(schema: key_schema_name, namespace: namespace)
|
50
|
+
key_schema_base.load_schema
|
51
|
+
generate_class_from_schema_base(key_schema_base)
|
52
|
+
end
|
53
|
+
generate_class_from_schema_base(schema_base, key_schema_base: key_schema_base)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param schema_base [Deimos::SchemaBackends::Base]
|
57
|
+
# @param key_schema_base[Avro::Schema::NamedSchema]
|
58
|
+
def generate_class_from_schema_base(schema_base, key_schema_base: nil)
|
59
|
+
schemas = schema_base.schema_store.schemas.values
|
60
|
+
sub_schemas = schemas.reject { |s| s.name == schema_base.schema }
|
61
|
+
@sub_schema_templates = sub_schemas.map do |schema|
|
62
|
+
_generate_class_template_from_schema(schema)
|
63
|
+
end
|
64
|
+
|
65
|
+
main_schema = schemas.find { |s| s.name == schema_base.schema }
|
66
|
+
class_template = _generate_class_template_from_schema(main_schema, key_schema_base)
|
67
|
+
@main_class_definition = class_template
|
68
|
+
|
69
|
+
file_prefix = main_schema.name.underscore
|
70
|
+
namespace_path = main_schema.namespace.tr('.', '/')
|
71
|
+
filename = "#{Deimos.config.schema.generated_class_path}/#{namespace_path}/#{file_prefix}.rb"
|
72
|
+
template(SCHEMA_CLASS_FILE, filename, force: true)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Format a given field into its appropriate to_h representation.
|
76
|
+
# @param field[Deimos::SchemaField]
|
77
|
+
# @return [String]
|
78
|
+
def field_to_h(field)
|
79
|
+
res = "'#{field.name}' => @#{field.name}"
|
80
|
+
field_base_type = _schema_base_class(field.type).type_sym
|
81
|
+
|
82
|
+
if %i(record enum).include?(field_base_type)
|
83
|
+
res += case field.type.type_sym
|
84
|
+
when :array
|
85
|
+
'.map { |v| v&.to_h }'
|
86
|
+
when :map
|
87
|
+
'.transform_values { |v| v&.to_h }'
|
88
|
+
else
|
89
|
+
'&.to_h'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
res + (field.name == @fields.last.name ? '' : ',')
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
desc 'Generate a class based on configured consumer and producers.'
|
99
|
+
# :nodoc:
|
100
|
+
def generate
|
101
|
+
_validate
|
102
|
+
Rails.logger.info("Generating schemas from Deimos.config to #{Deimos.config.schema.generated_class_path}")
|
103
|
+
Deimos.config.producer_objects.each do |config|
|
104
|
+
schema_name = config.schema
|
105
|
+
namespace = config.namespace || Deimos.config.producers.schema_namespace
|
106
|
+
key_schema_name = config.key_config[:schema]
|
107
|
+
generate_classes(schema_name, namespace, key_schema_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
Deimos.config.consumer_objects.each do |config|
|
111
|
+
schema_name = config.schema
|
112
|
+
namespace = config.namespace
|
113
|
+
key_schema_name = config.key_config[:schema]
|
114
|
+
generate_classes(schema_name, namespace, key_schema_name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Determines if Schema Class Generation can be run.
|
121
|
+
# @raise if Schema Backend is not of a Avro-based class
|
122
|
+
def _validate
|
123
|
+
backend = Deimos.config.schema.backend.to_s
|
124
|
+
raise 'Schema Class Generation requires an Avro-based Schema Backend' if backend !~ /^avro/
|
125
|
+
end
|
126
|
+
|
127
|
+
# @param schema[Avro::Schema::NamedSchema]
|
128
|
+
# @param key_schema_base[Avro::Schema::NamedSchema]
|
129
|
+
# @return [String]
|
130
|
+
def _generate_class_template_from_schema(schema, key_schema_base=nil)
|
131
|
+
_set_instance_variables(schema, key_schema_base)
|
132
|
+
|
133
|
+
temp = schema.is_a?(Avro::Schema::RecordSchema) ? _record_class_template : _enum_class_template
|
134
|
+
res = ERB.new(temp, nil, '-')
|
135
|
+
res.result(binding)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param schema[Avro::Schema::NamedSchema]
|
139
|
+
# @param key_schema_base[Avro::Schema::NamedSchema]
|
140
|
+
def _set_instance_variables(schema, key_schema_base=nil)
|
141
|
+
schema_is_record = schema.is_a?(Avro::Schema::RecordSchema)
|
142
|
+
@current_schema = schema
|
143
|
+
return unless schema_is_record
|
144
|
+
|
145
|
+
@fields = fields(schema)
|
146
|
+
if key_schema_base.present?
|
147
|
+
key_schema_base.load_schema
|
148
|
+
key_schema = key_schema_base.schema_store.schemas.values.first
|
149
|
+
@fields << Deimos::SchemaField.new('payload_key', key_schema, [], nil)
|
150
|
+
end
|
151
|
+
@initialization_definition = _initialization_definition
|
152
|
+
@field_assignments = _field_assignments
|
153
|
+
end
|
154
|
+
|
155
|
+
# Defines the initialization method for Schema Records with one keyword argument per line
|
156
|
+
# @return [String] A string which defines the method signature for the initialize method
|
157
|
+
def _initialization_definition
|
158
|
+
arguments = @fields.map do |schema_field|
|
159
|
+
arg = "#{schema_field.name}:"
|
160
|
+
arg += _field_default(schema_field)
|
161
|
+
arg.strip
|
162
|
+
end
|
163
|
+
|
164
|
+
result = "def initialize(#{arguments.first}"
|
165
|
+
arguments[1..-1].each_with_index do |arg, _i|
|
166
|
+
result += ",#{INITIALIZE_WHITESPACE}#{arg}"
|
167
|
+
end
|
168
|
+
"#{result})"
|
169
|
+
end
|
170
|
+
|
171
|
+
# @param [SchemaField]
|
172
|
+
# @return [String]
|
173
|
+
def _field_default(field)
|
174
|
+
default = field.default
|
175
|
+
return ' nil' if default == :no_default || default.nil? || IGNORE_DEFAULTS.include?(field.name)
|
176
|
+
|
177
|
+
case field.type.type_sym
|
178
|
+
when :string
|
179
|
+
" '#{default}'"
|
180
|
+
when :record
|
181
|
+
schema_name = Deimos::SchemaBackends::AvroBase.schema_classname(field.type)
|
182
|
+
class_instance = Utils::SchemaClass.instance(field.default, schema_name)
|
183
|
+
" #{class_instance.to_h}"
|
184
|
+
else
|
185
|
+
" #{default}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Overrides default attr accessor methods
|
190
|
+
# @return [Array<String>]
|
191
|
+
def _field_assignments
|
192
|
+
result = []
|
193
|
+
@fields.each do |field|
|
194
|
+
field_type = field.type.type_sym # Record, Union, Enum, Array or Map
|
195
|
+
schema_base_type = _schema_base_class(field.type)
|
196
|
+
field_base_type = _field_type(schema_base_type)
|
197
|
+
method_argument = %i(array map).include?(field_type) ? 'values' : 'value'
|
198
|
+
is_schema_class = %i(record enum).include?(schema_base_type.type_sym)
|
199
|
+
|
200
|
+
field_initialization = method_argument
|
201
|
+
|
202
|
+
if is_schema_class
|
203
|
+
field_initialization = "#{field_base_type}.initialize_from_value(value)"
|
204
|
+
end
|
205
|
+
|
206
|
+
result << {
|
207
|
+
field: field,
|
208
|
+
field_type: field_type,
|
209
|
+
is_schema_class: is_schema_class,
|
210
|
+
method_argument: method_argument,
|
211
|
+
deimos_type: deimos_field_type(field),
|
212
|
+
field_initialization: field_initialization
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
result
|
217
|
+
end
|
218
|
+
|
219
|
+
# Converts Avro::Schema::NamedSchema's to String form for generated YARD docs.
|
220
|
+
# Recursively handles the typing for Arrays, Maps and Unions.
|
221
|
+
# @param avro_schema [Avro::Schema::NamedSchema]
|
222
|
+
# @return [String] A string representation of the Type of this SchemaField
|
223
|
+
def _field_type(avro_schema)
|
224
|
+
Deimos::SchemaBackends::AvroBase.field_type(avro_schema)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns the base class for this schema. Decodes Arrays, Maps and Unions
|
228
|
+
# @param avro_schema [Avro::Schema::NamedSchema]
|
229
|
+
# @return [Avro::Schema::NamedSchema]
|
230
|
+
def _schema_base_class(avro_schema)
|
231
|
+
Deimos::SchemaBackends::AvroBase.schema_base_class(avro_schema)
|
232
|
+
end
|
233
|
+
|
234
|
+
# An ERB template for schema record classes
|
235
|
+
# @return [String]
|
236
|
+
def _record_class_template
|
237
|
+
File.read(SCHEMA_RECORD_PATH).strip
|
238
|
+
end
|
239
|
+
|
240
|
+
# An ERB template for schema enum classes
|
241
|
+
# @return [String]
|
242
|
+
def _enum_class_template
|
243
|
+
File.read(SCHEMA_ENUM_PATH).strip
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
data/lib/tasks/deimos.rake
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'phobos'
|
4
4
|
require 'phobos/cli'
|
5
|
+
require 'generators/deimos/schema_class_generator'
|
6
|
+
require 'optparse'
|
5
7
|
|
6
8
|
namespace :deimos do
|
7
9
|
desc 'Starts Deimos in the rails environment'
|
@@ -31,4 +33,10 @@ namespace :deimos do
|
|
31
33
|
Deimos::Utils::DbPoller.start!
|
32
34
|
end
|
33
35
|
|
36
|
+
desc 'Run Schema Model Generator'
|
37
|
+
task generate_schema_classes: :environment do
|
38
|
+
Rails.logger.info("Running deimos:generate_schema_classes")
|
39
|
+
Deimos::Generators::SchemaClassGenerator.start
|
40
|
+
end
|
41
|
+
|
34
42
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Wrapped in a module to prevent class leakage
|
4
4
|
module ActiveRecordBatchConsumerTest
|
5
|
-
describe Deimos::ActiveRecordConsumer do
|
5
|
+
describe Deimos::ActiveRecordConsumer, 'Batch Consumer' do
|
6
6
|
# Create ActiveRecord table and model
|
7
7
|
before(:all) do
|
8
8
|
ActiveRecord::Base.connection.create_table(:widgets, force: true) do |t|
|
@@ -68,133 +68,143 @@ module ActiveRecordBatchConsumerTest
|
|
68
68
|
test_consume_batch(MyBatchConsumer, payloads, keys: keys, call_original: true)
|
69
69
|
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
describe 'consume_batch' do
|
72
|
+
SCHEMA_CLASS_SETTINGS.each do |setting, use_schema_classes|
|
73
|
+
context "with Schema Class consumption #{setting}" do
|
74
|
+
before(:each) do
|
75
|
+
Deimos.configure { |config| config.schema.use_schema_classes = use_schema_classes }
|
76
|
+
end
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
{ key: 1,
|
79
|
-
payload: { test_id: 'abc', some_int: 3 } },
|
80
|
-
{ key: 2,
|
81
|
-
payload: { test_id: 'def', some_int: 4 } }
|
82
|
-
]
|
83
|
-
)
|
84
|
-
|
85
|
-
expect(all_widgets).
|
86
|
-
to match_array(
|
87
|
-
[
|
88
|
-
have_attributes(id: 1, test_id: 'abc', some_int: 3, updated_at: start, created_at: start),
|
89
|
-
have_attributes(id: 2, test_id: 'def', some_int: 4, updated_at: start, created_at: start)
|
90
|
-
]
|
91
|
-
)
|
92
|
-
end
|
78
|
+
it 'should handle an empty batch' do
|
79
|
+
expect { publish_batch([]) }.not_to raise_error
|
80
|
+
end
|
93
81
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
82
|
+
it 'should create records from a batch' do
|
83
|
+
publish_batch(
|
84
|
+
[
|
85
|
+
{ key: 1,
|
86
|
+
payload: { test_id: 'abc', some_int: 3 } },
|
87
|
+
{ key: 2,
|
88
|
+
payload: { test_id: 'def', some_int: 4 } }
|
89
|
+
]
|
90
|
+
)
|
101
91
|
|
102
|
-
|
103
|
-
|
92
|
+
expect(all_widgets).
|
93
|
+
to match_array(
|
94
|
+
[
|
95
|
+
have_attributes(id: 1, test_id: 'abc', some_int: 3, updated_at: start, created_at: start),
|
96
|
+
have_attributes(id: 2, test_id: 'def', some_int: 4, updated_at: start, created_at: start)
|
97
|
+
]
|
98
|
+
)
|
99
|
+
end
|
104
100
|
|
105
|
-
|
106
|
-
|
101
|
+
it 'should handle deleting a record that doesn\'t exist' do
|
102
|
+
publish_batch(
|
103
|
+
[
|
104
|
+
{ key: 1,
|
105
|
+
payload: nil }
|
106
|
+
]
|
107
|
+
)
|
107
108
|
|
108
|
-
|
109
|
-
|
110
|
-
{ key: 1,
|
111
|
-
payload: { test_id: 'abc', some_int: 3 } },
|
112
|
-
{ key: 1,
|
113
|
-
payload: nil }
|
114
|
-
]
|
115
|
-
)
|
109
|
+
expect(all_widgets).to be_empty
|
110
|
+
end
|
116
111
|
|
117
|
-
|
118
|
-
|
112
|
+
it 'should handle an update, followed by a delete in the correct order' do
|
113
|
+
Widget.create!(id: 1, test_id: 'abc', some_int: 2)
|
114
|
+
|
115
|
+
publish_batch(
|
116
|
+
[
|
117
|
+
{ key: 1,
|
118
|
+
payload: { test_id: 'abc', some_int: 3 } },
|
119
|
+
{ key: 1,
|
120
|
+
payload: nil }
|
121
|
+
]
|
122
|
+
)
|
119
123
|
|
120
|
-
|
121
|
-
|
124
|
+
expect(all_widgets).to be_empty
|
125
|
+
end
|
122
126
|
|
123
|
-
|
127
|
+
it 'should handle a delete, followed by an update in the correct order' do
|
128
|
+
Widget.create!(id: 1, test_id: 'abc', some_int: 2)
|
124
129
|
|
125
|
-
|
126
|
-
[
|
127
|
-
{ key: 1,
|
128
|
-
payload: nil },
|
129
|
-
{ key: 1,
|
130
|
-
payload: { test_id: 'abc', some_int: 3 } }
|
131
|
-
]
|
132
|
-
)
|
130
|
+
travel 1.day
|
133
131
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
132
|
+
publish_batch(
|
133
|
+
[
|
134
|
+
{ key: 1,
|
135
|
+
payload: nil },
|
136
|
+
{ key: 1,
|
137
|
+
payload: { test_id: 'abc', some_int: 3 } }
|
138
|
+
]
|
139
|
+
)
|
141
140
|
|
142
|
-
|
143
|
-
|
141
|
+
expect(all_widgets).
|
142
|
+
to match_array(
|
143
|
+
[
|
144
|
+
have_attributes(id: 1, test_id: 'abc', some_int: 3, updated_at: Time.zone.now, created_at: Time.zone.now)
|
145
|
+
]
|
146
|
+
)
|
147
|
+
end
|
144
148
|
|
145
|
-
|
149
|
+
it 'should handle a double update' do
|
150
|
+
Widget.create!(id: 1, test_id: 'abc', some_int: 2)
|
146
151
|
|
147
|
-
|
148
|
-
[
|
149
|
-
{ key: 1,
|
150
|
-
payload: { test_id: 'def', some_int: 3 } },
|
151
|
-
{ key: 1,
|
152
|
-
payload: { test_id: 'ghi', some_int: 4 } }
|
153
|
-
]
|
154
|
-
)
|
152
|
+
travel 1.day
|
155
153
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
154
|
+
publish_batch(
|
155
|
+
[
|
156
|
+
{ key: 1,
|
157
|
+
payload: { test_id: 'def', some_int: 3 } },
|
158
|
+
{ key: 1,
|
159
|
+
payload: { test_id: 'ghi', some_int: 4 } }
|
160
|
+
]
|
161
|
+
)
|
163
162
|
|
164
|
-
|
165
|
-
|
163
|
+
expect(all_widgets).
|
164
|
+
to match_array(
|
165
|
+
[
|
166
|
+
have_attributes(id: 1, test_id: 'ghi', some_int: 4, updated_at: Time.zone.now, created_at: start)
|
167
|
+
]
|
168
|
+
)
|
169
|
+
end
|
166
170
|
|
167
|
-
|
168
|
-
|
169
|
-
{ key: 1,
|
170
|
-
payload: nil },
|
171
|
-
{ key: 1,
|
172
|
-
payload: nil }
|
173
|
-
]
|
174
|
-
)
|
171
|
+
it 'should handle a double deletion' do
|
172
|
+
Widget.create!(id: 1, test_id: 'abc', some_int: 2)
|
175
173
|
|
176
|
-
|
177
|
-
|
174
|
+
publish_batch(
|
175
|
+
[
|
176
|
+
{ key: 1,
|
177
|
+
payload: nil },
|
178
|
+
{ key: 1,
|
179
|
+
payload: nil }
|
180
|
+
]
|
181
|
+
)
|
178
182
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
183
|
+
expect(all_widgets).to be_empty
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should ignore default scopes' do
|
187
|
+
Widget.create!(id: 1, test_id: 'abc', some_int: 2, deleted: true)
|
188
|
+
Widget.create!(id: 2, test_id: 'def', some_int: 3, deleted: true)
|
189
|
+
|
190
|
+
publish_batch(
|
191
|
+
[
|
192
|
+
{ key: 1,
|
193
|
+
payload: nil },
|
194
|
+
{ key: 2,
|
195
|
+
payload: { test_id: 'def', some_int: 5 } }
|
196
|
+
]
|
197
|
+
)
|
198
|
+
|
199
|
+
expect(all_widgets).
|
200
|
+
to match_array(
|
201
|
+
[
|
202
|
+
have_attributes(id: 2, test_id: 'def', some_int: 5)
|
203
|
+
]
|
204
|
+
)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
198
208
|
end
|
199
209
|
|
200
210
|
describe 'compacted mode' do
|