deimos-ruby 1.11.2 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -2
  3. data/Gemfile.lock +8 -8
  4. data/README.md +103 -0
  5. data/deimos-ruby.gemspec +1 -1
  6. data/docs/CONFIGURATION.md +4 -0
  7. data/lib/deimos/active_record_consume/batch_consumption.rb +7 -9
  8. data/lib/deimos/active_record_consume/message_consumption.rb +4 -3
  9. data/lib/deimos/active_record_consumer.rb +2 -2
  10. data/lib/deimos/active_record_producer.rb +3 -0
  11. data/lib/deimos/config/configuration.rb +29 -0
  12. data/lib/deimos/consume/batch_consumption.rb +2 -2
  13. data/lib/deimos/consume/message_consumption.rb +2 -2
  14. data/lib/deimos/consumer.rb +10 -0
  15. data/lib/deimos/producer.rb +4 -3
  16. data/lib/deimos/schema_backends/avro_base.rb +64 -1
  17. data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -1
  18. data/lib/deimos/schema_backends/base.rb +18 -2
  19. data/lib/deimos/schema_class/base.rb +62 -0
  20. data/lib/deimos/schema_class/enum.rb +24 -0
  21. data/lib/deimos/schema_class/record.rb +66 -0
  22. data/lib/deimos/shared_config.rb +5 -0
  23. data/lib/deimos/test_helpers.rb +43 -7
  24. data/lib/deimos/utils/schema_class.rb +29 -0
  25. data/lib/deimos/version.rb +1 -1
  26. data/lib/deimos.rb +21 -0
  27. data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +15 -0
  28. data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +21 -0
  29. data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +65 -0
  30. data/lib/generators/deimos/schema_class_generator.rb +247 -0
  31. data/lib/tasks/deimos.rake +8 -0
  32. data/spec/active_record_batch_consumer_spec.rb +120 -110
  33. data/spec/active_record_consumer_spec.rb +97 -88
  34. data/spec/active_record_producer_spec.rb +38 -27
  35. data/spec/batch_consumer_spec.rb +37 -28
  36. data/spec/config/configuration_spec.rb +10 -3
  37. data/spec/consumer_spec.rb +95 -84
  38. data/spec/generators/active_record_generator_spec.rb +1 -0
  39. data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +206 -0
  40. data/spec/generators/schema_class_generator_spec.rb +186 -0
  41. data/spec/producer_spec.rb +110 -0
  42. data/spec/schema_classes/generated.rb +156 -0
  43. data/spec/schema_classes/my_nested_schema.rb +114 -0
  44. data/spec/schema_classes/my_schema.rb +53 -0
  45. data/spec/schema_classes/my_schema_key.rb +35 -0
  46. data/spec/schema_classes/my_schema_with_complex_types.rb +172 -0
  47. data/spec/schemas/com/my-namespace/Generated.avsc +6 -0
  48. data/spec/schemas/com/my-namespace/MySchemaWithComplexTypes.avsc +95 -0
  49. data/spec/spec_helper.rb +6 -1
  50. metadata +28 -4
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Deimos
6
+ module SchemaClass
7
+ # Base Class for Schema Classes generated from Avro.
8
+ class Base
9
+
10
+ # :nodoc:
11
+ def initialize(*_args)
12
+ end
13
+
14
+ # Converts the object to a string that represents a JSON object
15
+ # @return [String] a JSON string
16
+ def to_json(*_args)
17
+ to_h.to_json
18
+ end
19
+
20
+ # Converts the object to a hash which can be used for debugging or comparing objects.
21
+ # @return [Hash] a hash representation of the payload
22
+ def as_json(_opts={})
23
+ JSON.parse(to_json)
24
+ end
25
+
26
+ # Converts the object attributes to a hash which can be used for Kafka
27
+ # @return [Hash] the payload as a hash.
28
+ def to_h
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # :nodoc:
33
+ def ==(other)
34
+ comparison = other
35
+ if other.class == self.class
36
+ comparison = other.as_json
37
+ end
38
+
39
+ comparison == self.as_json
40
+ end
41
+
42
+ # :nodoc:
43
+ def to_s
44
+ klass = self.class
45
+ "#{klass}(#{self.as_json.symbolize_keys.to_s[1..-2]})"
46
+ end
47
+
48
+ # Initializes this class from a given value
49
+ # @param value [Object]
50
+ def self.initialize_from_value(value)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ protected
55
+
56
+ # :nodoc:
57
+ def hash
58
+ as_json.hash
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'json'
5
+
6
+ module Deimos
7
+ module SchemaClass
8
+ # Base Class for Enum Classes generated from Avro.
9
+ class Enum < Base
10
+ # Returns all the valid symbols for this enum.
11
+ # @return [Array<String>]
12
+ def symbols
13
+ raise NotImplementedError
14
+ end
15
+
16
+ # :nodoc:
17
+ def self.initialize_from_value(value)
18
+ return nil if value.nil?
19
+
20
+ value.is_a?(self) ? value : self.new(value)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'json'
5
+
6
+ module Deimos
7
+ module SchemaClass
8
+ # Base Class of Record Classes generated from Avro.
9
+ class Record < Base
10
+
11
+ # Converts the object to a hash which can be used for debugging or comparing objects.
12
+ # @return [Hash] a hash representation of the payload
13
+ def as_json(_opts={})
14
+ super.except('payload_key')
15
+ end
16
+
17
+ # Element access method as if this Object were a hash
18
+ # @param key[String||Symbol]
19
+ # @return [Object] The value of the attribute if exists, nil otherwise
20
+ def [](key)
21
+ self.try(key.to_sym)
22
+ end
23
+
24
+ # :nodoc
25
+ def with_indifferent_access
26
+ self
27
+ end
28
+
29
+ # Returns the schema name of the inheriting class.
30
+ # @return [String]
31
+ def schema
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # Returns the namespace for the schema of the inheriting class.
36
+ # @return [String]
37
+ def namespace
38
+ raise NotImplementedError
39
+ end
40
+
41
+ # Returns the full schema name of the inheriting class.
42
+ # @return [String]
43
+ def full_schema
44
+ "#{namespace}.#{schema}"
45
+ end
46
+
47
+ # Returns the schema validator from the schema backend
48
+ # @return [Deimos::SchemaBackends::Base]
49
+ def validator
50
+ Deimos.schema_backend(schema: schema, namespace: namespace)
51
+ end
52
+
53
+ # @return [Array<String>] an array of fields names in the schema.
54
+ def schema_fields
55
+ validator.schema_fields.map(&:name)
56
+ end
57
+
58
+ # :nodoc:
59
+ def self.initialize_from_value(value)
60
+ return nil if value.nil?
61
+
62
+ value.is_a?(self) ? value : self.new(**value.symbolize_keys)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -58,6 +58,11 @@ module Deimos
58
58
  config[:key_field] = field&.to_s
59
59
  config[:key_schema] = schema
60
60
  end
61
+
62
+ # @param enabled [Boolean]
63
+ def schema_class_config(use_schema_classes)
64
+ config[:use_schema_classes] = use_schema_classes
65
+ end
61
66
  end
62
67
  end
63
68
  end
@@ -50,7 +50,6 @@ module Deimos
50
50
  included do
51
51
 
52
52
  RSpec.configure do |config|
53
-
54
53
  config.prepend_before(:each) do
55
54
  client = double('client').as_null_object
56
55
  allow(client).to receive(:time) do |*_args, &block|
@@ -210,9 +209,11 @@ module Deimos
210
209
  'value' => payload)
211
210
 
212
211
  unless skip_expectation
213
- expectation = expect(handler).to receive(:consume).
214
- with(payload, anything, &block)
215
- expectation.and_call_original if call_original
212
+ _handler_expectation(:consume,
213
+ payload,
214
+ handler,
215
+ call_original,
216
+ &block)
216
217
  end
217
218
  Phobos::Actions::ProcessMessage.new(
218
219
  listener: listener,
@@ -277,9 +278,11 @@ module Deimos
277
278
  'partition' => 1,
278
279
  'offset_lag' => 0)
279
280
  unless skip_expectation
280
- expectation = expect(handler).to receive(:consume_batch).
281
- with(payloads, anything, &block)
282
- expectation.and_call_original if call_original
281
+ _handler_expectation(:consume_batch,
282
+ payloads,
283
+ handler,
284
+ call_original,
285
+ &block)
283
286
  end
284
287
  action = Phobos::Actions::ProcessBatchInline.new(
285
288
  listener: listener,
@@ -356,5 +359,38 @@ module Deimos
356
359
 
357
360
  handler.handler.constantize
358
361
  end
362
+
363
+ # Test that a given handler will execute a `method` on an `input` correctly,
364
+ # If a block is given, that block will be executed when `method` is called.
365
+ # Otherwise it will just confirm that `method` is called at all.
366
+ # @param method [Symbol]
367
+ # @param input [Object]
368
+ # @param handler [Deimos::Consumer]
369
+ # @param call_original [Boolean]
370
+ def _handler_expectation(method,
371
+ input,
372
+ handler,
373
+ call_original,
374
+ &block)
375
+ schema_class = handler.class.config[:schema]
376
+ expected = input.dup
377
+
378
+ config = handler.class.config
379
+ use_schema_classes = config[:use_schema_classes]
380
+ use_schema_classes = use_schema_classes.present? ? use_schema_classes : Deimos.config.schema.use_schema_classes
381
+
382
+ if use_schema_classes && schema_class.present?
383
+ expected = if input.is_a?(Array)
384
+ input.map do |payload|
385
+ Utils::SchemaClass.instance(payload, schema_class)
386
+ end
387
+ else
388
+ Utils::SchemaClass.instance(input, schema_class)
389
+ end
390
+ end
391
+
392
+ expectation = expect(handler).to receive(method).with(expected, anything, &block)
393
+ expectation.and_call_original if call_original
394
+ end
359
395
  end
360
396
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deimos
4
+ module Utils
5
+ # Class used by SchemaClassGenerator and Consumer/Producer interfaces
6
+ module SchemaClass
7
+ class << self
8
+ # Converts a raw payload into an instance of the Schema Class
9
+ # @param payload [Hash]
10
+ # @param schema [String]
11
+ # @return [Deimos::SchemaClass::Record]
12
+ def instance(payload, schema)
13
+ klass = "Schemas::#{schema.underscore.camelize}".safe_constantize
14
+ return payload if klass.nil? || payload.nil?
15
+
16
+ klass.new(**payload.symbolize_keys)
17
+ end
18
+
19
+ # @param config [Hash] Producer or Consumer config
20
+ # @return [Boolean]
21
+ def use?(config)
22
+ use_schema_classes = config[:use_schema_classes]
23
+ use_schema_classes.present? ? use_schema_classes : Deimos.config.schema.use_schema_classes
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '1.11.2'
4
+ VERSION = '1.12.3'
5
5
  end
data/lib/deimos.rb CHANGED
@@ -19,6 +19,9 @@ require 'deimos/backends/kafka_async'
19
19
  require 'deimos/backends/test'
20
20
 
21
21
  require 'deimos/schema_backends/base'
22
+ require 'deimos/utils/schema_class'
23
+ require 'deimos/schema_class/enum'
24
+ require 'deimos/schema_class/record'
22
25
 
23
26
  require 'deimos/monkey_patches/phobos_producer'
24
27
  require 'deimos/monkey_patches/phobos_cli'
@@ -59,6 +62,24 @@ module Deimos
59
62
  schema_backend_class.new(schema: schema, namespace: namespace)
60
63
  end
61
64
 
65
+ # @param schema [String]
66
+ # @param namespace [String]
67
+ # @param payload [Hash]
68
+ # @param subject [String]
69
+ # @return [String]
70
+ def encode(schema:, namespace:, payload:, subject: nil)
71
+ self.schema_backend(schema: schema, namespace: namespace).
72
+ encode(payload, topic: subject || "#{namespace}.#{schema}" )
73
+ end
74
+
75
+ # @param schema [String]
76
+ # @param namespace [String]
77
+ # @param payload [String]
78
+ # @return [Hash,nil]
79
+ def decode(schema:, namespace:, payload:)
80
+ self.schema_backend(schema: schema, namespace: namespace).decode(payload)
81
+ end
82
+
62
83
  # Start the DB producers to send Kafka messages.
63
84
  # @param thread_count [Integer] the number of threads to start.
64
85
  def start_db_backend!(thread_count: 1)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is autogenerated by Deimos, Do NOT modify
4
+ module Schemas
5
+ <% if @sub_schema_templates.present? && @sub_schema_templates.any? -%>
6
+ ### Secondary Schema Classes ###
7
+ <% end -%>
8
+ <%- @sub_schema_templates.each do |schema_template| -%>
9
+ <%=- schema_template %>
10
+
11
+ <%- end -%>
12
+ ### Primary Schema Class ###
13
+ <%=- @main_class_definition -%>
14
+
15
+ end
@@ -0,0 +1,21 @@
1
+ # Autogenerated Schema for Enum at <%= @current_schema.namespace %>.<%= @current_schema.name %>
2
+ class <%= Deimos::SchemaBackends::AvroBase.schema_classname(@current_schema) %> < Deimos::SchemaClass::Enum
3
+ # @return ['<%= @current_schema.symbols.join("', '") %>']
4
+ attr_accessor :<%= @current_schema.name.underscore %>
5
+
6
+ # :nodoc:
7
+ def initialize(<%= @current_schema.name.underscore %>)
8
+ super
9
+ self.<%= @current_schema.name.underscore %> = <%= @current_schema.name.underscore %>
10
+ end
11
+
12
+ # @override
13
+ def symbols
14
+ %w(<%= @current_schema.symbols.join(' ') %>)
15
+ end
16
+
17
+ # @override
18
+ def to_h
19
+ @<%= @current_schema.name.underscore %>
20
+ end
21
+ end
@@ -0,0 +1,65 @@
1
+ # Autogenerated Schema for Record at <%= @current_schema.namespace %>.<%= @current_schema.name %>
2
+ class <%= Deimos::SchemaBackends::AvroBase.schema_classname(@current_schema) %> < Deimos::SchemaClass::Record
3
+ <%- if @field_assignments.select{ |h| h[:is_schema_class] }.any? -%>
4
+ ### Attribute Readers ###
5
+ <%- @field_assignments.select{ |h| h[:is_schema_class] }.each do |method_definition| -%>
6
+ # @return [<%= method_definition[:deimos_type] %>]
7
+ attr_reader :<%= method_definition[:field].name %>
8
+ <%- end -%>
9
+
10
+ <% end -%>
11
+ <%- if @field_assignments.select{ |h| !h[:is_schema_class] }.any? -%>
12
+ ### Attribute Accessors ###
13
+ <%- @field_assignments.select{ |h| !h[:is_schema_class] }.each do |method_definition| -%>
14
+ # @param <%= method_definition[:method_argument] %> [<%= method_definition[:deimos_type] %>]
15
+ attr_accessor :<%= method_definition[:field].name %>
16
+ <%- end -%>
17
+
18
+ <% end -%>
19
+ <%- if @field_assignments.select{ |h| h[:is_schema_class] }.any? -%>
20
+ ### Attribute Writers ###
21
+ <%- @field_assignments.select{ |h| h[:is_schema_class] }.each do |method_definition| -%>
22
+ # @param <%= method_definition[:method_argument] %> [<%= method_definition[:deimos_type] %>]
23
+ def <%= method_definition[:field].name %>=(<%= method_definition[:method_argument] %>)
24
+ <%- if method_definition[:field_type] == :array -%>
25
+ @<%= method_definition[:field].name %> = values.map do |value|
26
+ <%= method_definition[:field_initialization] %>
27
+ end
28
+ <%- elsif method_definition[:field_type] == :map -%>
29
+ @<%= method_definition[:field].name %> = values.transform_values do |value|
30
+ <%= method_definition[:field_initialization] %>
31
+ end
32
+ <%- else -%>
33
+ @<%= method_definition[:field].name %> = <%= method_definition[:field_initialization] %>
34
+ <%- end -%>
35
+ end
36
+
37
+ <%- end -%>
38
+ <% end -%>
39
+ # @override
40
+ <%= @initialization_definition %>
41
+ super
42
+ <%- @fields.each do |field| -%>
43
+ self.<%= field.name %> = <%= field.name %>
44
+ <% end -%>
45
+ end
46
+
47
+ # @override
48
+ def schema
49
+ '<%= @current_schema.name %>'
50
+ end
51
+
52
+ # @override
53
+ def namespace
54
+ '<%= @current_schema.namespace %>'
55
+ end
56
+
57
+ # @override
58
+ def to_h
59
+ {
60
+ <%- @fields.each do |field| -%>
61
+ <%= field_to_h(field) %>
62
+ <% end -%>
63
+ }
64
+ end
65
+ end
@@ -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
@@ -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