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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panko::CodeGen
|
|
4
|
+
module Generators
|
|
5
|
+
# Mutual-recursion peers +require_relative+ each other. Ruby allows this because
|
|
6
|
+
# a peer's constant is read only when a method runs, after both files have loaded.
|
|
7
|
+
module Fanout
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def emit_files(descriptor, output:, config:)
|
|
11
|
+
cyclic_ids = CycleMembership.cyclic_descriptor_ids(descriptor)
|
|
12
|
+
DescriptorWalk.in_emit_order(descriptor).map do |desc|
|
|
13
|
+
{
|
|
14
|
+
descriptor: desc,
|
|
15
|
+
basename: basename_for(desc, output),
|
|
16
|
+
source: build_file(desc, output, config, cyclic_ids)
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def basename_for(descriptor, output)
|
|
22
|
+
"#{snake_case(descriptor.name)}_#{output}.rb"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def snake_case(camel)
|
|
26
|
+
camel.gsub(/(?<=.)([A-Z])/, '_\1').downcase
|
|
27
|
+
end
|
|
28
|
+
private_class_method :snake_case
|
|
29
|
+
|
|
30
|
+
def build_file(descriptor, output, config, cyclic_ids)
|
|
31
|
+
builder = CodeBuilder.new
|
|
32
|
+
builder.line "# frozen_string_literal: true"
|
|
33
|
+
builder.blank
|
|
34
|
+
Banner.emit(builder, descriptor, output: output, config: config)
|
|
35
|
+
deps = ordered_dependencies(descriptor)
|
|
36
|
+
deps.each do |dep|
|
|
37
|
+
builder.line %(require_relative "#{snake_case(dep.name)}_#{output}")
|
|
38
|
+
end
|
|
39
|
+
builder.blank if deps.any?
|
|
40
|
+
emitter = ClassEmitter.new(Panko::CodeGen::Generator.sink_for(output))
|
|
41
|
+
emitter.emit_class(descriptor, config, builder, cyclic_ids)
|
|
42
|
+
builder.to_s + "\n"
|
|
43
|
+
end
|
|
44
|
+
private_class_method :build_file
|
|
45
|
+
|
|
46
|
+
# Seeded with +descriptor+ itself: a self-loop needs no +require_relative+.
|
|
47
|
+
def ordered_dependencies(descriptor)
|
|
48
|
+
seen = {descriptor.__id__ => true}
|
|
49
|
+
deps = []
|
|
50
|
+
descriptor.associations.flat_map(&:descriptors).each do |target|
|
|
51
|
+
next if seen[target.__id__]
|
|
52
|
+
seen[target.__id__] = true
|
|
53
|
+
deps << target
|
|
54
|
+
end
|
|
55
|
+
deps
|
|
56
|
+
end
|
|
57
|
+
private_class_method :ordered_dependencies
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panko::CodeGen
|
|
4
|
+
module Generators
|
|
5
|
+
# Indexes are written into each Field's +filters.drops?(N)+ check, so
|
|
6
|
+
# filtering a Field costs one Array lookup instead of a Hash lookup.
|
|
7
|
+
module FieldIndex
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Look indexes up by +GeneratedNames.filter_key+, never by position:
|
|
11
|
+
# {FieldWalk} emits Associations before Method Attributes.
|
|
12
|
+
def build(descriptor)
|
|
13
|
+
index = {}
|
|
14
|
+
i = 0
|
|
15
|
+
fields = descriptor.attributes + descriptor.method_attributes + descriptor.associations
|
|
16
|
+
fields.each do |field|
|
|
17
|
+
index[GeneratedNames.filter_key(field)] = i
|
|
18
|
+
i += 1
|
|
19
|
+
end
|
|
20
|
+
index
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The +name: N+ shorthand parses only for identifier-style names.
|
|
24
|
+
def to_hash_literal(field_index)
|
|
25
|
+
return "{}" if field_index.empty?
|
|
26
|
+
pairs = field_index.map { |name, idx| "#{name}: #{idx}" }
|
|
27
|
+
"{#{pairs.join(", ")}}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panko::CodeGen
|
|
4
|
+
module Generators
|
|
5
|
+
# Both Record-access strategies and both output modes share this walk,
|
|
6
|
+
# so field order and filter indexes cannot differ between them.
|
|
7
|
+
module FieldWalk
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# +attribute_emit+ lets the Specialized strategy pick each Attribute's
|
|
11
|
+
# read from the Model's column metadata instead of one +read_expr+ for all.
|
|
12
|
+
def emit_fields(descriptor, config, field_index, builder, sink, read_expr:, attribute_emit: nil)
|
|
13
|
+
sink.open_record(builder)
|
|
14
|
+
descriptor.attributes.each do |attribute|
|
|
15
|
+
index = field_index.fetch(GeneratedNames.filter_key(attribute))
|
|
16
|
+
if attribute_emit
|
|
17
|
+
attribute_emit.call(attribute, index)
|
|
18
|
+
else
|
|
19
|
+
sink.attribute(attribute, read_expr.call(attribute.source), config, index, builder)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
descriptor.associations.each do |association|
|
|
23
|
+
sink.association(
|
|
24
|
+
association, read_expr.call(association.source), config,
|
|
25
|
+
field_index.fetch(GeneratedNames.filter_key(association)), builder
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
descriptor.method_attributes.each do |method_attribute|
|
|
29
|
+
sink.method_attribute(
|
|
30
|
+
method_attribute, config,
|
|
31
|
+
field_index.fetch(GeneratedNames.filter_key(method_attribute)), builder
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
sink.close_record(builder)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panko::CodeGen
|
|
4
|
+
module Generators
|
|
5
|
+
# Names that generated source and the emitters must agree on, kept here
|
|
6
|
+
# so a rename edits one method.
|
|
7
|
+
module GeneratedNames
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def class_name(descriptor, suffix)
|
|
11
|
+
"#{descriptor.name}_#{suffix}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serializer_ivar(association)
|
|
15
|
+
"@#{association.name}_serializer"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def variant_serializer_ivar(association, index)
|
|
19
|
+
"#{serializer_ivar(association)}_#{index}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def callable_ivar(method_attribute)
|
|
23
|
+
"@cb_#{method_attribute.name}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def if_guard_ivar(association)
|
|
27
|
+
"@cb_if_#{association.name}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def write_one
|
|
31
|
+
"_write_one"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_hash
|
|
35
|
+
"_to_hash"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def generic_write_one
|
|
39
|
+
"_generic_write_one"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def generic_to_hash
|
|
43
|
+
"_generic_to_hash"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def write_one_hash
|
|
47
|
+
"_write_one_hash"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_one_object
|
|
51
|
+
"_write_one_object"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_hash_hash
|
|
55
|
+
"_to_hash_hash"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_hash_object
|
|
59
|
+
"_to_hash_object"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def field_index_const
|
|
63
|
+
"FIELD_INDEX"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Associations are keyed by Source, not output name, because nested
|
|
67
|
+
# filters are looked up by Source (+Filter::Indexed#child+).
|
|
68
|
+
def filter_key(field)
|
|
69
|
+
field.is_a?(Association) ? field.source : field.name
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panko::CodeGen
|
|
4
|
+
module Generators
|
|
5
|
+
# Values go through +Panko::CodeGen.cast_datetime+ so Hash mode matches what
|
|
6
|
+
# Oj's +:rails+ mode does to them in JSON mode.
|
|
7
|
+
class HashSink < Sink
|
|
8
|
+
def output
|
|
9
|
+
:hash
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def suffix
|
|
13
|
+
"Hash"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def entry_name
|
|
17
|
+
GeneratedNames.to_hash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def generic_entry_name
|
|
21
|
+
GeneratedNames.generic_to_hash
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def split_hash_helper
|
|
25
|
+
GeneratedNames.to_hash_hash
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def split_object_helper
|
|
29
|
+
GeneratedNames.to_hash_object
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def entry_params
|
|
33
|
+
"record, context, scope, filters"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Hash mode needs no constants beyond +FIELD_INDEX+.
|
|
37
|
+
def emit_class_constants(descriptor, config, builder)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def emit_serialize_one(config, builder)
|
|
41
|
+
signature = config.supports_root_key ?
|
|
42
|
+
"def serialize_one(record, context: nil, scope: nil, filters: nil, root_key: nil)" :
|
|
43
|
+
"def serialize_one(record, context: nil, scope: nil, filters: nil)"
|
|
44
|
+
builder.line signature
|
|
45
|
+
builder.indent do
|
|
46
|
+
builder.line "filters = Panko::CodeGen::Filter.wrap(filters, #{GeneratedNames.field_index_const})"
|
|
47
|
+
if config.supports_root_key
|
|
48
|
+
builder.line "validate_root_key!(root_key)"
|
|
49
|
+
builder.line "result = #{GeneratedNames.to_hash}(record, context, scope, filters)"
|
|
50
|
+
builder.line "root_key ? {root_key => result} : result"
|
|
51
|
+
else
|
|
52
|
+
builder.line "#{GeneratedNames.to_hash}(record, context, scope, filters)"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
builder.line "end"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def emit_serialize_many(config, builder)
|
|
59
|
+
signature = config.supports_root_key ?
|
|
60
|
+
"def serialize_many(records, context: nil, scope: nil, filters: nil, root_key: nil)" :
|
|
61
|
+
"def serialize_many(records, context: nil, scope: nil, filters: nil)"
|
|
62
|
+
builder.line signature
|
|
63
|
+
builder.indent do
|
|
64
|
+
builder.line "filters = Panko::CodeGen::Filter.wrap(filters, #{GeneratedNames.field_index_const})"
|
|
65
|
+
if config.supports_root_key
|
|
66
|
+
builder.line "validate_root_key!(root_key)"
|
|
67
|
+
builder.line "result = records.map { |r| #{GeneratedNames.to_hash}(r, context, scope, filters) }"
|
|
68
|
+
builder.line "root_key ? {root_key => result} : result"
|
|
69
|
+
else
|
|
70
|
+
builder.line "records.map { |r| #{GeneratedNames.to_hash}(r, context, scope, filters) }"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
builder.line "end"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def open_record(builder)
|
|
77
|
+
builder.line "result = {}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def close_record(builder)
|
|
81
|
+
builder.line "result"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# +cast: false+ skips +cast_datetime+ when the column type proves the cast would
|
|
85
|
+
# return the value unchanged; the wrapper otherwise runs on every write.
|
|
86
|
+
def attribute(attribute, read_expr, config, index, builder, cast: true)
|
|
87
|
+
value_expr = cast ? "Panko::CodeGen.cast_datetime(#{read_expr})" : read_expr
|
|
88
|
+
builder.line "unless filters.drops?(#{index})"
|
|
89
|
+
builder.indent do
|
|
90
|
+
builder.line "result[#{key_literal(attribute.name, config)}] = #{value_expr}"
|
|
91
|
+
end
|
|
92
|
+
builder.line "end"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def specialized_attribute(attribute, ar_model, config, index, builder)
|
|
96
|
+
if RecordAccess::Specialized.datetime_column_attribute?(attribute, ar_model)
|
|
97
|
+
datetime_column_attribute(attribute, config, index, builder)
|
|
98
|
+
else
|
|
99
|
+
attribute(
|
|
100
|
+
attribute,
|
|
101
|
+
RecordAccess::Specialized.attribute_read_expr(attribute, ar_model),
|
|
102
|
+
config,
|
|
103
|
+
index,
|
|
104
|
+
builder,
|
|
105
|
+
cast: !RecordAccess::Specialized.plain_column_attribute?(attribute, ar_model)
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def method_attribute(method_attribute, config, index, builder)
|
|
111
|
+
builder.line "unless filters.drops?(#{index})"
|
|
112
|
+
builder.indent do
|
|
113
|
+
builder.line "value = #{method_attribute_call_expression(method_attribute)}"
|
|
114
|
+
builder.line "unless value.equal?(Panko::CodeGen::SKIP)"
|
|
115
|
+
builder.indent do
|
|
116
|
+
builder.line "result[#{key_literal(method_attribute.name, config)}] = Panko::CodeGen.cast_datetime(value)"
|
|
117
|
+
end
|
|
118
|
+
builder.line "end"
|
|
119
|
+
end
|
|
120
|
+
builder.line "end"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def association(association, source_read_expr, config, index, builder)
|
|
124
|
+
key_lit = key_literal(association.name, config)
|
|
125
|
+
builder.line "unless filters.drops?(#{index})"
|
|
126
|
+
builder.indent do
|
|
127
|
+
with_if_guard(association, builder) do
|
|
128
|
+
case association.kind
|
|
129
|
+
when :has_one
|
|
130
|
+
builder.line "value = #{source_read_expr}"
|
|
131
|
+
if config.null_for_missing_has_one
|
|
132
|
+
has_one_default(association, key_lit, builder)
|
|
133
|
+
else
|
|
134
|
+
has_one_omit(association, key_lit, builder)
|
|
135
|
+
end
|
|
136
|
+
when :has_many
|
|
137
|
+
has_many(association, source_read_expr, key_lit, builder)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
builder.line "end"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
def key_literal(name, config)
|
|
147
|
+
case config.hash_output_key_type
|
|
148
|
+
when :symbol then ":#{name}"
|
|
149
|
+
else %("#{name}")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def has_one_default(association, key_lit, builder)
|
|
154
|
+
builder.line "result[#{key_lit}] = if value.nil?"
|
|
155
|
+
builder.indent { builder.line "nil" }
|
|
156
|
+
builder.line "else"
|
|
157
|
+
builder.indent do
|
|
158
|
+
child_write(association, "value", builder) do |ivar|
|
|
159
|
+
"#{ivar}.#{GeneratedNames.to_hash}(value, context, scope, #{child_filter_expr(association)})"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
builder.line "end"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def has_one_omit(association, key_lit, builder)
|
|
166
|
+
builder.line "unless value.nil?"
|
|
167
|
+
builder.indent do
|
|
168
|
+
if association.variants.empty?
|
|
169
|
+
builder.line "result[#{key_lit}] = #{GeneratedNames.serializer_ivar(association)}." \
|
|
170
|
+
"#{GeneratedNames.to_hash}(value, context, scope, #{child_filter_expr(association)})"
|
|
171
|
+
else
|
|
172
|
+
builder.line "result[#{key_lit}] = begin"
|
|
173
|
+
builder.indent do
|
|
174
|
+
child_write(association, "value", builder) do |ivar|
|
|
175
|
+
"#{ivar}.#{GeneratedNames.to_hash}(value, context, scope, #{child_filter_expr(association)})"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
builder.line "end"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
builder.line "end"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# +child_filter+ is looked up once per record, not once per element.
|
|
185
|
+
def has_many(association, source_read_expr, key_lit, builder)
|
|
186
|
+
builder.line "child_filter = #{child_filter_expr(association)}"
|
|
187
|
+
if association.variants.empty?
|
|
188
|
+
builder.line(
|
|
189
|
+
"result[#{key_lit}] = #{source_read_expr}.map { |element| " \
|
|
190
|
+
"#{GeneratedNames.serializer_ivar(association)}.#{GeneratedNames.to_hash}(element, context, scope, child_filter) }"
|
|
191
|
+
)
|
|
192
|
+
return
|
|
193
|
+
end
|
|
194
|
+
builder.line "result[#{key_lit}] = #{source_read_expr}.map do |element|"
|
|
195
|
+
builder.indent do
|
|
196
|
+
child_write(association, "element", builder) do |ivar|
|
|
197
|
+
"#{ivar}.#{GeneratedNames.to_hash}(element, context, scope, child_filter)"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
builder.line "end"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# +format_raw+ returns nil for raw values it cannot format; the typed-read
|
|
204
|
+
# fallback then goes through +cast_datetime+ so a Time still becomes a String.
|
|
205
|
+
def datetime_column_attribute(attribute, config, index, builder)
|
|
206
|
+
source_lit = %("#{attribute.source}")
|
|
207
|
+
builder.line "unless filters.drops?(#{index})"
|
|
208
|
+
builder.indent do
|
|
209
|
+
builder.line "value = Panko::CodeGen::DateTimeFormat.format_raw(record.read_attribute_before_type_cast(#{source_lit}))"
|
|
210
|
+
builder.line "result[#{key_literal(attribute.name, config)}] = value || " \
|
|
211
|
+
"Panko::CodeGen.cast_datetime(record._read_attribute(#{source_lit}))"
|
|
212
|
+
end
|
|
213
|
+
builder.line "end"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|