panko_serializer 0.8.4 → 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 +8 -6
- 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 -99
- data/.clang-format +0 -3
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/database_matrix.yml +0 -85
- data/.github/workflows/docs.yml +0 -39
- data/.github/workflows/lint.yml +0 -30
- data/.github/workflows/tests.yml +0 -34
- data/.gitignore +0 -20
- data/.rspec +0 -2
- data/.rubocop.yml +0 -37
- data/Appraisals +0 -28
- data/Gemfile +0 -37
- data/Rakefile +0 -93
- data/benchmarks/BENCHMARKS.md +0 -48
- data/benchmarks/allocs.rb +0 -23
- data/benchmarks/app.rb +0 -11
- data/benchmarks/benchmarking_support.rb +0 -44
- data/benchmarks/bm_ams_0_10.rb +0 -43
- data/benchmarks/bm_object_writer.rb +0 -65
- data/benchmarks/bm_panko_json.rb +0 -60
- data/benchmarks/bm_panko_object.rb +0 -46
- data/benchmarks/bm_plain_object.rb +0 -99
- data/benchmarks/bm_serialization_descriptor.rb +0 -76
- data/benchmarks/bm_serializer_resolver.rb +0 -22
- data/benchmarks/bm_to_object.rb +0 -81
- data/benchmarks/profile.rb +0 -86
- data/benchmarks/sanity.rb +0 -83
- data/benchmarks/setup.rb +0 -56
- data/benchmarks/type_casts/bm_active_record.rb +0 -58
- data/benchmarks/type_casts/bm_panko.rb +0 -65
- data/benchmarks/type_casts/support.rb +0 -27
- data/docs/docs/associations.md +0 -107
- data/docs/docs/attributes.md +0 -150
- data/docs/docs/design-choices.md +0 -127
- data/docs/docs/getting-started.md +0 -70
- data/docs/docs/introduction.md +0 -13
- data/docs/docs/performance.md +0 -32
- data/docs/docs/response-bag.md +0 -83
- data/docs/docusaurus.config.js +0 -86
- data/docs/package-lock.json +0 -17872
- data/docs/package.json +0 -18
- data/docs/sidebars.json +0 -15
- data/docs/src/css/customTheme.css +0 -9
- data/docs/static/.DS_Store +0 -0
- data/docs/static/CNAME +0 -1
- data/docs/static/css/custom.css +0 -51
- data/docs/static/img/favicon.ico +0 -0
- data/docs/static/img/oss_logo.png +0 -0
- data/docs/static/img/undraw_code_review.svg +0 -1
- data/docs/static/img/undraw_monitor.svg +0 -1
- data/docs/static/img/undraw_note_list.svg +0 -1
- data/docs/static/img/undraw_online.svg +0 -1
- data/docs/static/img/undraw_open_source.svg +0 -1
- data/docs/static/img/undraw_operating_system.svg +0 -1
- data/docs/static/img/undraw_react.svg +0 -1
- data/docs/static/img/undraw_tweetstorm.svg +0 -1
- data/docs/static/img/undraw_youtube_tutorial.svg +0 -1
- data/docs/static/index.html +0 -14
- 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 -398
- 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/gemfiles/7.2.0.gemfile +0 -41
- data/gemfiles/7.2.0.gemfile.lock +0 -221
- data/gemfiles/8.0.0.gemfile +0 -41
- data/gemfiles/8.0.0.gemfile.lock +0 -239
- data/gemfiles/8.1.0.gemfile +0 -41
- data/gemfiles/8.1.0.gemfile.lock +0 -248
- 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/panko_serializer.gemspec +0 -36
data/benchmarks/sanity.rb
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "benchmarking_support"
|
|
4
|
-
require_relative "app"
|
|
5
|
-
require_relative "setup"
|
|
6
|
-
|
|
7
|
-
class PostWithAliasModel < ActiveRecord::Base
|
|
8
|
-
self.table_name = "posts"
|
|
9
|
-
|
|
10
|
-
alias_attribute :new_id, :id
|
|
11
|
-
alias_attribute :new_body, :body
|
|
12
|
-
alias_attribute :new_title, :title
|
|
13
|
-
alias_attribute :new_author_id, :author_id
|
|
14
|
-
alias_attribute :new_created_at, :created_at
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
class PostWithAliasFastSerializer < Panko::Serializer
|
|
18
|
-
attributes :new_id, :new_body, :new_title, :new_author_id, :new_created_at
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def benchmark_aliased(prefix, serializer, options = {})
|
|
22
|
-
posts = PostWithAliasModel.all.to_a
|
|
23
|
-
posts_50 = posts.first(50).to_a
|
|
24
|
-
|
|
25
|
-
merged_options = options.merge(each_serializer: serializer)
|
|
26
|
-
|
|
27
|
-
Benchmark.run("Panko_#{prefix}_PostWithAliasModels_#{posts.count}") do
|
|
28
|
-
Panko::ArraySerializer.new(posts, merged_options).to_json
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
Benchmark.run("Panko_#{prefix}_Posts_50") do
|
|
32
|
-
Panko::ArraySerializer.new(posts_50, merged_options).to_json
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
class AuthorFastSerializer < Panko::Serializer
|
|
37
|
-
attributes :id, :name
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
class PostFastSerializer < Panko::Serializer
|
|
41
|
-
attributes :id, :body, :title, :author_id, :created_at
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
class PostFastWithMethodCallSerializer < Panko::Serializer
|
|
45
|
-
attributes :id, :body, :title, :author_id, :created_at, :method_call
|
|
46
|
-
|
|
47
|
-
def method_call
|
|
48
|
-
object.id
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
class PostWithHasOneFastSerializer < Panko::Serializer
|
|
53
|
-
attributes :id, :body, :title, :author_id, :created_at
|
|
54
|
-
|
|
55
|
-
has_one :author, serializer: AuthorFastSerializer
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
class AuthorWithHasManyFastSerializer < Panko::Serializer
|
|
59
|
-
attributes :id, :name
|
|
60
|
-
|
|
61
|
-
has_many :posts, serializer: PostFastSerializer
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def benchmark(prefix, serializer, options = {})
|
|
65
|
-
posts = Benchmark.data[:all]
|
|
66
|
-
|
|
67
|
-
merged_options = options.merge(each_serializer: serializer)
|
|
68
|
-
|
|
69
|
-
Benchmark.run("Panko_#{prefix}_Posts_#{posts.count}") do
|
|
70
|
-
Panko::ArraySerializer.new(posts, merged_options).to_json
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
posts_50 = Benchmark.data[:small]
|
|
74
|
-
|
|
75
|
-
Benchmark.run("Panko_#{prefix}_Posts_50") do
|
|
76
|
-
Panko::ArraySerializer.new(posts_50, merged_options).to_json
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
benchmark "Simple", PostFastSerializer
|
|
81
|
-
benchmark "HasOne", PostWithHasOneFastSerializer
|
|
82
|
-
benchmark "SimpleWithMethodCall", PostFastWithMethodCallSerializer
|
|
83
|
-
benchmark_aliased "Simple (aliased)", PostWithAliasFastSerializer
|
data/benchmarks/setup.rb
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
###########################################
|
|
4
|
-
# Setup active record models
|
|
5
|
-
##########################################
|
|
6
|
-
require "active_record"
|
|
7
|
-
require "sqlite3"
|
|
8
|
-
require "securerandom"
|
|
9
|
-
|
|
10
|
-
# Change the following to reflect your database settings
|
|
11
|
-
ActiveRecord::Base.establish_connection(
|
|
12
|
-
adapter: "sqlite3",
|
|
13
|
-
database: ":memory:"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
# Don't show migration output when constructing fake db
|
|
17
|
-
ActiveRecord::Migration.verbose = false
|
|
18
|
-
|
|
19
|
-
ActiveRecord::Schema.define do
|
|
20
|
-
create_table :authors, force: true do |t|
|
|
21
|
-
t.string :name
|
|
22
|
-
t.timestamps(null: false)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
create_table :posts, force: true do |t|
|
|
26
|
-
t.text :body
|
|
27
|
-
t.string :title
|
|
28
|
-
t.references :author
|
|
29
|
-
t.json :data
|
|
30
|
-
t.timestamps(null: false)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
class Author < ActiveRecord::Base
|
|
35
|
-
has_one :profile
|
|
36
|
-
has_many :posts
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
class Post < ActiveRecord::Base
|
|
40
|
-
belongs_to :author
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
Post.destroy_all
|
|
44
|
-
Author.destroy_all
|
|
45
|
-
|
|
46
|
-
# Build out the data to serialize
|
|
47
|
-
Post.transaction do
|
|
48
|
-
ENV.fetch("ITEMS_COUNT", "2300").to_i.times do
|
|
49
|
-
Post.create(
|
|
50
|
-
body: SecureRandom.hex(30),
|
|
51
|
-
title: SecureRandom.hex(20),
|
|
52
|
-
author: Author.create(name: SecureRandom.alphanumeric),
|
|
53
|
-
data: {a: 1, b: 2, c: 3}
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "support"
|
|
4
|
-
|
|
5
|
-
def ar_type_convert(type_klass, from, to)
|
|
6
|
-
converter = type_klass.new
|
|
7
|
-
|
|
8
|
-
assert type_klass.name, converter.deserialize(from), to
|
|
9
|
-
|
|
10
|
-
Benchmark.run("#{type_klass.name}_TypeCast") do
|
|
11
|
-
converter.deserialize(from)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
Benchmark.run("#{type_klass.name}_NoTypeCast") do
|
|
15
|
-
converter.deserialize(to)
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def utc_ar_time
|
|
20
|
-
date = DateTime.new(2017, 3, 4, 12, 45, 23)
|
|
21
|
-
tz = ActiveSupport::TimeZone.new("UTC")
|
|
22
|
-
from = date.in_time_zone(tz).iso8601
|
|
23
|
-
|
|
24
|
-
type = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.new
|
|
25
|
-
converter = ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter.new(type)
|
|
26
|
-
|
|
27
|
-
Benchmark.run("#{tz}_#{type.class.name}_TypeCast") do
|
|
28
|
-
converter.deserialize(from).iso8601
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def db_ar_time
|
|
33
|
-
type = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.new
|
|
34
|
-
converter = ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter.new(type)
|
|
35
|
-
|
|
36
|
-
from = "2017-07-10 09:26:40.937392"
|
|
37
|
-
|
|
38
|
-
Benchmark.run("ActiveRecord_Time_TypeCast_WithISO8601") do
|
|
39
|
-
converter.deserialize(from).iso8601
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
ar_type_convert ActiveRecord::Type::String, 1, "1"
|
|
44
|
-
ar_type_convert ActiveRecord::Type::Text, 1, "1"
|
|
45
|
-
ar_type_convert ActiveRecord::Type::Integer, "1", 1
|
|
46
|
-
ar_type_convert ActiveRecord::Type::Float, "1.23", 1.23
|
|
47
|
-
ar_type_convert ActiveRecord::Type::Boolean, "true", true
|
|
48
|
-
ar_type_convert ActiveRecord::Type::Boolean, "t", true
|
|
49
|
-
|
|
50
|
-
if check_if_exists "ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json"
|
|
51
|
-
ar_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json, '{"a":1}', {a: 1}
|
|
52
|
-
end
|
|
53
|
-
if check_if_exists "ActiveRecord::Type::Json"
|
|
54
|
-
ar_type_convert ActiveRecord::Type::Json, '{"a":1}', {a: 1}
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
db_ar_time
|
|
58
|
-
utc_ar_time
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "support"
|
|
4
|
-
|
|
5
|
-
def panko_type_convert(type_klass, from, to)
|
|
6
|
-
converter = type_klass.new
|
|
7
|
-
assert type_klass.name.to_s, Panko._type_cast(converter, from), to
|
|
8
|
-
|
|
9
|
-
Benchmark.run("#{type_klass.name}_TypeCast") do
|
|
10
|
-
Panko._type_cast(converter, from)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
Benchmark.run("#{type_klass.name}_NoTypeCast") do
|
|
14
|
-
Panko._type_cast(converter, to)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def utc_panko_time
|
|
19
|
-
date = DateTime.new(2017, 3, 4, 12, 45, 23)
|
|
20
|
-
tz = ActiveSupport::TimeZone.new("UTC")
|
|
21
|
-
from = date.in_time_zone(tz).iso8601
|
|
22
|
-
|
|
23
|
-
type = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.new
|
|
24
|
-
converter = ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter.new(type)
|
|
25
|
-
|
|
26
|
-
to = Panko._type_cast(converter, from)
|
|
27
|
-
|
|
28
|
-
Benchmark.run("#{tz}_#{type.class.name}_TypeCast") do
|
|
29
|
-
Panko._type_cast(converter, from)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
Benchmark.run("#{tz}_#{type.class.name}_NoTypeCast") do
|
|
33
|
-
Panko._type_cast(converter, to)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def db_panko_time
|
|
38
|
-
type = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.new
|
|
39
|
-
converter = ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter.new(type)
|
|
40
|
-
|
|
41
|
-
from = "2017-07-10 09:26:40.937392"
|
|
42
|
-
|
|
43
|
-
Benchmark.run("Panko_Time_TypeCast") do
|
|
44
|
-
Panko._type_cast(converter, from)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
panko_type_convert ActiveRecord::Type::String, 1, "1"
|
|
49
|
-
panko_type_convert ActiveRecord::Type::Text, 1, "1"
|
|
50
|
-
panko_type_convert ActiveRecord::Type::Integer, "1", 1
|
|
51
|
-
panko_type_convert ActiveRecord::Type::Float, "1.23", 1.23
|
|
52
|
-
panko_type_convert ActiveRecord::Type::Boolean, "true", true
|
|
53
|
-
panko_type_convert ActiveRecord::Type::Boolean, "t", true
|
|
54
|
-
|
|
55
|
-
if check_if_exists "ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json"
|
|
56
|
-
panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json, '{"a":1}', '{"a":1}'
|
|
57
|
-
end
|
|
58
|
-
if check_if_exists "ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb"
|
|
59
|
-
panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb, '{"a":1}', '{"a":1}'
|
|
60
|
-
end
|
|
61
|
-
if check_if_exists "ActiveRecord::Type::Json"
|
|
62
|
-
panko_type_convert ActiveRecord::Type::Json, '{"a":1}', '{"a":1}'
|
|
63
|
-
end
|
|
64
|
-
db_panko_time
|
|
65
|
-
utc_panko_time
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_record"
|
|
4
|
-
require "active_record/connection_adapters/postgresql_adapter"
|
|
5
|
-
require "active_model"
|
|
6
|
-
require "active_support/all"
|
|
7
|
-
require "pg"
|
|
8
|
-
require "oj"
|
|
9
|
-
|
|
10
|
-
require_relative "../benchmarking_support"
|
|
11
|
-
require_relative "../../lib/panko_serializer"
|
|
12
|
-
|
|
13
|
-
def assert(type_name, from, to)
|
|
14
|
-
raise "#{type_name} - #{from.class} is not equals to #{to.class}" unless from.to_json == to.to_json
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def check_if_exists(module_name)
|
|
18
|
-
mod = begin
|
|
19
|
-
module_name.constantize
|
|
20
|
-
rescue
|
|
21
|
-
nil
|
|
22
|
-
end
|
|
23
|
-
return true if mod
|
|
24
|
-
false unless mod
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
Time.zone = "UTC"
|
data/docs/docs/associations.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: associations
|
|
3
|
-
title: Associations
|
|
4
|
-
sidebar_label: Associations
|
|
5
|
-
---
|
|
6
|
-
A serializer can define it's own associations - both `has_many` and `has_one` to serialize under the context of the object.
|
|
7
|
-
|
|
8
|
-
For example:
|
|
9
|
-
|
|
10
|
-
```ruby
|
|
11
|
-
|
|
12
|
-
class PostSerializer < Panko::Serializer
|
|
13
|
-
attributes :title, :body
|
|
14
|
-
|
|
15
|
-
has_one :author, serializer: AuthorSerializer
|
|
16
|
-
has_many :comments, each_serializer: CommentSerializer
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Associations with aliases
|
|
22
|
-
|
|
23
|
-
An association key name can be aliased with the `name` option.
|
|
24
|
-
|
|
25
|
-
For example:
|
|
26
|
-
the `actual_author` property will be converted to `alias_author`.
|
|
27
|
-
|
|
28
|
-
```ruby
|
|
29
|
-
|
|
30
|
-
class PostSerializer < Panko::Serializer
|
|
31
|
-
attributes :title, :body
|
|
32
|
-
|
|
33
|
-
has_one :actual_author, serializer: AuthorSerializer, name: :alias_author
|
|
34
|
-
has_many :comments, each_serializer: CommentSerializer
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Inference
|
|
40
|
-
|
|
41
|
-
Panko can find the type of the serializer by looking at the relationship name, so instead of specifying
|
|
42
|
-
the serializer at the above example, we can:
|
|
43
|
-
|
|
44
|
-
```ruby
|
|
45
|
-
|
|
46
|
-
class PostSerializer < Panko::Serializer
|
|
47
|
-
attributes :title, :body
|
|
48
|
-
|
|
49
|
-
has_one :author
|
|
50
|
-
has_many :comments
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
The logic of inferencing is:
|
|
56
|
-
|
|
57
|
-
- Take the name of the relationship (for example - `:author` / `:comments`) singularize and camelize it.
|
|
58
|
-
- Look for const defined with the name above and "Serializer" suffix (by using `Object.const_get`).
|
|
59
|
-
|
|
60
|
-
> If Panko can't find the serializer it will throw an error on startup time, for example: `Can't find serializer for PostSerializer.author has_one relationship`.
|
|
61
|
-
|
|
62
|
-
## Nested Filters
|
|
63
|
-
|
|
64
|
-
As talked before, Panko allows you to filter the attributes of a serializer.
|
|
65
|
-
But Panko lets you take that step further, and filters the attributes of you associations so you can re-use your serializers in your application.
|
|
66
|
-
|
|
67
|
-
For example, let's say one portion of the application needs to serialize a list of posts but only with their - `title`, `body`, author's id and comments id.
|
|
68
|
-
|
|
69
|
-
We can declare tailored serializer for this, or we can re-use the above defined serializer - `PostSerializer` and use nested filters.
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
|
|
73
|
-
posts = Post.all
|
|
74
|
-
|
|
75
|
-
Panko::ArraySerializer.new(posts, each_serializer: PostSerializer, only: {
|
|
76
|
-
instance: [:title, :body, :author, :comments],
|
|
77
|
-
author: [:id],
|
|
78
|
-
comments: [:id],
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Let's dissect the `only` option we passed:
|
|
84
|
-
|
|
85
|
-
- `instance` - list of attributes (and associations) we want to serialize for the current instance of the serializer, in this case - `PostSerializer`.
|
|
86
|
-
- `author`, `comments` - here we specify the list of attributes we want to serialize for each association.
|
|
87
|
-
|
|
88
|
-
It's important to note that Nested Filters are recursive, in other words, we can filter the association's associations.
|
|
89
|
-
|
|
90
|
-
For example, `CommentSerializer` has an `has_one` association `Author`, and for each `comments.author` we can only serialize it's name.
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
|
|
94
|
-
posts = Post.all
|
|
95
|
-
|
|
96
|
-
Panko::ArraySerializer.new(posts, only: {
|
|
97
|
-
instance: [:title, :body, :author, :comments],
|
|
98
|
-
author: [:id],
|
|
99
|
-
comments: {
|
|
100
|
-
instance: [:id, :author],
|
|
101
|
-
author: [:name]
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
As you see now in `comments` the `instance` have different meaning, the `CommentSerializer`.
|
data/docs/docs/attributes.md
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: attributes
|
|
3
|
-
title: Attributes
|
|
4
|
-
sidebar_label: Attributes
|
|
5
|
-
---
|
|
6
|
-
Attributes allow you to specify which record attributes you want to serialize.
|
|
7
|
-
|
|
8
|
-
There are two types of attributes:
|
|
9
|
-
|
|
10
|
-
- Field - simple columns defined on the record it self.
|
|
11
|
-
- Virtual/Method - this allows to include properties beyond simple fields.
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
|
|
15
|
-
class UserSerializer < Panko::Serializer
|
|
16
|
-
attributes :full_name
|
|
17
|
-
|
|
18
|
-
def full_name
|
|
19
|
-
"#{object.first_name} #{object.last_name}"
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Field Attributes
|
|
26
|
-
|
|
27
|
-
Using field attributes you can control which columns of the given ActiveRecord object you want to serialize.
|
|
28
|
-
|
|
29
|
-
Instead of relying on ActiveRecord to do it's type casting, Panko does on it's own for performance reasons (read more in [Design Choices](design-choices.md#type-casting)).
|
|
30
|
-
|
|
31
|
-
## Method Attributes
|
|
32
|
-
|
|
33
|
-
Method attributes are used when your serialized values can be derived from the object you are serializing.
|
|
34
|
-
|
|
35
|
-
The serializer's attribute methods can access the object being serialized as `object`:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
|
|
39
|
-
class PostSerializer < Panko::Serializer
|
|
40
|
-
attributes :author_name
|
|
41
|
-
|
|
42
|
-
def author_name
|
|
43
|
-
"#{object.author.first_name} #{object.author.last_name}"
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Another useful thing you can pass your serializer is `context`, a `context` is a bag of data whom your serializer may need.
|
|
50
|
-
|
|
51
|
-
For example, here we will pass feature flags:
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
|
|
55
|
-
class UserSerializer < Panko::Serializer
|
|
56
|
-
attributes :id, :email
|
|
57
|
-
|
|
58
|
-
def feature_flags
|
|
59
|
-
context[:feature_flags]
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
serializer = UserSerializer.new(context: {
|
|
64
|
-
feature_flags: FeatureFlags.all
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
serializer.serialize(User.first)
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Filters
|
|
72
|
-
|
|
73
|
-
Filters allows us to reduce the amount of attributes we can serialize, therefore reduce the data usage & performance of serializing.
|
|
74
|
-
|
|
75
|
-
There are two types of filters:
|
|
76
|
-
|
|
77
|
-
- only - use those attributes **only** and nothing else.
|
|
78
|
-
- except - all attributes **except** those attributes.
|
|
79
|
-
|
|
80
|
-
Usage example:
|
|
81
|
-
|
|
82
|
-
```ruby
|
|
83
|
-
|
|
84
|
-
class UserSerializer < Panko::Serializer
|
|
85
|
-
attributes :id, :name, :email
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# this line will return { 'name': '..' }
|
|
89
|
-
UserSerializer.new(only: [:name]).serialize(User.first)
|
|
90
|
-
|
|
91
|
-
# this line will return { 'id': '..', 'email': ... }
|
|
92
|
-
UserSerializer.new(except: [:name]).serialize(User.first)
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
> **Note** that if you want to user filter on an associations, the `:name` property is not taken into account.
|
|
97
|
-
If you have a `has_many :state_transitions, name: :history` association defined, the key to use in filters is
|
|
98
|
-
`:state_transitions` (e.g. `{ except: [:state_transitions] }`).
|
|
99
|
-
|
|
100
|
-
## Filters For
|
|
101
|
-
|
|
102
|
-
Sometimes you find yourself having the same filtering logic in actions. In order to
|
|
103
|
-
solve this duplication, Panko allows you to write the filters in the serializer.
|
|
104
|
-
|
|
105
|
-
```ruby
|
|
106
|
-
|
|
107
|
-
class UserSerializer < Panko::Serializer
|
|
108
|
-
attributes :id, :name, :email
|
|
109
|
-
|
|
110
|
-
def self.filters_for(context, scope)
|
|
111
|
-
{
|
|
112
|
-
only: [:name]
|
|
113
|
-
}
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# this line will return { 'name': '..' }
|
|
118
|
-
UserSerializer.serialize(User.first)
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
> See discussion in: [https:](https://github.com/yosiat/panko_serializer/issues/16)
|
|
123
|
-
|
|
124
|
-
## Aliases
|
|
125
|
-
|
|
126
|
-
Let's say we have an attribute name that we want to expose to client as different name, the current way of doing so is using method attribute, for example:
|
|
127
|
-
|
|
128
|
-
```ruby
|
|
129
|
-
|
|
130
|
-
class PostSerializer < Panko::Serializer
|
|
131
|
-
attributes :published_at
|
|
132
|
-
|
|
133
|
-
def published_at
|
|
134
|
-
object.created_at
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
The downside of this approach is that `created_at` skips Panko's type casting, therefore we get a direct hit on performance.
|
|
141
|
-
|
|
142
|
-
To fix this, we can use aliases:
|
|
143
|
-
|
|
144
|
-
```ruby
|
|
145
|
-
|
|
146
|
-
class PostSerializer < Panko::Serializer
|
|
147
|
-
aliases created_at: :published_at
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
```
|
data/docs/docs/design-choices.md
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: design-choices
|
|
3
|
-
title: Design Choices
|
|
4
|
-
sidebar_label: Design Choices
|
|
5
|
-
---
|
|
6
|
-
In short, Panko is a serializer for ActiveRecord objects (it can't serialize any other object), which strives for high performance & simple API (which is inspired by ActiveModelSerializers).
|
|
7
|
-
|
|
8
|
-
Its performance is achieved by:
|
|
9
|
-
|
|
10
|
-
- `Oj::StringWriter` - I will elaborate later.
|
|
11
|
-
- Type casting — instead of relying on ActiveRecord to do its type cast, Panko is doing it by itself.
|
|
12
|
-
- Figuring out the metadata, ahead of time — therefore, we ask less questions during the `serialization loop`.
|
|
13
|
-
|
|
14
|
-
## Serialization overview
|
|
15
|
-
|
|
16
|
-
First, let's start with an overview. Let's say we want to serialize an `User` object, which has
|
|
17
|
-
`first_name`, `last_name`, `age`, and `email` properties.
|
|
18
|
-
|
|
19
|
-
The serializer definition will be something like this:
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
|
|
23
|
-
class UserSerializer < Panko::Serializer
|
|
24
|
-
attributes :name, :age, :email
|
|
25
|
-
|
|
26
|
-
def name
|
|
27
|
-
"#{object.first_name} #{object.last_name}"
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
And the usage of this serializer will be:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
|
|
37
|
-
# fetch user from database
|
|
38
|
-
user = User.first
|
|
39
|
-
|
|
40
|
-
# create serializer, with empty options
|
|
41
|
-
serializer = UserSerializer.new
|
|
42
|
-
|
|
43
|
-
# serialize to JSON
|
|
44
|
-
serializer.serialize_to_json(user)
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Let's go over the steps that Panko will execute behind the scenes for this flow.
|
|
49
|
-
_I will skip the serializer definition part, because it's fairly simple and straightforward (see `lib/panko/serializer.rb`)._
|
|
50
|
-
|
|
51
|
-
First step, while initializing the UserSerializer, we will create a **Serialization Descriptor** for this class.
|
|
52
|
-
Serialization Descriptor's goal is to answer those questions:
|
|
53
|
-
|
|
54
|
-
- Which fields do we have? In our case, `:age`, `:email`.
|
|
55
|
-
- Which method fields do we have? In our case `:name`.
|
|
56
|
-
- Which associations do we have (and their serialization descriptors)?
|
|
57
|
-
|
|
58
|
-
The serialization description is also responsible for filtering the attributes (`only` \\ `except`).
|
|
59
|
-
|
|
60
|
-
Now, that we have the serialization descriptor, we are finished with the Ruby part of Panko, and all we did here is done in _initialization time_ and now we move to C code.
|
|
61
|
-
|
|
62
|
-
In C land, we take the `user` object and the serialization descriptor, and start the serialization process which is separated to 4 parts:
|
|
63
|
-
|
|
64
|
-
- Serializing Fields - looping through serialization descriptor's `fields` and read them from the ActiveRecord object (see `Type Casting`) and write them to the writer.
|
|
65
|
-
- Serializing Method Fields - creating (a cached) serializer instance, setting its `@object` and `@context`, calling all the method fields and writing them to the writer.
|
|
66
|
-
- Serializing associations — this is simple, once we have fields + method fields, we just repeat the process.
|
|
67
|
-
|
|
68
|
-
Once this is finished, we have a nice JSON string.
|
|
69
|
-
Now let's dig deeper.
|
|
70
|
-
|
|
71
|
-
## Interesting parts
|
|
72
|
-
|
|
73
|
-
### Oj::StringWriter
|
|
74
|
-
|
|
75
|
-
If you read the code of ActiveRecord serialization code in Ruby, you will observe this flow:
|
|
76
|
-
|
|
77
|
-
1. Get an array of ActiveRecord objects (`User.all` for example).
|
|
78
|
-
2. Build a new array of hashes where each hash is an `User` with the attributes we selected.
|
|
79
|
-
3. The JSON serializer, takes this array of hashes and loop them, and converts it to a JSON string.
|
|
80
|
-
|
|
81
|
-
This entire process is expensive in terms of Memory & CPU, and this where the combination of Panko and Oj::StringWriter really shines.
|
|
82
|
-
|
|
83
|
-
In Panko, the serialization process of the above is:
|
|
84
|
-
|
|
85
|
-
1. Get an array of ActiveRecord objects (`User.all` for example).
|
|
86
|
-
2. Create `Oj::StringWriter` and feed the values to it, via `push_value` / `push_object` / `push_object` and behind the scene, `Oj::StringWriter` will serialize the objects incrementally into a string.
|
|
87
|
-
3. Get from `Oj::StringWriter` the completed JSON string — which is a no-op, since `Oj::StringWriter` already built the string.
|
|
88
|
-
|
|
89
|
-
### Figuring out the metadata, ahead of time.
|
|
90
|
-
|
|
91
|
-
Another observation I noticed in the Ruby serializers is that they ask and do a lot in a serialization loop:
|
|
92
|
-
|
|
93
|
-
- Is this field a method? is it a property?
|
|
94
|
-
- Which fields and associations do I need for the serializer to consider the `only` and `except` options?
|
|
95
|
-
- What is the serializer of this has_one association?
|
|
96
|
-
|
|
97
|
-
Panko tries to ask the bare minimum in serialization by building `Serialization Descriptor` for each serialization and caching it.
|
|
98
|
-
|
|
99
|
-
The Serialization Descriptor will do the filtering of `only` and `except` and will check if a field is a method or not (therefore Panko doesn't have list of `attributes`).
|
|
100
|
-
|
|
101
|
-
### Type Casting
|
|
102
|
-
|
|
103
|
-
This is the final part, which helped yield most of the performance improvements.
|
|
104
|
-
In ActiveRecord, when we read the value of an attribute, it does type casting of the DB value to its real Ruby type.
|
|
105
|
-
|
|
106
|
-
For example, time strings are converted to Time objects, Strings are duplicated, and Integers are converted from their values to Number.
|
|
107
|
-
|
|
108
|
-
This type casting is really expensive, as it's responsible for most of the allocations in the serialization flow and most of them can be "relaxed".
|
|
109
|
-
|
|
110
|
-
If we think about it, we don't need to duplicate strings or convert time strings to time objects or even parse JSON strings for the JSON serialization process.
|
|
111
|
-
|
|
112
|
-
What Panko does is that if we have ActiveRecord type string, we won't duplicate it.
|
|
113
|
-
If we have an integer string value, we will convert it to an integer, and the same goes for other types.
|
|
114
|
-
|
|
115
|
-
All of these conversions are done in C, which of course yields a big performance improvement.
|
|
116
|
-
|
|
117
|
-
#### Time type casting
|
|
118
|
-
|
|
119
|
-
While you read Panko source code, you will encounter the time type casting and immediately you will have a "WTF?" moment.
|
|
120
|
-
|
|
121
|
-
The idea behind the time type casting code relies on the end result of JSON type casting — what we need in order to serialize Time to JSON? UTC ISO8601 time format representation.
|
|
122
|
-
|
|
123
|
-
The time type casting works as follows:
|
|
124
|
-
|
|
125
|
-
- If it's a string that ends with `Z`, and the strings matches the UTC ISO8601 regex, then we just return the string.
|
|
126
|
-
- If it's a string and it doesn't follow the rules above, we check if it's a timestamp in database format and convert it via regex + string concat to UTC ISO8601 - Yes, there is huge assumption here, that the database returns UTC timestamps — this will be configurable (before Panko official release).
|
|
127
|
-
- If it's none of the above, I will let ActiveRecord type casting do it's magic.
|