panko_serializer 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +102 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +22 -0
  6. data/.travis.yml +8 -0
  7. data/Gemfile +36 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +8 -0
  10. data/Rakefile +62 -0
  11. data/benchmarks/BENCHMARKS.md +48 -0
  12. data/benchmarks/allocs.rb +23 -0
  13. data/benchmarks/app.rb +13 -0
  14. data/benchmarks/benchmarking_support.rb +43 -0
  15. data/benchmarks/bm_active_model_serializers.rb +45 -0
  16. data/benchmarks/bm_controller.rb +81 -0
  17. data/benchmarks/bm_panko_json.rb +60 -0
  18. data/benchmarks/bm_panko_object.rb +69 -0
  19. data/benchmarks/profile.rb +88 -0
  20. data/benchmarks/sanity.rb +67 -0
  21. data/benchmarks/setup.rb +62 -0
  22. data/benchmarks/type_casts/bm_active_record.rb +57 -0
  23. data/benchmarks/type_casts/bm_panko.rb +67 -0
  24. data/benchmarks/type_casts/bm_pg.rb +35 -0
  25. data/benchmarks/type_casts/support.rb +16 -0
  26. data/ext/panko_serializer/attributes_iterator.c +62 -0
  27. data/ext/panko_serializer/attributes_iterator.h +17 -0
  28. data/ext/panko_serializer/extconf.rb +8 -0
  29. data/ext/panko_serializer/panko_serializer.c +189 -0
  30. data/ext/panko_serializer/panko_serializer.h +17 -0
  31. data/ext/panko_serializer/serialization_descriptor.c +166 -0
  32. data/ext/panko_serializer/serialization_descriptor.h +30 -0
  33. data/ext/panko_serializer/time_conversion.c +94 -0
  34. data/ext/panko_serializer/time_conversion.h +6 -0
  35. data/ext/panko_serializer/type_cast.c +271 -0
  36. data/ext/panko_serializer/type_cast.h +74 -0
  37. data/lib/panko/array_serializer.rb +40 -0
  38. data/lib/panko/cache.rb +35 -0
  39. data/lib/panko/serialization_descriptor.rb +119 -0
  40. data/lib/panko/serializer.rb +57 -0
  41. data/lib/panko/version.rb +4 -0
  42. data/lib/panko.rb +8 -0
  43. data/panko_serializer.gemspec +31 -0
  44. 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]
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+ require "mkmf"
3
+
4
+ $CPPFLAGS += " -Wall"
5
+
6
+ extension_name = "panko_serializer"
7
+ dir_config(extension_name)
8
+ create_makefile("panko_serializer/panko_serializer")
@@ -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);