deimos-ruby 1.11.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -2
  3. data/Gemfile.lock +8 -8
  4. data/README.md +96 -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 +67 -0
  20. data/lib/deimos/schema_class/enum.rb +24 -0
  21. data/lib/deimos/schema_class/record.rb +59 -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 +3 -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 +94 -83
  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,67 @@
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.
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.state
37
+ end
38
+
39
+ comparison == self.state
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 state
58
+ as_json
59
+ end
60
+
61
+ # :nodoc:
62
+ def hash
63
+ state.hash
64
+ end
65
+ end
66
+ end
67
+ 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,59 @@
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
+ # Element access method as if this Object were a hash
11
+ # @param key[String||Symbol]
12
+ # @return [Object] The value of the attribute if exists, nil otherwise
13
+ def [](key)
14
+ self.try(key.to_sym)
15
+ end
16
+
17
+ # :nodoc
18
+ def with_indifferent_access
19
+ self
20
+ end
21
+
22
+ # Returns the schema name of the inheriting class.
23
+ # @return [String]
24
+ def schema
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # Returns the namespace for the schema of the inheriting class.
29
+ # @return [String]
30
+ def namespace
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # Returns the full schema name of the inheriting class.
35
+ # @return [String]
36
+ def full_schema
37
+ "#{namespace}.#{schema}"
38
+ end
39
+
40
+ # Returns the schema validator from the schema backend
41
+ # @return [Deimos::SchemaBackends::Base]
42
+ def validator
43
+ Deimos.schema_backend(schema: schema, namespace: namespace)
44
+ end
45
+
46
+ # @return [Array<String>] an array of fields names in the schema.
47
+ def schema_fields
48
+ validator.schema_fields.map(&:name)
49
+ end
50
+
51
+ # :nodoc:
52
+ def self.initialize_from_value(value)
53
+ return nil if value.nil?
54
+
55
+ value.is_a?(self) ? value : self.new(**value.symbolize_keys)
56
+ end
57
+ end
58
+ end
59
+ 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 = "Deimos::#{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.0'
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'
@@ -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