avromatic 0.8.0 → 0.9.0.rc0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5cf8d1ba64c3de84625325553fbeeff017e3ac7
4
- data.tar.gz: f536b77ce377759a8f70f7ba3d9d0ea4cb796815
3
+ metadata.gz: 1a36670183dc6e3348395dcc336222da9ac1bd00
4
+ data.tar.gz: b37d41366b6737fb4aa7d4793128c13d90860920
5
5
  SHA512:
6
- metadata.gz: 9241a4262287eda4cadfb2f636b3ee74c655863ccab6b779c0a6c28f12c7cce84cc3fcddc6bbdf7b8f3ce817b96c9cabd95f34afa8a0099c2fcb9daf7518ea40
7
- data.tar.gz: 389244130b9ac99560219d2019222c5f7576f3b5df1b4795926569f0d6e158b4db1378ec33e9c8b0459836cf4d490dec58d26adae51f0c4131f498435a0889bf
6
+ metadata.gz: d316d600c9e38743538abc7b1a598cb13cfa102c6181476ed7e2684ccf63000a6856998ebc9ad065bfb71c98a54adab72a8675aa06f67622f5d2eb415acfea73
7
+ data.tar.gz: 86ac869dd270188af7f24f22ce1259ed2cf0b109e09d23ff3da9af4288deb191ab1fc7a05f4c2d81159335216b07e3e63e8618c86d3e0c4b09055246881b830f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # avromatic changelog
2
2
 
3
+ ## v0.9.0 (unreleased)
4
+ - Add support for more than one non-null type in a union.
5
+
3
6
  ## v0.8.0
4
7
  - Add support for logical types. Currently this requires using the
5
8
  `avro-salsify-fork` gem for logical types support with Ruby.
data/README.md CHANGED
@@ -116,6 +116,9 @@ These customizations are registered on the `Avromatic` module. Once a custom typ
116
116
  is registered, it is used for all models with a schema that references that type.
117
117
  It is recommended to register types within a block passed to `Avromatic.configure`:
118
118
 
119
+ Note: custom types are not currently supported on members of unions with more
120
+ than one non-null type.
121
+
119
122
  ```ruby
120
123
  Avromatic.configure do |config|
121
124
  config.register_type('com.example.my_string', MyString)
@@ -248,8 +251,7 @@ The following validations are supported:
248
251
 
249
252
  The following types/features are not supported for generated models:
250
253
 
251
- - Generic union fields: The special case of an optional field, the union of `:null` and
252
- another type, is supported.
254
+ - Custom types for members within a union.
253
255
  - Reused models for nested records: Currently an anonymous model class is
254
256
  generated for each subrecord.
255
257
 
@@ -0,0 +1,52 @@
1
+ require 'avromatic/model/attribute_type/union'
2
+
3
+ module Avromatic
4
+ module Model
5
+ module Attribute
6
+
7
+ # This subclass of Virtus::Attribute is used for any unions that are
8
+ # defined as subclasses of the primitive Avromatic::Model::AttributeType::Union.
9
+ # Values are coerced by first checking if they already match one of the
10
+ # member types, and then by attempting to coerce to each member type in
11
+ # order.
12
+ class Union < Virtus::Attribute
13
+ primitive Avromatic::Model::AttributeType::Union
14
+
15
+ def initialize(*)
16
+ super
17
+
18
+ primitive.types.each do |type|
19
+ member_attributes << Virtus::Attribute.build(type)
20
+ end
21
+ end
22
+
23
+ def coerce(input)
24
+ return input if value_coerced?(input)
25
+
26
+ result = nil
27
+ member_attributes.find do |union_attribute|
28
+ begin
29
+ coerced = union_attribute.coerce(input)
30
+ result = coerced unless coerced.is_a?(Avromatic::Model::Attributes) && coerced.invalid?
31
+ rescue
32
+ nil
33
+ end
34
+ end
35
+ result
36
+ end
37
+
38
+ def value_coerced?(value)
39
+ member_attributes.any? do |union_attribute|
40
+ union_attribute.value_coerced?(value)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def member_attributes
47
+ @member_attributes ||= Array.new
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ module Avromatic
2
+ module Model
3
+ module AttributeType
4
+
5
+ # This class is used as the base class for Virtus attributes that
6
+ # represent unions. A subclass is generated using Union[*types]. This
7
+ # subclass is specified when defining a Virtus attribute.
8
+ class Union
9
+ class << self
10
+ attr_reader :types
11
+
12
+ protected
13
+
14
+ attr_writer :types
15
+ end
16
+
17
+ # Factory method to define Union types with the specified list of
18
+ # types (classes).
19
+ def self.[](*types)
20
+ Class.new(self).tap do |klass|
21
+ klass.types = types
22
+ # See https://github.com/solnic/virtus/issues/284#issuecomment-56405137
23
+ Axiom::Types::Object.new { primitive(klass) }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -13,9 +13,10 @@ module Avromatic
13
13
  # TODO: This is a hack until I find a better solution for unions with
14
14
  # Virtus. This only handles a union for an optional field with :null
15
15
  # and one other type.
16
- schemas = field_type.schemas.reject { |schema| schema.type_sym == :null }
17
- raise "Only the union of null with one other type is supported #{field_type}" if schemas.size > 1
18
- schemas.first
16
+ # This hack lives on for now because custom type coercion is not pushed
17
+ # down into unions. This means that custom types can only be optional
18
+ # fields, not members of real unions.
19
+ field_type.schemas.reject { |schema| schema.type_sym == :null }.first
19
20
  end
20
21
 
21
22
  module ClassMethods
@@ -60,10 +61,10 @@ module Avromatic
60
61
 
61
62
  attribute(field.name,
62
63
  field_class,
63
- avro_field_options(field))
64
+ avro_field_options(field, field_class))
64
65
 
65
66
  add_validation(field)
66
- add_serializer(field)
67
+ add_serializer(field, field_class)
67
68
  end
68
69
  end
69
70
 
@@ -127,22 +128,29 @@ module Avromatic
127
128
  when :record
128
129
  # TODO: This should add the generated model to a module.
129
130
  # A hash of generated models should be kept by name for reuse.
130
- Class.new do
131
- include Avromatic::Model.build(schema: field_type)
132
- end
131
+ Avromatic::Model.model(schema: field_type)
133
132
  else
134
133
  raise "Unsupported type #{field_type}"
135
134
  end
136
135
  end
137
136
 
138
137
  def union_field_class(field_type)
139
- avro_field_class(Avromatic::Model::Attributes.first_union_schema(field_type))
138
+ field_classes = field_type.schemas.reject { |schema| schema.type_sym == :null }
139
+ .map { |schema| avro_field_class(schema) }
140
+
141
+ if field_classes.size == 1
142
+ field_classes.first
143
+ else
144
+ Avromatic::Model::AttributeType::Union[*field_classes]
145
+ end
140
146
  end
141
147
 
142
- def avro_field_options(field)
148
+ def avro_field_options(field, field_class)
143
149
  options = {}
144
150
 
145
- custom_type = Avromatic.type_registry.fetch(field)
151
+ prevent_union_including_custom_type!(field, field_class)
152
+
153
+ custom_type = Avromatic.type_registry.fetch(field, field_class)
146
154
  coercer = custom_type.deserializer
147
155
  options[:coercer] = coercer if coercer
148
156
 
@@ -154,8 +162,10 @@ module Avromatic
154
162
  options
155
163
  end
156
164
 
157
- def add_serializer(field)
158
- custom_type = Avromatic.type_registry.fetch(field)
165
+ def add_serializer(field, field_class)
166
+ prevent_union_including_custom_type!(field, field_class)
167
+
168
+ custom_type = Avromatic.type_registry.fetch(field, field_class)
159
169
  serializer = custom_type.serializer
160
170
 
161
171
  avro_serializer[field.name.to_sym] = serializer if serializer
@@ -164,6 +174,24 @@ module Avromatic
164
174
  def default_for(value)
165
175
  value.duplicable? ? value.dup.deep_freeze : value
166
176
  end
177
+
178
+ # TODO: the methods below are temporary until support for custom types
179
+ # as union members are supported.
180
+ def member_uses_custom_type?(field)
181
+ field.type.schemas.any? do |klass|
182
+ Avromatic.type_registry.fetch(klass) != NullCustomType
183
+ end
184
+ end
185
+
186
+ def prevent_union_including_custom_type!(field, field_class)
187
+ if field_class.is_a?(Class) &&
188
+ field_class < Avromatic::Model::AttributeType::Union &&
189
+ member_uses_custom_type?(field)
190
+
191
+ raise 'custom types within unions are currently unsupported'
192
+ end
193
+ end
194
+
167
195
  end
168
196
 
169
197
  end
@@ -4,6 +4,7 @@ require 'active_model'
4
4
  require 'avromatic/model/configuration'
5
5
  require 'avromatic/model/value_object'
6
6
  require 'avromatic/model/configurable'
7
+ require 'avromatic/model/attribute/union'
7
8
  require 'avromatic/model/attributes'
8
9
  require 'avromatic/model/raw_serialization'
9
10
  require 'avromatic/model/messaging_serialization'
@@ -23,10 +23,11 @@ module Avromatic
23
23
 
24
24
  # @object [Avro::Schema] Custom type may be fetched based on a Avro field
25
25
  # or schema. If there is no custom type, then NullCustomType is returned.
26
- def fetch(object)
26
+ # @field_class [Object] Value class that has been determined for a field.
27
+ def fetch(object, field_class = nil)
27
28
  field_type = object.is_a?(Avro::Schema::Field) ? object.type : object
28
29
 
29
- if field_type.type_sym == :union
30
+ if field_class && field_type.type_sym == :union && !(field_class < Avromatic::Model::AttributeType::Union)
30
31
  field_type = Avromatic::Model::Attributes.first_union_schema(field_type)
31
32
  end
32
33
 
@@ -1,3 +1,3 @@
1
1
  module Avromatic
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '0.9.0.rc0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avromatic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0.rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salsify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-29 00:00:00.000000000 Z
11
+ date: 2016-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro
@@ -246,6 +246,8 @@ files:
246
246
  - gemfiles/rails4_2.gemfile
247
247
  - lib/avromatic.rb
248
248
  - lib/avromatic/model.rb
249
+ - lib/avromatic/model/attribute/union.rb
250
+ - lib/avromatic/model/attribute_type/union.rb
249
251
  - lib/avromatic/model/attributes.rb
250
252
  - lib/avromatic/model/builder.rb
251
253
  - lib/avromatic/model/configurable.rb
@@ -278,12 +280,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
278
280
  version: '0'
279
281
  required_rubygems_version: !ruby/object:Gem::Requirement
280
282
  requirements:
281
- - - ">="
283
+ - - ">"
282
284
  - !ruby/object:Gem::Version
283
- version: '0'
285
+ version: 1.3.1
284
286
  requirements: []
285
287
  rubyforge_project:
286
- rubygems_version: 2.6.6
288
+ rubygems_version: 2.4.8
287
289
  signing_key:
288
290
  specification_version: 4
289
291
  summary: Generate Ruby models from Avro schemas