panko_serializer 0.8.5 → 1.0.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/README.md +6 -4
- data/lib/panko/array_serializer.rb +55 -16
- data/lib/panko/code_gen/active_record/access_classifier.rb +65 -0
- data/lib/panko/code_gen/active_record/define_attribute_methods.rb +13 -0
- data/lib/panko/code_gen/code_builder.rb +32 -0
- data/lib/panko/code_gen/compile_cache.rb +28 -0
- data/lib/panko/code_gen/compiler.rb +56 -0
- data/lib/panko/code_gen/config.rb +79 -0
- data/lib/panko/code_gen/datetime_format.rb +38 -0
- data/lib/panko/code_gen/descriptor.rb +136 -0
- data/lib/panko/code_gen/descriptor_builder.rb +166 -0
- data/lib/panko/code_gen/dump.rb +52 -0
- data/lib/panko/code_gen/errors.rb +21 -0
- data/lib/panko/code_gen/filter.rb +29 -0
- data/lib/panko/code_gen/filter_adapter.rb +44 -0
- data/lib/panko/code_gen/filters/indexed.rb +62 -0
- data/lib/panko/code_gen/filters/none.rb +19 -0
- data/lib/panko/code_gen/generator.rb +23 -0
- data/lib/panko/code_gen/generators/banner.rb +27 -0
- data/lib/panko/code_gen/generators/class_emitter.rb +120 -0
- data/lib/panko/code_gen/generators/cycle_membership.rb +53 -0
- data/lib/panko/code_gen/generators/descriptor_walk.rb +22 -0
- data/lib/panko/code_gen/generators/fanout.rb +60 -0
- data/lib/panko/code_gen/generators/field_index.rb +31 -0
- data/lib/panko/code_gen/generators/field_walk.rb +38 -0
- data/lib/panko/code_gen/generators/generated_names.rb +73 -0
- data/lib/panko/code_gen/generators/hash_sink.rb +217 -0
- data/lib/panko/code_gen/generators/json_sink.rb +290 -0
- data/lib/panko/code_gen/generators/record_access/generic.rb +92 -0
- data/lib/panko/code_gen/generators/record_access/specialized.rb +90 -0
- data/lib/panko/code_gen/generators/release.rb +60 -0
- data/lib/panko/code_gen/generators/sink.rb +80 -0
- data/lib/panko/code_gen/instance_pool.rb +27 -0
- data/lib/panko/code_gen/runtime.rb +27 -0
- data/lib/panko/code_gen/serializer_cache.rb +242 -0
- data/lib/panko/code_gen/validators/callable_arity.rb +42 -0
- data/lib/panko/code_gen/validators/name_uniqueness.rb +44 -0
- data/lib/panko/code_gen/validators/source_resolution.rb +48 -0
- data/lib/panko/code_gen/validators/validator.rb +19 -0
- data/lib/panko/code_gen/writers_pool.rb +51 -0
- data/lib/panko/code_gen.rb +82 -0
- data/lib/panko/compile_all.rb +80 -0
- data/lib/panko/compile_all_result.rb +21 -0
- data/lib/panko/config.rb +71 -0
- data/lib/panko/descriptor.rb +93 -0
- data/lib/panko/response.rb +40 -8
- data/lib/panko/serializer.rb +175 -91
- data/lib/panko/serializer_resolver.rb +2 -8
- data/lib/panko/version.rb +1 -1
- data/lib/panko_serializer.rb +3 -5
- metadata +49 -35
- data/ext/panko_serializer/attributes_writer/active_record.c +0 -181
- data/ext/panko_serializer/attributes_writer/active_record.h +0 -17
- data/ext/panko_serializer/attributes_writer/attributes_writer.c +0 -55
- data/ext/panko_serializer/attributes_writer/attributes_writer.h +0 -35
- data/ext/panko_serializer/attributes_writer/common.c +0 -10
- data/ext/panko_serializer/attributes_writer/common.h +0 -9
- data/ext/panko_serializer/attributes_writer/hash.c +0 -13
- data/ext/panko_serializer/attributes_writer/hash.h +0 -7
- data/ext/panko_serializer/attributes_writer/plain.c +0 -13
- data/ext/panko_serializer/attributes_writer/plain.h +0 -7
- data/ext/panko_serializer/attributes_writer/type_cast/time_conversion.c +0 -156
- data/ext/panko_serializer/attributes_writer/type_cast/time_conversion.h +0 -10
- data/ext/panko_serializer/attributes_writer/type_cast/type_cast.c +0 -409
- data/ext/panko_serializer/attributes_writer/type_cast/type_cast.h +0 -81
- data/ext/panko_serializer/common.h +0 -9
- data/ext/panko_serializer/extconf.rb +0 -28
- data/ext/panko_serializer/panko_serializer.c +0 -181
- data/ext/panko_serializer/panko_serializer.h +0 -12
- data/ext/panko_serializer/serialization_descriptor/association.c +0 -90
- data/ext/panko_serializer/serialization_descriptor/association.h +0 -20
- data/ext/panko_serializer/serialization_descriptor/attribute.c +0 -96
- data/ext/panko_serializer/serialization_descriptor/attribute.h +0 -27
- data/ext/panko_serializer/serialization_descriptor/serialization_descriptor.c +0 -177
- data/ext/panko_serializer/serialization_descriptor/serialization_descriptor.h +0 -30
- data/lib/panko/association.rb +0 -17
- data/lib/panko/attribute.rb +0 -29
- data/lib/panko/object_writer.rb +0 -68
- data/lib/panko/serialization_descriptor.rb +0 -180
data/lib/panko/config.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panko
|
|
4
|
+
# Process-global settings, read at serialize time. Set them in an
|
|
5
|
+
# initializer, before serializers compile:
|
|
6
|
+
#
|
|
7
|
+
# Panko.configure do |config|
|
|
8
|
+
# config.auto_specialization.capacity = 32
|
|
9
|
+
# end
|
|
10
|
+
class Config
|
|
11
|
+
# The engine compiles a specialized Generated Class for each named
|
|
12
|
+
# ActiveRecord class a serializer sees, up to +capacity+.
|
|
13
|
+
class AutoSpecialization
|
|
14
|
+
# @return [Boolean] +false+ routes every record class to the generic path
|
|
15
|
+
attr_reader :enabled
|
|
16
|
+
|
|
17
|
+
# @return [Integer] max specialized variants per serializer class and output
|
|
18
|
+
# mode; past it, record classes use the generic path, with one warning
|
|
19
|
+
# per serializer class
|
|
20
|
+
attr_reader :capacity
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@enabled = true
|
|
24
|
+
@capacity = 16
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def enabled=(value)
|
|
28
|
+
unless value.equal?(true) || value.equal?(false)
|
|
29
|
+
raise ArgumentError,
|
|
30
|
+
"auto_specialization.enabled: must be true or false; got #{value.inspect}:#{value.class}"
|
|
31
|
+
end
|
|
32
|
+
@enabled = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def capacity=(value)
|
|
36
|
+
unless value.is_a?(Integer) && value.positive?
|
|
37
|
+
raise ArgumentError,
|
|
38
|
+
"auto_specialization.capacity: must be a positive Integer; got #{value.inspect}:#{value.class}"
|
|
39
|
+
end
|
|
40
|
+
@capacity = value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@auto_specialization = AutoSpecialization.new
|
|
45
|
+
@writer_pool_max_bytes = 1_048_576
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
attr_reader :auto_specialization
|
|
49
|
+
|
|
50
|
+
# Largest +serialize_to_json+ output, in bytes, whose writer goes back to
|
|
51
|
+
# the pool. +Oj::StringWriter#reset+ keeps the grown buffer, so a larger
|
|
52
|
+
# writer is dropped instead of holding that memory. Read on every checkin.
|
|
53
|
+
#
|
|
54
|
+
# @return [Integer] default 1_048_576 (1 MB)
|
|
55
|
+
attr_reader :writer_pool_max_bytes
|
|
56
|
+
|
|
57
|
+
def writer_pool_max_bytes=(value)
|
|
58
|
+
unless value.is_a?(Integer) && value.positive?
|
|
59
|
+
raise ArgumentError,
|
|
60
|
+
"writer_pool_max_bytes: must be a positive Integer; got #{value.inspect}:#{value.class}"
|
|
61
|
+
end
|
|
62
|
+
@writer_pool_max_bytes = value
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Yields the {Config} class for block-style configuration.
|
|
68
|
+
def self.configure
|
|
69
|
+
yield Config
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "code_gen/serializer_cache"
|
|
4
|
+
|
|
5
|
+
module Panko
|
|
6
|
+
# Public, read-only view of a serializer's shape, for tools like association
|
|
7
|
+
# preloaders. Separate from Panko::CodeGen::Descriptor so the engine can change.
|
|
8
|
+
class Descriptor
|
|
9
|
+
Attribute = Data.define(:name, :source)
|
|
10
|
+
MethodAttribute = Data.define(:name, :source)
|
|
11
|
+
Association = Data.define(:name, :source, :kind, :descriptor)
|
|
12
|
+
|
|
13
|
+
attr_reader :serializer, :attributes, :method_attributes, :associations
|
|
14
|
+
|
|
15
|
+
# Cached on the serializer class. A concurrent first build is harmless:
|
|
16
|
+
# both threads build frozen views with the same content.
|
|
17
|
+
def self.for(serializer_class)
|
|
18
|
+
serializer_class._cg_public_descriptor ||= wrap(
|
|
19
|
+
CodeGen::SerializerCache.descriptor_for(serializer_class)
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.wrap(internal)
|
|
24
|
+
new(
|
|
25
|
+
serializer: internal.parent_class,
|
|
26
|
+
attributes: internal.attributes.map { |a| Attribute.new(name: a.name, source: a.source) }.freeze,
|
|
27
|
+
method_attributes: internal.method_attributes.map { |m| wrap_method_attribute(m) }.freeze,
|
|
28
|
+
associations: internal.associations.map do |as|
|
|
29
|
+
Association.new(name: as.name, source: as.source, kind: as.kind, descriptor: wrap(as.descriptor))
|
|
30
|
+
end.freeze
|
|
31
|
+
).freeze
|
|
32
|
+
end
|
|
33
|
+
private_class_method :wrap
|
|
34
|
+
|
|
35
|
+
# A Callable body has no method name, so +source+ is nil. The DSL only
|
|
36
|
+
# makes Symbol bodies.
|
|
37
|
+
def self.wrap_method_attribute(method_attribute)
|
|
38
|
+
body = method_attribute.body
|
|
39
|
+
MethodAttribute.new(name: method_attribute.name, source: body.is_a?(Symbol) ? body : nil)
|
|
40
|
+
end
|
|
41
|
+
private_class_method :wrap_method_attribute
|
|
42
|
+
|
|
43
|
+
def initialize(serializer:, attributes:, method_attributes:, associations:)
|
|
44
|
+
@serializer = serializer
|
|
45
|
+
@attributes = attributes
|
|
46
|
+
@method_attributes = method_attributes
|
|
47
|
+
@associations = associations
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Resolves each level on first read and memoizes it, so only the levels a
|
|
51
|
+
# caller visits cost anything. +filters+ has the shape
|
|
52
|
+
# Panko::CodeGen::Runtime.runtime_filters returns.
|
|
53
|
+
class Filtered < Descriptor
|
|
54
|
+
def initialize(skeleton, filters)
|
|
55
|
+
@skeleton = skeleton
|
|
56
|
+
@filters = filters
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def serializer
|
|
60
|
+
@skeleton.serializer
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def attributes
|
|
64
|
+
@attributes ||= @skeleton.attributes.select { |a| keep?(a.name) }.freeze
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def method_attributes
|
|
68
|
+
@method_attributes ||= @skeleton.method_attributes.select { |m| keep?(m.name) }.freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Associations are filtered by +source+, the declared relation name, as
|
|
72
|
+
# the serialize path does.
|
|
73
|
+
def associations
|
|
74
|
+
@associations ||= @skeleton.associations.filter_map do |as|
|
|
75
|
+
next unless keep?(as.source)
|
|
76
|
+
sub = @filters[as.source]
|
|
77
|
+
(sub.is_a?(Hash) && !sub.empty?) ? as.with(descriptor: Filtered.new(as.descriptor, sub)) : as
|
|
78
|
+
end.freeze
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# FilterAdapter never emits both :only and :except at one level.
|
|
84
|
+
def keep?(name)
|
|
85
|
+
only = @filters[:only]
|
|
86
|
+
return only.include?(name) unless only.nil?
|
|
87
|
+
except = @filters[:except]
|
|
88
|
+
return !except.include?(name) unless except.nil?
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/panko/response.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "oj"
|
|
4
|
+
require_relative "code_gen/writers_pool"
|
|
4
5
|
|
|
5
6
|
module Panko
|
|
6
7
|
JsonValue = Struct.new(:value) do
|
|
@@ -13,6 +14,17 @@ module Panko
|
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
# A serializer and the object it serializes, written straight into the Response writer.
|
|
18
|
+
SerializerValue = Struct.new(:serializer, :object) do
|
|
19
|
+
def to_json
|
|
20
|
+
serializer.serialize_to_json(object)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def write_json(writer, key = nil)
|
|
24
|
+
serializer.write_json(object, writer, key)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
16
28
|
class ResponseCreator
|
|
17
29
|
def self.value(value)
|
|
18
30
|
Panko::Response.new(value)
|
|
@@ -28,19 +40,39 @@ module Panko
|
|
|
28
40
|
end
|
|
29
41
|
|
|
30
42
|
def self.serializer(data, serializer, options = {})
|
|
31
|
-
|
|
43
|
+
Panko::SerializerValue.new(serializer.new(options), data)
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
class Response
|
|
48
|
+
# Picked on first use, like the generated classes' pools, so an app that loads
|
|
49
|
+
# ActiveSupport after Panko still shares one writer stack with them.
|
|
50
|
+
def self.writers
|
|
51
|
+
@writers ||= defined?(ActiveSupport::IsolatedExecutionState) ?
|
|
52
|
+
Panko::CodeGen::WritersPool::IsolatedExecutionState.new :
|
|
53
|
+
Panko::CodeGen::WritersPool::ThreadLocal.new
|
|
54
|
+
end
|
|
55
|
+
|
|
36
56
|
def initialize(data)
|
|
37
57
|
@data = data
|
|
38
58
|
end
|
|
39
59
|
|
|
40
60
|
def to_json(_options = nil)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
writers = Response.writers
|
|
62
|
+
writer = writers.checkout
|
|
63
|
+
result = nil
|
|
64
|
+
begin
|
|
65
|
+
write(writer, @data)
|
|
66
|
+
result = writer.to_s
|
|
67
|
+
ensure
|
|
68
|
+
writers.checkin(writer, result)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Writes this Response into +writer+ as the value of +key+, or as the next element when
|
|
73
|
+
# +key+ is nil.
|
|
74
|
+
def write_json(writer, key = nil)
|
|
75
|
+
write(writer, @data, key)
|
|
44
76
|
end
|
|
45
77
|
|
|
46
78
|
def self.create
|
|
@@ -74,10 +106,10 @@ module Panko
|
|
|
74
106
|
end
|
|
75
107
|
|
|
76
108
|
def write_value(writer, value, key = nil)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
case value
|
|
110
|
+
when Panko::ArraySerializer, Panko::Response, Panko::SerializerValue
|
|
111
|
+
value.write_json(writer, key)
|
|
112
|
+
when Panko::Serializer, Panko::JsonValue
|
|
81
113
|
writer.push_json(value.to_json, key)
|
|
82
114
|
else
|
|
83
115
|
writer.push_value(value, key)
|
data/lib/panko/serializer.rb
CHANGED
|
@@ -1,146 +1,230 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "code_gen"
|
|
4
|
+
require_relative "code_gen/descriptor_builder"
|
|
5
|
+
require_relative "code_gen/runtime"
|
|
4
6
|
require "oj"
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
module Panko
|
|
9
|
+
class Serializer
|
|
10
|
+
# A method field that returns SKIP leaves its key out of the output.
|
|
11
|
+
SKIP = Panko::CodeGen::SKIP
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
@context = context
|
|
11
|
-
@scope = scope
|
|
12
|
-
end
|
|
13
|
+
EMPTY_MODELS = [].freeze
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
SerializationContext.new(options[:context], options[:scope])
|
|
17
|
-
else
|
|
18
|
-
EmptySerializerContext.new
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
15
|
+
class << self
|
|
16
|
+
attr_accessor :_cg_attributes, :_cg_method_attributes, :_cg_associations
|
|
22
17
|
|
|
23
|
-
class
|
|
24
|
-
|
|
25
|
-
nil
|
|
26
|
-
end
|
|
18
|
+
# Not copied on inheritance: each class compiles its own.
|
|
19
|
+
attr_accessor :_cg_state
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
end
|
|
21
|
+
# One-entry inline caches: frozen [model, pool] pairs.
|
|
22
|
+
attr_accessor :_cg_last_json, :_cg_last_hash
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
class Serializer
|
|
35
|
-
SKIP = Object.new.freeze
|
|
24
|
+
attr_accessor :_cg_public_descriptor
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
base._descriptor = Panko::SerializationDescriptor.new
|
|
26
|
+
# Lets the unfiltered hot path skip filter resolution. A stale +true+ only
|
|
27
|
+
# costs a +respond_to?+ inside +runtime_filters+.
|
|
28
|
+
attr_accessor :_cg_has_filters_for
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
base._descriptor.aliases = {}
|
|
30
|
+
attr_accessor :_cg_models
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
def inherited(base)
|
|
33
|
+
base._cg_attributes = (_cg_attributes || []).dup
|
|
34
|
+
base._cg_method_attributes = (_cg_method_attributes || []).dup
|
|
35
|
+
base._cg_associations = (_cg_associations || []).dup
|
|
36
|
+
base._cg_has_filters_for = base.respond_to?(:filters_for)
|
|
37
|
+
base._cg_models = _cg_models || EMPTY_MODELS
|
|
38
|
+
end
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
# Declares the record classes this serializer expects, so Panko.compile_all
|
|
41
|
+
# can compile a specialized variant for each at boot. A hint only: records
|
|
42
|
+
# of other classes still serialize. Subclasses inherit the list; a second
|
|
43
|
+
# call replaces it.
|
|
44
|
+
#
|
|
45
|
+
# @param models [Array<Class>]
|
|
46
|
+
# @raise [ArgumentError] when an argument is not a Class
|
|
47
|
+
def models(*models)
|
|
48
|
+
invalid = models.reject { |model| model.is_a?(Class) }
|
|
49
|
+
raise ArgumentError, "models: expected classes, got #{invalid.inspect}" unless invalid.empty?
|
|
50
|
+
self._cg_models = models.freeze
|
|
53
51
|
end
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
# A +filters_for+ defined or stubbed after the first serialize must still apply.
|
|
54
|
+
def singleton_method_added(method)
|
|
55
|
+
super
|
|
56
|
+
@_cg_has_filters_for = true if method == :filters_for
|
|
57
|
+
end
|
|
56
58
|
|
|
57
59
|
def attributes(*attrs)
|
|
58
|
-
|
|
60
|
+
attrs.each { |attr| add_attribute(attr.to_sym, attr.to_sym) }
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def aliases(aliases = {})
|
|
62
|
-
aliases.each
|
|
63
|
-
@_descriptor.attributes << Attribute.create(attr, alias_name: alias_name)
|
|
64
|
-
end
|
|
64
|
+
aliases.each { |source, output| add_attribute(source.to_sym, output.to_sym) }
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# A method named like a declared attribute turns that attribute into a
|
|
68
|
+
# method field with the same output key.
|
|
67
69
|
def method_added(method)
|
|
68
70
|
super
|
|
71
|
+
return if _cg_attributes.nil?
|
|
72
|
+
index = _cg_attributes.index { |attribute| attribute.source == method }
|
|
73
|
+
return if index.nil?
|
|
74
|
+
attribute = _cg_attributes.delete_at(index)
|
|
75
|
+
_cg_method_attributes << Panko::CodeGen::MethodAttribute.new(name: attribute.name, body: method)
|
|
76
|
+
end
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@_descriptor.method_fields << Attribute.create(deleted_attr.name, alias_name: deleted_attr.alias_name) unless deleted_attr.nil?
|
|
78
|
+
# The static public view of this serializer's declared shape.
|
|
79
|
+
def descriptor
|
|
80
|
+
Panko::Descriptor.for(self)
|
|
74
81
|
end
|
|
75
82
|
|
|
76
83
|
def has_one(name, options = {})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
add_association(:has_one, name, options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def has_many(name, options = {})
|
|
88
|
+
add_association(:has_many, name, options)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
def add_attribute(source, output)
|
|
94
|
+
return if _cg_attributes.any? { |attribute| attribute.source == source }
|
|
95
|
+
_cg_attributes << Panko::CodeGen::Attribute.new(name: output, source: source)
|
|
96
|
+
end
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
def add_association(kind, name, options)
|
|
99
|
+
serializer = resolve_association_serializer(name, options, kind)
|
|
100
|
+
descriptor = Panko::CodeGen::DescriptorBuilder.build(serializer)
|
|
101
|
+
only, except = association_filters(serializer, options)
|
|
102
|
+
descriptor = Panko::CodeGen::DescriptorBuilder.narrow(descriptor, only, except)
|
|
103
|
+
_cg_associations << Panko::CodeGen::Association.new(
|
|
104
|
+
name: options.fetch(:name, name).to_s.to_sym,
|
|
105
|
+
kind: kind,
|
|
106
|
+
descriptor: descriptor,
|
|
107
|
+
source: name.to_sym
|
|
89
108
|
)
|
|
90
109
|
end
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
# The nested serializer's +filters_for+ runs once, at declaration, with nil
|
|
112
|
+
# context and scope. Its keys win over the declared +only:+/+except:+.
|
|
113
|
+
def association_filters(serializer, options)
|
|
114
|
+
only = options[:only]
|
|
115
|
+
except = options[:except]
|
|
116
|
+
|
|
117
|
+
if serializer.respond_to?(:filters_for)
|
|
118
|
+
filters = serializer.filters_for(nil, nil)
|
|
119
|
+
only = filters[:only] if filters.key?(:only)
|
|
120
|
+
except = filters[:except] if filters.key?(:except)
|
|
96
121
|
end
|
|
97
|
-
serializer_const ||= Panko::SerializerResolver.resolve(name.to_s, self)
|
|
98
122
|
|
|
99
|
-
|
|
123
|
+
[only, except]
|
|
124
|
+
end
|
|
100
125
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
def resolve_association_serializer(name, options, kind)
|
|
127
|
+
serializer = options[:serializer] || options[:each_serializer]
|
|
128
|
+
serializer = Panko::SerializerResolver.resolve(serializer, self) if serializer.is_a?(String)
|
|
129
|
+
serializer ||= Panko::SerializerResolver.resolve(name.to_s, self)
|
|
130
|
+
raise "Can't find serializer for #{self.name}.#{name} #{kind} relationship." if serializer.nil?
|
|
131
|
+
serializer
|
|
106
132
|
end
|
|
107
133
|
end
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return if options[:_skip_init]
|
|
135
|
+
# A literal +{}+ default would allocate a Hash on every +.new+.
|
|
136
|
+
EMPTY_OPTIONS = {}.freeze
|
|
112
137
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
end
|
|
138
|
+
def initialize(options = EMPTY_OPTIONS)
|
|
139
|
+
# Unset ivars read as nil, so +.new+ skips four Hash lookups and four writes.
|
|
140
|
+
return if options.equal?(EMPTY_OPTIONS)
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
@
|
|
142
|
+
@context = options[:context]
|
|
143
|
+
@scope = options[:scope]
|
|
144
|
+
@only = options[:only]
|
|
145
|
+
@except = options[:except]
|
|
120
146
|
end
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
|
|
148
|
+
# Generated code sets these per record on the instance a method field runs on.
|
|
149
|
+
attr_reader :object, :context, :scope
|
|
150
|
+
|
|
151
|
+
# The class-level view, narrowed by this instance's +only+/+except+ and +filters_for+.
|
|
152
|
+
def descriptor
|
|
153
|
+
klass = self.class
|
|
154
|
+
filters = if @only || @except || klass._cg_has_filters_for
|
|
155
|
+
Panko::CodeGen::Runtime.runtime_filters(klass, @context, @scope, @only, @except)
|
|
156
|
+
end
|
|
157
|
+
base = Panko::Descriptor.for(klass)
|
|
158
|
+
filters ? Panko::Descriptor::Filtered.new(base, filters) : base
|
|
124
159
|
end
|
|
125
160
|
|
|
126
|
-
|
|
127
|
-
|
|
161
|
+
# Both methods inline the pool checkout instead of calling a shared Runtime
|
|
162
|
+
# method, which would go polymorphic once an app has more than one serializer.
|
|
128
163
|
|
|
129
164
|
def serialize(object)
|
|
130
|
-
|
|
165
|
+
klass = self.class
|
|
166
|
+
cached = klass._cg_last_hash
|
|
167
|
+
pool = if cached && object.instance_of?(cached[0])
|
|
168
|
+
cached[1]
|
|
169
|
+
else
|
|
170
|
+
Panko::CodeGen::SerializerCache.variant_pool(klass, :hash, object.class)
|
|
171
|
+
end
|
|
172
|
+
filters = if @only || @except || klass._cg_has_filters_for
|
|
173
|
+
Panko::CodeGen::Runtime.runtime_filters(klass, @context, @scope, @only, @except)
|
|
174
|
+
end
|
|
175
|
+
stack = pool.stack
|
|
176
|
+
instance = stack.pop || pool.build
|
|
177
|
+
begin
|
|
178
|
+
instance.serialize_one(object, context: @context, scope: @scope, filters: filters)
|
|
179
|
+
ensure
|
|
180
|
+
# A pooled instance must not hold the last record or request context.
|
|
181
|
+
instance._release
|
|
182
|
+
stack.push(instance)
|
|
183
|
+
end
|
|
131
184
|
end
|
|
132
185
|
|
|
133
186
|
def serialize_to_json(object)
|
|
134
|
-
|
|
187
|
+
klass = self.class
|
|
188
|
+
cached = klass._cg_last_json
|
|
189
|
+
pool = if cached && object.instance_of?(cached[0])
|
|
190
|
+
cached[1]
|
|
191
|
+
else
|
|
192
|
+
Panko::CodeGen::SerializerCache.variant_pool(klass, :json, object.class)
|
|
193
|
+
end
|
|
194
|
+
filters = if @only || @except || klass._cg_has_filters_for
|
|
195
|
+
Panko::CodeGen::Runtime.runtime_filters(klass, @context, @scope, @only, @except)
|
|
196
|
+
end
|
|
197
|
+
stack = pool.stack
|
|
198
|
+
instance = stack.pop || pool.build
|
|
199
|
+
begin
|
|
200
|
+
instance.serialize_one(object, context: @context, scope: @scope, filters: filters)
|
|
201
|
+
ensure
|
|
202
|
+
instance._release
|
|
203
|
+
stack.push(instance)
|
|
204
|
+
end
|
|
135
205
|
end
|
|
136
206
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
207
|
+
# Writes +object+ into +writer+ (an +Oj::StringWriter+) as the value of +key+, or as the
|
|
208
|
+
# next element when +key+ is nil. The bytes match what +serialize_to_json+ returns.
|
|
209
|
+
def write_json(object, writer, key = nil)
|
|
210
|
+
klass = self.class
|
|
211
|
+
cached = klass._cg_last_json
|
|
212
|
+
pool = if cached && object.instance_of?(cached[0])
|
|
213
|
+
cached[1]
|
|
214
|
+
else
|
|
215
|
+
Panko::CodeGen::SerializerCache.variant_pool(klass, :json, object.class)
|
|
216
|
+
end
|
|
217
|
+
filters = if @only || @except || klass._cg_has_filters_for
|
|
218
|
+
Panko::CodeGen::Runtime.runtime_filters(klass, @context, @scope, @only, @except)
|
|
219
|
+
end
|
|
220
|
+
stack = pool.stack
|
|
221
|
+
instance = stack.pop || pool.build
|
|
222
|
+
begin
|
|
223
|
+
instance.serialize_one_to(writer, object, key, context: @context, scope: @scope, filters: filters)
|
|
224
|
+
ensure
|
|
225
|
+
instance._release
|
|
226
|
+
stack.push(instance)
|
|
227
|
+
end
|
|
144
228
|
end
|
|
145
229
|
end
|
|
146
230
|
end
|
|
@@ -21,14 +21,8 @@ class Panko::SerializerResolver
|
|
|
21
21
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
from.module_parent_name
|
|
27
|
-
end
|
|
28
|
-
else
|
|
29
|
-
def namespace_for(from)
|
|
30
|
-
from.parent_name
|
|
31
|
-
end
|
|
24
|
+
def namespace_for(from)
|
|
25
|
+
from.module_parent_name
|
|
32
26
|
end
|
|
33
27
|
|
|
34
28
|
def safe_serializer_get(name)
|
data/lib/panko/version.rb
CHANGED
data/lib/panko_serializer.rb
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "panko/version"
|
|
4
|
-
require "panko/
|
|
5
|
-
require "panko/association"
|
|
4
|
+
require "panko/config"
|
|
6
5
|
require "panko/serializer"
|
|
6
|
+
require "panko/compile_all"
|
|
7
|
+
require "panko/descriptor"
|
|
7
8
|
require "panko/array_serializer"
|
|
8
9
|
require "panko/response"
|
|
9
10
|
require "panko/serializer_resolver"
|
|
10
|
-
require "panko/object_writer"
|
|
11
11
|
|
|
12
|
-
# C Extension
|
|
13
12
|
require "oj"
|
|
14
|
-
require "panko/panko_serializer"
|