panko_serializer 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.clang-format +102 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +22 -0
- data/.travis.yml +8 -0
- data/Gemfile +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +8 -0
- data/Rakefile +62 -0
- data/benchmarks/BENCHMARKS.md +48 -0
- data/benchmarks/allocs.rb +23 -0
- data/benchmarks/app.rb +13 -0
- data/benchmarks/benchmarking_support.rb +43 -0
- data/benchmarks/bm_active_model_serializers.rb +45 -0
- data/benchmarks/bm_controller.rb +81 -0
- data/benchmarks/bm_panko_json.rb +60 -0
- data/benchmarks/bm_panko_object.rb +69 -0
- data/benchmarks/profile.rb +88 -0
- data/benchmarks/sanity.rb +67 -0
- data/benchmarks/setup.rb +62 -0
- data/benchmarks/type_casts/bm_active_record.rb +57 -0
- data/benchmarks/type_casts/bm_panko.rb +67 -0
- data/benchmarks/type_casts/bm_pg.rb +35 -0
- data/benchmarks/type_casts/support.rb +16 -0
- data/ext/panko_serializer/attributes_iterator.c +62 -0
- data/ext/panko_serializer/attributes_iterator.h +17 -0
- data/ext/panko_serializer/extconf.rb +8 -0
- data/ext/panko_serializer/panko_serializer.c +189 -0
- data/ext/panko_serializer/panko_serializer.h +17 -0
- data/ext/panko_serializer/serialization_descriptor.c +166 -0
- data/ext/panko_serializer/serialization_descriptor.h +30 -0
- data/ext/panko_serializer/time_conversion.c +94 -0
- data/ext/panko_serializer/time_conversion.h +6 -0
- data/ext/panko_serializer/type_cast.c +271 -0
- data/ext/panko_serializer/type_cast.h +74 -0
- data/lib/panko/array_serializer.rb +40 -0
- data/lib/panko/cache.rb +35 -0
- data/lib/panko/serialization_descriptor.rb +119 -0
- data/lib/panko/serializer.rb +57 -0
- data/lib/panko/version.rb +4 -0
- data/lib/panko.rb +8 -0
- data/panko_serializer.gemspec +31 -0
- metadata +171 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./benchmarking_support"
|
3
|
+
require_relative "./app"
|
4
|
+
require_relative "./setup"
|
5
|
+
|
6
|
+
require "memory_profiler"
|
7
|
+
|
8
|
+
class NullLogger < Logger
|
9
|
+
def initialize(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(*args, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class BenchmarkApp < Rails::Application
|
17
|
+
routes.append do
|
18
|
+
get "/simple" => "main#simple"
|
19
|
+
get "/text" => "main#text"
|
20
|
+
|
21
|
+
get "/serialize_to_string" => "main#serialize_to_string"
|
22
|
+
get "/serialize_to_stream" => "streaming#serialize_to_stream"
|
23
|
+
end
|
24
|
+
|
25
|
+
config.secret_token = "s"*30
|
26
|
+
config.secret_key_base = "foo"
|
27
|
+
config.consider_all_requests_local = false
|
28
|
+
|
29
|
+
# simulate production
|
30
|
+
config.cache_classes = true
|
31
|
+
config.eager_load = true
|
32
|
+
config.action_controller.perform_caching = true
|
33
|
+
|
34
|
+
# otherwise deadlock occured
|
35
|
+
config.middleware.delete "Rack::Lock"
|
36
|
+
|
37
|
+
# to disable log files
|
38
|
+
config.logger = NullLogger.new
|
39
|
+
config.active_support.deprecation = :log
|
40
|
+
end
|
41
|
+
|
42
|
+
BenchmarkApp.initialize!
|
43
|
+
|
44
|
+
class AuthorFastSerializer < Panko::Serializer
|
45
|
+
attributes :id, :name
|
46
|
+
end
|
47
|
+
|
48
|
+
class PostWithHasOneFastSerializer < Panko::Serializer
|
49
|
+
attributes :id, :body, :title, :author_id
|
50
|
+
|
51
|
+
has_one :author, serializer: AuthorFastSerializer
|
52
|
+
end
|
53
|
+
|
54
|
+
class StreamingController < ActionController::Base
|
55
|
+
include ActionController::Live
|
56
|
+
|
57
|
+
def serialize_to_stream
|
58
|
+
headers["Content-Type".freeze] = "application/json".freeze
|
59
|
+
|
60
|
+
data = Benchmark.data[:all]
|
61
|
+
serializer = Panko::ArraySerializer.new([], each_serializer: PostWithHasOneFastSerializer)
|
62
|
+
writer = Oj::StreamWriter.new(response.stream, mode: :rails)
|
63
|
+
|
64
|
+
serializer.serialize_to_writer(data, writer)
|
65
|
+
|
66
|
+
response.stream.close
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
class RouteNotFoundError < StandardError;end
|
72
|
+
|
73
|
+
|
74
|
+
def request(method, path)
|
75
|
+
response = Rack::MockRequest.new(BenchmarkApp).send(method, path)
|
76
|
+
if response.status.in?([404, 500])
|
77
|
+
raise RouteNotFoundError.new, 'not found #{method.to_s.upcase} #{path}'
|
78
|
+
end
|
79
|
+
response
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def memory(&block)
|
84
|
+
mem = MemoryProfiler.report(&block)
|
85
|
+
mem.pretty_print
|
86
|
+
end
|
87
|
+
|
88
|
+
memory { request(:get, "/serialize_to_stream") }
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./benchmarking_support"
|
3
|
+
require_relative "./app"
|
4
|
+
require_relative "./setup"
|
5
|
+
|
6
|
+
class AuthorFastSerializer < Panko::Serializer
|
7
|
+
attributes :id, :name
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class PostFastSerializer < Panko::Serializer
|
12
|
+
attributes :id, :body, :title, :author_id, :created_at
|
13
|
+
end
|
14
|
+
|
15
|
+
class PostFastWithMethodCallSerializer < Panko::Serializer
|
16
|
+
attributes :id, :body, :title, :author_id, :created_at, :method_call
|
17
|
+
|
18
|
+
def method_call
|
19
|
+
object.id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class PostWithHasOneFastSerializer < Panko::Serializer
|
24
|
+
attributes :id, :body, :title, :author_id, :created_at
|
25
|
+
|
26
|
+
has_one :author, serializer: AuthorFastSerializer
|
27
|
+
end
|
28
|
+
|
29
|
+
class AuthorWithHasManyFastSerializer < Panko::Serializer
|
30
|
+
attributes :id, :name
|
31
|
+
|
32
|
+
has_many :posts, serializer: PostFastSerializer
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def benchmark(prefix, serializer, options = {})
|
37
|
+
data = Benchmark.data
|
38
|
+
posts = data[:all]
|
39
|
+
posts_50 = data[:small]
|
40
|
+
|
41
|
+
merged_options = options.merge(each_serializer: serializer)
|
42
|
+
|
43
|
+
Benchmark.ams("Panko_#{prefix}_Posts_#{posts.count}") do
|
44
|
+
Panko::ArraySerializer.new(posts, merged_options).to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
data = Benchmark.data
|
48
|
+
posts = data[:all]
|
49
|
+
posts_50 = data[:small]
|
50
|
+
|
51
|
+
Benchmark.ams("Panko_#{prefix}_Posts_50") do
|
52
|
+
Panko::ArraySerializer.new(posts_50, merged_options).to_json
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#puts "Waiting .. #{Process.pid}"
|
57
|
+
#gets.chomp
|
58
|
+
|
59
|
+
#puts "Starting!"
|
60
|
+
|
61
|
+
benchmark 'SimpleWithMethodCall', PostFastWithMethodCallSerializer
|
62
|
+
benchmark "HasOne", PostWithHasOneFastSerializer
|
63
|
+
benchmark "Simple", PostFastSerializer
|
64
|
+
|
65
|
+
# benchmark 'SimpleWithMethodCall', PostFastWithMethodCallSerializer
|
66
|
+
# benchmark 'Except', PostWithHasOneFastSerializer, except: [:title]
|
67
|
+
# benchmark 'Include', PostWithHasOneFastSerializer, include: [:id, :body, :author_id, :author]
|
data/benchmarks/setup.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
###########################################
|
3
|
+
# Setup active record models
|
4
|
+
##########################################
|
5
|
+
require "active_record"
|
6
|
+
require "sqlite3"
|
7
|
+
|
8
|
+
|
9
|
+
# Change the following to reflect your database settings
|
10
|
+
ActiveRecord::Base.establish_connection(
|
11
|
+
adapter: "sqlite3",
|
12
|
+
database: ":memory:"
|
13
|
+
)
|
14
|
+
|
15
|
+
# Don't show migration output when constructing fake db
|
16
|
+
ActiveRecord::Migration.verbose = false
|
17
|
+
|
18
|
+
ActiveRecord::Schema.define do
|
19
|
+
create_table :authors, force: true do |t|
|
20
|
+
t.string :name
|
21
|
+
t.timestamps(null: false)
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table :posts, force: true do |t|
|
25
|
+
t.text :body
|
26
|
+
t.string :title
|
27
|
+
t.references :author
|
28
|
+
t.timestamps(null: false)
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :profiles, force: true do |t|
|
32
|
+
t.text :project_url
|
33
|
+
t.text :bio
|
34
|
+
t.date :birthday
|
35
|
+
t.references :author
|
36
|
+
t.timestamps(null: false)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Author < ActiveRecord::Base
|
41
|
+
has_one :profile
|
42
|
+
has_many :posts
|
43
|
+
end
|
44
|
+
|
45
|
+
class Post < ActiveRecord::Base
|
46
|
+
belongs_to :author
|
47
|
+
end
|
48
|
+
|
49
|
+
class Profile < ActiveRecord::Base
|
50
|
+
belongs_to :author
|
51
|
+
end
|
52
|
+
|
53
|
+
# Build out the data to serialize
|
54
|
+
Post.transaction do
|
55
|
+
ENV.fetch("ITEMS_COUNT", "2300").to_i.times do
|
56
|
+
Post.create(
|
57
|
+
body: "something about how password restrictions are evil, and less secure, and with the math to prove it.",
|
58
|
+
title: "Your bank is does not know how to do security",
|
59
|
+
author: Author.create(name: "Preston Sego")
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./support"
|
3
|
+
|
4
|
+
def ar_type_convert(type_klass, from, to)
|
5
|
+
converter = type_klass.new
|
6
|
+
assert type_klass.name, converter.type_cast_from_database(from), to
|
7
|
+
|
8
|
+
Benchmark.ams("#{type_klass.name}_TypeCast") do
|
9
|
+
converter.type_cast_from_database(from)
|
10
|
+
end
|
11
|
+
|
12
|
+
Benchmark.ams("#{type_klass.name}_NoTypeCast") do
|
13
|
+
converter.type_cast_from_database(to)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def utc_ar_time
|
18
|
+
date = DateTime.new(2017, 3, 4, 12, 45, 23)
|
19
|
+
tz = ActiveSupport::TimeZone.new("UTC")
|
20
|
+
from = date.in_time_zone(tz).iso8601
|
21
|
+
|
22
|
+
type = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.new
|
23
|
+
converter = ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter.new(type)
|
24
|
+
|
25
|
+
Benchmark.ams("#{tz}_#{type.class.name}_TypeCast") do
|
26
|
+
converter.type_cast_from_database(from).iso8601
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
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.ams("ActiveRecord_Time_TypeCast_WithISO8601") do
|
39
|
+
converter.type_cast_from_database(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::Float, "Infinity", 0.0
|
48
|
+
ar_type_convert ActiveRecord::Type::Boolean, "true", true
|
49
|
+
ar_type_convert ActiveRecord::Type::Boolean, "t", true
|
50
|
+
|
51
|
+
ar_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer, "1", 1
|
52
|
+
ar_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Float, "1.23", 1.23
|
53
|
+
ar_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Float, "Infinity", ::Float::INFINITY
|
54
|
+
ar_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json, '{"a":1}', {a:1}
|
55
|
+
|
56
|
+
db_ar_time
|
57
|
+
utc_ar_time
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./support"
|
3
|
+
|
4
|
+
def panko_type_convert(type_klass, from, to)
|
5
|
+
converter = type_klass.new
|
6
|
+
assert "#{type_klass.name}", Panko::_type_cast(converter, from), to
|
7
|
+
|
8
|
+
Benchmark.ams("#{type_klass.name}_TypeCast") do
|
9
|
+
Panko::_type_cast(converter, from)
|
10
|
+
end
|
11
|
+
|
12
|
+
Benchmark.ams("#{type_klass.name}_NoTypeCast") do
|
13
|
+
Panko::_type_cast(converter, to)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
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.ams("#{tz}_#{type.class.name}_TypeCast") do
|
29
|
+
Panko::_type_cast(converter, from)
|
30
|
+
end
|
31
|
+
|
32
|
+
Benchmark.ams("#{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.ams("Panko_Time_TypeCast") do
|
44
|
+
Panko::_type_cast(converter, from)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
db_panko_time
|
49
|
+
utc_panko_time
|
50
|
+
|
51
|
+
exit
|
52
|
+
|
53
|
+
panko_type_convert ActiveRecord::Type::String, 1, "1"
|
54
|
+
panko_type_convert ActiveRecord::Type::Text, 1, "1"
|
55
|
+
panko_type_convert ActiveRecord::Type::Integer, "1", 1
|
56
|
+
panko_type_convert ActiveRecord::Type::Float, "1.23", 1.23
|
57
|
+
panko_type_convert ActiveRecord::Type::Float, "Infinity", ::Float::INFINITY
|
58
|
+
panko_type_convert ActiveRecord::Type::Boolean, "true", true
|
59
|
+
panko_type_convert ActiveRecord::Type::Boolean, "t", true
|
60
|
+
|
61
|
+
panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer, "1", 1
|
62
|
+
panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Float, "1.23", 1.23
|
63
|
+
panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Float, "Infinity", ::Float::INFINITY
|
64
|
+
|
65
|
+
panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json, '{"a":1}', {a:1}
|
66
|
+
db_panko_time
|
67
|
+
utc_panko_time
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./support"
|
3
|
+
|
4
|
+
def pg_type_convert(type_klass, from, to)
|
5
|
+
converter = type_klass.new
|
6
|
+
assert type_klass.name, converter.decode(from), to
|
7
|
+
|
8
|
+
Benchmark.ams("#{type_klass.name}_TypeCast") do
|
9
|
+
converter.decode(from)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def pg_time
|
14
|
+
decoder = PG::TextDecoder::TimestampWithoutTimeZone.new
|
15
|
+
|
16
|
+
from = "2017-07-10 09:26:40.937392"
|
17
|
+
|
18
|
+
Benchmark.ams("#{decoder.class.name}_TypeCast") do
|
19
|
+
decoder.decode(from)
|
20
|
+
end
|
21
|
+
|
22
|
+
Benchmark.ams("#{decoder.class.name}_TypeCast_InTimeZone") do
|
23
|
+
decoder.decode(from).in_time_zone
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
pg_type_convert PG::TextDecoder::Integer, "1", 1
|
31
|
+
pg_type_convert PG::TextDecoder::Float, "1.23", 1.23
|
32
|
+
pg_type_convert PG::TextDecoder::Float, "Infinity", ::Float::INFINITY
|
33
|
+
pg_type_convert PG::TextDecoder::Float, "-Infinity", ::Float::INFINITY
|
34
|
+
pg_type_convert PG::TextDecoder::Float, "NaN", ::Float::NaN
|
35
|
+
pg_time
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "active_record"
|
3
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
4
|
+
require "active_model"
|
5
|
+
require "active_support/all"
|
6
|
+
require "pg"
|
7
|
+
|
8
|
+
|
9
|
+
require_relative "../benchmarking_support"
|
10
|
+
require_relative "../../lib/panko/panko"
|
11
|
+
|
12
|
+
def assert(type_name, from, to)
|
13
|
+
raise "#{type_name} - #{from.class} is not equals to #{to.class}" unless from.to_json == to.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
Time.zone = "UTC"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#include "attributes_iterator.h"
|
2
|
+
|
3
|
+
static ID attributes_id = 0;
|
4
|
+
static ID types_id = 0;
|
5
|
+
static ID values_id = 0;
|
6
|
+
|
7
|
+
VALUE read_attributes(VALUE obj) {
|
8
|
+
return rb_ivar_get(obj, attributes_id);
|
9
|
+
}
|
10
|
+
|
11
|
+
VALUE panko_read_lazy_attributes_hash(VALUE object) {
|
12
|
+
VALUE attributes_set = read_attributes(object);
|
13
|
+
if (attributes_set == Qnil) {
|
14
|
+
return Qnil;
|
15
|
+
}
|
16
|
+
|
17
|
+
VALUE attributes_hash = read_attributes(attributes_set);
|
18
|
+
if (attributes_hash == Qnil) {
|
19
|
+
return Qnil;
|
20
|
+
}
|
21
|
+
|
22
|
+
return attributes_hash;
|
23
|
+
}
|
24
|
+
|
25
|
+
void panko_read_types_and_value(VALUE attributes_hash,
|
26
|
+
VALUE* types,
|
27
|
+
VALUE* values) {
|
28
|
+
*types = rb_ivar_get(attributes_hash, types_id);
|
29
|
+
*values = rb_ivar_get(attributes_hash, values_id);
|
30
|
+
}
|
31
|
+
|
32
|
+
VALUE panko_each_attribute(VALUE obj,
|
33
|
+
SerializationDescriptor descriptor,
|
34
|
+
VALUE attributes,
|
35
|
+
EachAttributeFunc func,
|
36
|
+
VALUE context) {
|
37
|
+
VALUE attributes_hash = panko_read_lazy_attributes_hash(obj);
|
38
|
+
if(attributes_hash == Qnil) {
|
39
|
+
return Qnil;
|
40
|
+
}
|
41
|
+
|
42
|
+
VALUE types, values;
|
43
|
+
panko_read_types_and_value(attributes_hash, &types, &values);
|
44
|
+
|
45
|
+
int i;
|
46
|
+
for (i = 0; i < RARRAY_LEN(attributes); i++) {
|
47
|
+
VALUE member = rb_sym2str(RARRAY_AREF(attributes, i));
|
48
|
+
|
49
|
+
VALUE value = rb_hash_aref(values, member);
|
50
|
+
VALUE type_metadata = rb_hash_aref(types, member);
|
51
|
+
|
52
|
+
func(obj, member, value, type_metadata, context);
|
53
|
+
}
|
54
|
+
|
55
|
+
return Qnil;
|
56
|
+
}
|
57
|
+
|
58
|
+
void panko_init_attributes_iterator(VALUE mPanko) {
|
59
|
+
attributes_id = rb_intern("@attributes");
|
60
|
+
values_id = rb_intern("@values");
|
61
|
+
types_id = rb_intern("@types");
|
62
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#include "serialization_descriptor.h"
|
4
|
+
|
5
|
+
typedef void (*EachAttributeFunc)(VALUE object,
|
6
|
+
VALUE name,
|
7
|
+
VALUE value,
|
8
|
+
VALUE type_metadata,
|
9
|
+
VALUE context);
|
10
|
+
|
11
|
+
extern VALUE panko_each_attribute(VALUE object,
|
12
|
+
SerializationDescriptor descriptor,
|
13
|
+
VALUE attributes,
|
14
|
+
EachAttributeFunc func,
|
15
|
+
VALUE context);
|
16
|
+
|
17
|
+
void panko_init_attributes_iterator(VALUE mPanko);
|
@@ -0,0 +1,189 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#include "panko_serializer.h"
|
4
|
+
|
5
|
+
static ID push_value_id = 0;
|
6
|
+
static ID push_array_id = 0;
|
7
|
+
static ID push_object_id = 0;
|
8
|
+
static ID pop_id = 0;
|
9
|
+
|
10
|
+
static ID to_a_id = 0;
|
11
|
+
|
12
|
+
void write_value(VALUE str_writer,
|
13
|
+
VALUE key,
|
14
|
+
VALUE value,
|
15
|
+
VALUE type_metadata) {
|
16
|
+
if (type_metadata != Qnil) {
|
17
|
+
value = type_cast(type_metadata, value);
|
18
|
+
}
|
19
|
+
|
20
|
+
rb_funcall(str_writer, push_value_id, 2, value, key);
|
21
|
+
}
|
22
|
+
|
23
|
+
void serialize_method_fields(VALUE subject,
|
24
|
+
VALUE str_writer,
|
25
|
+
SerializationDescriptor descriptor,
|
26
|
+
VALUE context) {
|
27
|
+
VALUE method_fields = descriptor->method_fields;
|
28
|
+
if (RARRAY_LEN(method_fields) == 0) {
|
29
|
+
return;
|
30
|
+
}
|
31
|
+
|
32
|
+
VALUE serializer = sd_build_serializer(descriptor);
|
33
|
+
sd_apply_serializer_config(serializer, subject, context);
|
34
|
+
|
35
|
+
long i;
|
36
|
+
for (i = 0; i < RARRAY_LEN(method_fields); i++) {
|
37
|
+
VALUE attribute_name = RARRAY_AREF(method_fields, i);
|
38
|
+
VALUE result = rb_funcall(serializer, rb_sym2id(attribute_name), 0);
|
39
|
+
|
40
|
+
write_value(str_writer, rb_sym2str(attribute_name), result, Qnil);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
void panko_attributes_iter(VALUE object,
|
45
|
+
VALUE name,
|
46
|
+
VALUE value,
|
47
|
+
VALUE type_metadata,
|
48
|
+
VALUE context) {
|
49
|
+
write_value(context, name, value, type_metadata);
|
50
|
+
}
|
51
|
+
|
52
|
+
void serialize_fields(VALUE subject,
|
53
|
+
VALUE str_writer,
|
54
|
+
SerializationDescriptor descriptor,
|
55
|
+
VALUE context) {
|
56
|
+
panko_each_attribute(subject, descriptor, descriptor->fields,
|
57
|
+
panko_attributes_iter, str_writer);
|
58
|
+
|
59
|
+
serialize_method_fields(subject, str_writer, descriptor, context);
|
60
|
+
}
|
61
|
+
|
62
|
+
void serialize_has_one_associatoins(VALUE subject,
|
63
|
+
VALUE str_writer,
|
64
|
+
VALUE context,
|
65
|
+
SerializationDescriptor descriptor,
|
66
|
+
VALUE associations) {
|
67
|
+
long i;
|
68
|
+
for (i = 0; i < RARRAY_LEN(associations); i++) {
|
69
|
+
VALUE association = RARRAY_AREF(associations, i);
|
70
|
+
|
71
|
+
VALUE name = RARRAY_AREF(association, 0);
|
72
|
+
VALUE association_descriptor = RARRAY_AREF(association, 1);
|
73
|
+
VALUE value = rb_funcall(subject, rb_sym2id(name), 0);
|
74
|
+
|
75
|
+
if (value == Qnil) {
|
76
|
+
write_value(str_writer, rb_sym2str(name), value, Qnil);
|
77
|
+
} else {
|
78
|
+
serialize_subject(rb_sym2str(name), value, str_writer,
|
79
|
+
sd_read(association_descriptor), context);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
void serialize_has_many_associatoins(VALUE subject,
|
85
|
+
VALUE str_writer,
|
86
|
+
VALUE context,
|
87
|
+
SerializationDescriptor descriptor,
|
88
|
+
VALUE associations) {
|
89
|
+
long i;
|
90
|
+
for (i = 0; i < RARRAY_LEN(associations); i++) {
|
91
|
+
VALUE association = RARRAY_AREF(associations, i);
|
92
|
+
|
93
|
+
VALUE name = RARRAY_AREF(association, 0);
|
94
|
+
VALUE association_descriptor = RARRAY_AREF(association, 1);
|
95
|
+
VALUE value = rb_funcall(subject, rb_sym2id(name), 0);
|
96
|
+
|
97
|
+
if (value == Qnil) {
|
98
|
+
write_value(str_writer, rb_sym2str(name), value, Qnil);
|
99
|
+
} else {
|
100
|
+
serialize_subjects(rb_sym2str(name), value, str_writer,
|
101
|
+
sd_read(association_descriptor), context);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
VALUE serialize_subject(VALUE key,
|
107
|
+
VALUE subject,
|
108
|
+
VALUE str_writer,
|
109
|
+
SerializationDescriptor descriptor,
|
110
|
+
VALUE context) {
|
111
|
+
rb_funcall(str_writer, push_object_id, 1, key);
|
112
|
+
|
113
|
+
serialize_fields(subject, str_writer, descriptor, context);
|
114
|
+
|
115
|
+
if (RARRAY_LEN(descriptor->has_one_associations) >= 0) {
|
116
|
+
serialize_has_one_associatoins(subject, str_writer, context, descriptor,
|
117
|
+
descriptor->has_one_associations);
|
118
|
+
}
|
119
|
+
|
120
|
+
if (RARRAY_LEN(descriptor->has_many_associations) >= 0) {
|
121
|
+
serialize_has_many_associatoins(subject, str_writer, context, descriptor,
|
122
|
+
descriptor->has_many_associations);
|
123
|
+
}
|
124
|
+
|
125
|
+
rb_funcall(str_writer, pop_id, 0);
|
126
|
+
|
127
|
+
return Qnil;
|
128
|
+
}
|
129
|
+
|
130
|
+
VALUE serialize_subjects(VALUE key,
|
131
|
+
VALUE subjects,
|
132
|
+
VALUE str_writer,
|
133
|
+
SerializationDescriptor descriptor,
|
134
|
+
VALUE context) {
|
135
|
+
rb_funcall(str_writer, push_array_id, 1, key);
|
136
|
+
|
137
|
+
if (!RB_TYPE_P(subjects, T_ARRAY)) {
|
138
|
+
subjects = rb_funcall(subjects, to_a_id, 0);
|
139
|
+
}
|
140
|
+
|
141
|
+
long i;
|
142
|
+
for (i = 0; i < RARRAY_LEN(subjects); i++) {
|
143
|
+
VALUE subject = RARRAY_AREF(subjects, i);
|
144
|
+
serialize_subject(Qnil, subject, str_writer, descriptor, context);
|
145
|
+
}
|
146
|
+
|
147
|
+
rb_funcall(str_writer, pop_id, 0);
|
148
|
+
|
149
|
+
return Qnil;
|
150
|
+
}
|
151
|
+
|
152
|
+
VALUE serialize_subject_api(VALUE klass,
|
153
|
+
VALUE subject,
|
154
|
+
VALUE str_writer,
|
155
|
+
VALUE descriptor,
|
156
|
+
VALUE context) {
|
157
|
+
return serialize_subject(Qnil, subject, str_writer, sd_read(descriptor),
|
158
|
+
context);
|
159
|
+
}
|
160
|
+
|
161
|
+
VALUE serialize_subjects_api(VALUE klass,
|
162
|
+
VALUE subjects,
|
163
|
+
VALUE str_writer,
|
164
|
+
VALUE descriptor,
|
165
|
+
VALUE context) {
|
166
|
+
serialize_subjects(Qnil, subjects, str_writer, sd_read(descriptor), context);
|
167
|
+
|
168
|
+
return Qnil;
|
169
|
+
}
|
170
|
+
|
171
|
+
void Init_panko_serializer() {
|
172
|
+
CONST_ID(push_value_id, "push_value");
|
173
|
+
CONST_ID(push_array_id, "push_array");
|
174
|
+
CONST_ID(push_object_id, "push_object");
|
175
|
+
CONST_ID(pop_id, "pop");
|
176
|
+
CONST_ID(to_a_id, "to_a");
|
177
|
+
|
178
|
+
VALUE mPanko = rb_define_module("Panko");
|
179
|
+
|
180
|
+
rb_define_singleton_method(mPanko, "serialize_subject", serialize_subject_api,
|
181
|
+
4);
|
182
|
+
|
183
|
+
rb_define_singleton_method(mPanko, "serialize_subjects",
|
184
|
+
serialize_subjects_api, 4);
|
185
|
+
|
186
|
+
panko_init_serialization_descriptor(mPanko);
|
187
|
+
panko_init_attributes_iterator(mPanko);
|
188
|
+
panko_init_type_cast(mPanko);
|
189
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#include "serialization_descriptor.h"
|
4
|
+
#include "attributes_iterator.h"
|
5
|
+
#include "type_cast.h"
|
6
|
+
|
7
|
+
VALUE serialize_subject(VALUE key,
|
8
|
+
VALUE subject,
|
9
|
+
VALUE str_writer,
|
10
|
+
SerializationDescriptor descriptor,
|
11
|
+
VALUE context);
|
12
|
+
|
13
|
+
VALUE serialize_subjects(VALUE key,
|
14
|
+
VALUE subjects,
|
15
|
+
VALUE str_writer,
|
16
|
+
SerializationDescriptor descriptor,
|
17
|
+
VALUE context);
|