panko_serializer 0.1.1

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.
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);