panko_serializer 0.3.4 → 0.3.5

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +16 -2
  3. data/.travis.yml +1 -2
  4. data/Gemfile +5 -3
  5. data/Rakefile +31 -5
  6. data/benchmarks/bm_ams_0_10.rb +56 -0
  7. data/benchmarks/bm_fastjsonapi.rb +56 -0
  8. data/benchmarks/bm_panko_json.rb +4 -4
  9. data/benchmarks/bm_panko_object.rb +4 -22
  10. data/benchmarks/bm_plain_object.rb +102 -0
  11. data/benchmarks/type_casts/bm_panko.rb +0 -3
  12. data/docs/README.md +1 -1
  13. data/ext/panko_serializer/{attributes_iterator.c → attributes_writer/active_record.c} +8 -12
  14. data/ext/panko_serializer/attributes_writer/active_record.h +15 -0
  15. data/ext/panko_serializer/attributes_writer/common.c +10 -0
  16. data/ext/panko_serializer/attributes_writer/common.h +8 -0
  17. data/ext/panko_serializer/attributes_writer/plain.c +17 -0
  18. data/ext/panko_serializer/attributes_writer/plain.h +9 -0
  19. data/ext/panko_serializer/{time_conversion.c → attributes_writer/type_cast/time_conversion.c} +0 -0
  20. data/ext/panko_serializer/{time_conversion.h → attributes_writer/type_cast/time_conversion.h} +2 -0
  21. data/ext/panko_serializer/{type_cast.c → attributes_writer/type_cast/type_cast.c} +2 -1
  22. data/ext/panko_serializer/{type_cast.h → attributes_writer/type_cast/type_cast.h} +2 -5
  23. data/ext/panko_serializer/attributes_writer/writer.c +50 -0
  24. data/ext/panko_serializer/attributes_writer/writer.h +31 -0
  25. data/ext/panko_serializer/extconf.rb +20 -0
  26. data/ext/panko_serializer/panko_serializer.c +7 -5
  27. data/ext/panko_serializer/panko_serializer.h +4 -5
  28. data/ext/panko_serializer/{association.c → serialization_descriptor/association.c} +0 -0
  29. data/ext/panko_serializer/{association.h → serialization_descriptor/association.h} +0 -0
  30. data/ext/panko_serializer/{attribute.c → serialization_descriptor/attribute.c} +0 -0
  31. data/ext/panko_serializer/{attribute.h → serialization_descriptor/attribute.h} +0 -0
  32. data/ext/panko_serializer/{serialization_descriptor.c → serialization_descriptor/serialization_descriptor.c} +12 -0
  33. data/ext/panko_serializer/{serialization_descriptor.h → serialization_descriptor/serialization_descriptor.h} +7 -4
  34. data/lib/panko/serialization_descriptor.rb +4 -0
  35. data/lib/panko/version.rb +1 -1
  36. metadata +24 -17
  37. data/benchmarks/bm_active_model_serializers.rb +0 -45
  38. data/benchmarks/bm_controller.rb +0 -81
  39. data/ext/panko_serializer/attributes_iterator.h +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2b1c0dbc2c75d9b48d7b2057ef66f2150702e86db83110e552ba8b99dfb7662
4
- data.tar.gz: b980005c2670d69ba9093fd9aa4bee57f2f106eeedbccbd8739b64004591f62b
3
+ metadata.gz: 4504cdc0e66927014ebd2843a52e8cef9aa5d4443b2a2cc6c31342468ebdeb08
4
+ data.tar.gz: 83a6d226222bd9910addcaaf61f2c77ea131da5d0b33bcdf0ecda150d82df878
5
5
  SHA512:
6
- metadata.gz: d27d528c54db62acffb611c5eba616f6346e410a78c9fe52835ccb5a60c4253e890f58dbc4aa6316f496db45fd1da7b4b79b4751fbd3898588b91efe715fb83e
7
- data.tar.gz: ddf22bfc9d998fea61482a9e6ec71c460848401b7c922352f1c37395e717944c7cad77537133f90751a6cc9581e507cbb06d927af484f03bded9966a74e549e3
6
+ metadata.gz: 8bd4eab5358c889a895dc4ab48623eb5659572312879c5c64fccc5785e62f1cf2a9d576430cae4109937b8e61574a26273d3e9e3399dd92a03e7d60673593fce
7
+ data.tar.gz: f3c1994beb8627937db1827f5d5f07ba17d99809f40e752c8a3157bd38c66aa040d2db94722a14cd0643241405512be87ed6696ef5c3037283b3c930215e7709
data/.clang-format CHANGED
@@ -29,10 +29,13 @@ BraceWrapping:
29
29
  AfterObjCDeclaration: false
30
30
  AfterStruct: false
31
31
  AfterUnion: false
32
+ AfterExternBlock: false
32
33
  BeforeCatch: false
33
34
  BeforeElse: false
34
35
  IndentBraces: false
35
- SplitEmptyFunctionBody: true
36
+ SplitEmptyFunction: true
37
+ SplitEmptyRecord: true
38
+ SplitEmptyNamespace: true
36
39
  BreakBeforeBinaryOperators: None
37
40
  BreakBeforeBraces: Attach
38
41
  BreakBeforeInheritanceComma: false
@@ -52,8 +55,13 @@ DerivePointerAlignment: false
52
55
  DisableFormat: false
53
56
  ExperimentalAutoDetectBinPacking: false
54
57
  FixNamespaceComments: true
55
- ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
58
+ ForEachMacros:
59
+ - foreach
60
+ - Q_FOREACH
61
+ - BOOST_FOREACH
56
62
  IncludeCategories:
63
+ - Regex: '^<ext/.*\.h>'
64
+ Priority: 2
57
65
  - Regex: '^<.*\.h>'
58
66
  Priority: 1
59
67
  - Regex: '^<.*'
@@ -62,6 +70,7 @@ IncludeCategories:
62
70
  Priority: 3
63
71
  IncludeIsMainRegex: '([-_](test|unittest))?$'
64
72
  IndentCaseLabels: true
73
+ IndentPPDirectives: None
65
74
  IndentWidth: 2
66
75
  IndentWrappedFunctionNames: false
67
76
  JavaScriptQuotes: Leave
@@ -82,8 +91,13 @@ PenaltyBreakString: 1000
82
91
  PenaltyExcessCharacter: 1000000
83
92
  PenaltyReturnTypeOnItsOwnLine: 200
84
93
  PointerAlignment: Left
94
+ RawStringFormats:
95
+ - Delimiter: pb
96
+ Language: TextProto
97
+ BasedOnStyle: google
85
98
  ReflowComments: true
86
99
  SortIncludes: true
100
+ SortUsingDeclarations: true
87
101
  SpaceAfterCStyleCast: false
88
102
  SpaceAfterTemplateKeyword: true
89
103
  SpaceBeforeAssignmentOperators: true
data/.travis.yml CHANGED
@@ -2,7 +2,7 @@ sudo: false
2
2
  cache: bundler
3
3
  language: ruby
4
4
  rvm:
5
- - 2.4.2
5
+ - 2.5.0
6
6
 
7
7
  env:
8
8
  global:
@@ -23,5 +23,4 @@ after_success:
23
23
  env:
24
24
  matrix:
25
25
  - "RAILS_VERSION=4.2"
26
- - "RAILS_VERSION=5.0"
27
26
  - "RAILS_VERSION=5.1"
data/Gemfile CHANGED
@@ -3,7 +3,8 @@ source "https://rubygems.org"
3
3
 
4
4
  gemspec
5
5
 
6
- rails_version = "~> #{ENV.fetch("RAILS_VERSION", "4.2")}"
6
+ raw_rails_version = ENV.fetch("RAILS_VERSION", "4.2")
7
+ rails_version = "~> #{raw_rails_version}"
7
8
 
8
9
  gem "rails", rails_version
9
10
  gem "railties", rails_version
@@ -14,14 +15,15 @@ gem "activerecord", rails_version, group: :test
14
15
 
15
16
  group :benchmarks do
16
17
  gem "sqlite3"
17
- gem "pg"
18
+ gem "pg", "0.21"
18
19
 
19
20
  gem "memory_profiler"
20
21
  gem "ruby-prof"
21
22
  gem "ruby-prof-flamegraph"
22
23
 
23
24
  gem "benchmark-ips"
24
- gem "active_model_serializers", "0.9.7"
25
+ gem "active_model_serializers"
26
+ gem "fast_jsonapi" if raw_rails_version.start_with? "5"
25
27
 
26
28
  gem "terminal-table"
27
29
  end
data/Rakefile CHANGED
@@ -4,6 +4,7 @@ require "rspec/core/rake_task"
4
4
  require "json"
5
5
  require "terminal-table"
6
6
  require "rake/extensiontask"
7
+ require "pty"
7
8
 
8
9
  gem = Gem::Specification.load( File.dirname(__FILE__) + "/panko_serializer.gemspec" )
9
10
 
@@ -21,15 +22,40 @@ Rake::Task[:spec].prerequisites << :compile
21
22
 
22
23
  task default: :spec
23
24
 
25
+ def print_and_flush(str)
26
+ print str
27
+ $stdout.flush
28
+ end
29
+
30
+ def run_process(cmd)
31
+ puts "> Running #{cmd}"
32
+ lines = []
33
+ PTY.spawn(cmd) do |stdout, stdin, pid|
34
+ begin
35
+ stdout.each do |line|
36
+ print_and_flush '.'
37
+ lines << line
38
+ end
39
+ rescue Errno::EIO
40
+ puts "Errno:EIO error, but this probably just means " +
41
+ "that the process has finished giving output - #{cmd}"
42
+ end
43
+ end
44
+
45
+ lines
46
+ rescue PTY::ChildExited
47
+ puts "The child process exited! - #{cmd}"
48
+ []
49
+ end
24
50
 
25
- def run_benchmarks(files, items_count: 14_000)
51
+ def run_benchmarks(files, items_count: 7_000)
26
52
  headings = ["Benchmark", "ip/s", "allocs/retained"]
27
53
  files.each do |benchmark_file|
28
- output = `ITEMS_COUNT=#{items_count} RAILS_ENV=production ruby #{benchmark_file}`
29
54
 
30
- rows = output.each_line.map do |line|
31
- result = JSON.parse(line)
32
- result.values
55
+ lines = run_process "ITEMS_COUNT=#{items_count} RAILS_ENV=production ruby #{benchmark_file}"
56
+ rows = lines.map do |line|
57
+ row = JSON.parse(line)
58
+ row.values
33
59
  end
34
60
 
35
61
  puts "\n\n"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./benchmarking_support"
3
+ require_relative "./app"
4
+ require_relative "./setup"
5
+
6
+ # disable logging for benchmarks
7
+ ActiveModelSerializers.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new('/dev/null'))
8
+
9
+
10
+ class AmsAuthorFastSerializer < ActiveModel::Serializer
11
+ attributes :id, :name
12
+ end
13
+
14
+ class AmsPostFastSerializer < ActiveModel::Serializer
15
+ attributes :id, :body, :title, :author_id, :created_at
16
+ end
17
+
18
+ class AmsPostWithHasOneFastSerializer < ActiveModel::Serializer
19
+ attributes :id, :body, :title, :author_id
20
+
21
+ has_one :author, serializer: AmsAuthorFastSerializer
22
+ end
23
+
24
+ def benchmark_ams(prefix, serializer, options = {})
25
+ merged_options = options.merge(each_serializer: serializer)
26
+
27
+ data = Benchmark.data
28
+ posts = data[:all]
29
+ posts_50 = data[:small]
30
+
31
+
32
+ Benchmark.run("AMS_#{prefix}_Posts_#{posts.count}") do
33
+ ActiveModelSerializers::SerializableResource.new(posts, merged_options).to_json
34
+ end
35
+
36
+ data = Benchmark.data
37
+ posts = data[:all]
38
+ posts_50 = data[:small]
39
+
40
+ Benchmark.run("AMS_#{prefix}_Posts_50") do
41
+ ActiveModelSerializers::SerializableResource.new(posts_50, merged_options).to_json
42
+ end
43
+ end
44
+
45
+
46
+ benchmark_ams "Attributes_Simple", AmsPostFastSerializer
47
+ benchmark_ams "Attributes_HasOne", AmsPostWithHasOneFastSerializer
48
+ benchmark_ams "Attributes_Except", AmsPostWithHasOneFastSerializer, except: [:title]
49
+ benchmark_ams "Attributes_Only", AmsPostWithHasOneFastSerializer, only: [:id, :body, :author_id, :author]
50
+
51
+ ActiveModel::Serializer.config.adapter = :json_api
52
+
53
+ benchmark_ams "JsonAPI_Simple", AmsPostFastSerializer
54
+ benchmark_ams "JsonAPI_HasOne", AmsPostWithHasOneFastSerializer
55
+ benchmark_ams "JsonAPI_Except", AmsPostWithHasOneFastSerializer, except: [:title]
56
+ benchmark_ams "JsonAPI_Only", AmsPostWithHasOneFastSerializer, only: [:id, :body, :author_id, :author]
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ exit unless ENV.fetch("RAILS_VERSION", "4.2").start_with? "5"
3
+
4
+ require_relative "./benchmarking_support"
5
+ require_relative "./app"
6
+ require_relative "./setup"
7
+ require 'fast_jsonapi'
8
+
9
+ class AuthorSerializer
10
+ include FastJsonapi::ObjectSerializer
11
+ attributes :id, :name
12
+ end
13
+
14
+
15
+ class PostFastSerializer
16
+ include FastJsonapi::ObjectSerializer
17
+ attributes :id, :body, :title, :author_id, :created_at
18
+ end
19
+
20
+ class PostWithHasOneFastSerializer
21
+ include FastJsonapi::ObjectSerializer
22
+ attributes :id, :body, :title, :author_id
23
+
24
+ has_one :author
25
+ end
26
+
27
+ class AuthorWithHasManyFastSerializer
28
+ include FastJsonapi::ObjectSerializer
29
+ attributes :id, :name
30
+
31
+ has_many :posts, serializer: :post_fast
32
+ end
33
+
34
+
35
+ def benchmark(prefix, serializer, options = {})
36
+ data = Benchmark.data
37
+ posts = data[:all]
38
+ posts_50 = data[:small]
39
+
40
+ merged_options = options.merge(each_serializer: serializer)
41
+
42
+ Benchmark.run("FastJSONAPI_#{prefix}_Posts_#{posts.count}") do
43
+ serializer.new(posts, merged_options).serialized_json
44
+ end
45
+
46
+ data = Benchmark.data
47
+ posts = data[:all]
48
+ posts_50 = data[:small]
49
+
50
+ Benchmark.run("FastJSONAPI_#{prefix}_Posts_50") do
51
+ serializer.new(posts_50, merged_options).serialized_json
52
+ end
53
+ end
54
+
55
+ benchmark "Simple", PostFastSerializer
56
+ benchmark "HasOne", PostWithHasOneFastSerializer, include: [:author]
@@ -40,7 +40,7 @@ def benchmark(prefix, serializer, options = {})
40
40
 
41
41
  merged_options = options.merge(each_serializer: serializer)
42
42
 
43
- Benchmark.run("Panko_#{prefix}_Posts_#{posts.count}") do
43
+ Benchmark.run("Panko_ActiveRecord_#{prefix}_Posts_#{posts.count}") do
44
44
  Panko::ArraySerializer.new(posts, merged_options).to_json
45
45
  end
46
46
 
@@ -48,13 +48,13 @@ def benchmark(prefix, serializer, options = {})
48
48
  posts = data[:all]
49
49
  posts_50 = data[:small]
50
50
 
51
- Benchmark.run("Panko_#{prefix}_Posts_50") do
51
+ Benchmark.run("Panko_ActiveRecord_#{prefix}_Posts_50") do
52
52
  Panko::ArraySerializer.new(posts_50, merged_options).to_json
53
53
  end
54
54
  end
55
55
 
56
- benchmark "HasOne", PostWithHasOneFastSerializer
57
56
  benchmark "Simple", PostFastSerializer
57
+ benchmark "HasOne", PostWithHasOneFastSerializer
58
58
  benchmark "SimpleWithMethodCall", PostFastWithMethodCallSerializer
59
59
  benchmark "Except", PostWithHasOneFastSerializer, except: [:title]
60
- benchmark "Include", PostWithHasOneFastSerializer, include: [:id, :body, :author_id, :author]
60
+ benchmark "Only", PostWithHasOneFastSerializer, only: [:id, :body, :author_id, :author]
@@ -32,7 +32,7 @@ def benchmark(prefix, serializer, options = {})
32
32
 
33
33
  merged_options = options.merge(each_serializer: serializer)
34
34
 
35
- Benchmark.run("Panko_#{prefix}_Posts_#{posts.count}") do
35
+ Benchmark.run("Panko_ActiveRecord_#{prefix}_Posts_#{posts.count}") do
36
36
  Panko::ArraySerializer.new(posts, merged_options).to_a
37
37
  end
38
38
 
@@ -40,30 +40,12 @@ def benchmark(prefix, serializer, options = {})
40
40
  posts = data[:all]
41
41
  posts_50 = data[:small]
42
42
 
43
- Benchmark.run("Panko_#{prefix}_Posts_50") do
43
+ Benchmark.run("Panko_ActiveRecord_#{prefix}_Posts_50") do
44
44
  Panko::ArraySerializer.new(posts_50, merged_options).to_a
45
45
  end
46
-
47
- posts_array_serializer = Panko::ArraySerializer.new([], merged_options)
48
-
49
- data = Benchmark.data
50
- posts = data[:all]
51
- posts_50 = data[:small]
52
-
53
- Benchmark.run("Panko_Reused_#{prefix}_Posts_#{posts.count}") do
54
- posts_array_serializer.serialize posts
55
- end
56
-
57
- data = Benchmark.data
58
- posts = data[:all]
59
- posts_50 = data[:small]
60
-
61
- Benchmark.run("Panko_Reused_#{prefix}_Posts_50") do
62
- posts_array_serializer.serialize posts_50
63
- end
64
46
  end
65
47
 
66
- benchmark "HasOne", PostWithHasOneFastSerializer
67
48
  benchmark "Simple", PostFastSerializer
49
+ benchmark "HasOne", PostWithHasOneFastSerializer
68
50
  benchmark "Except", PostWithHasOneFastSerializer, except: [:title]
69
- benchmark "Include", PostWithHasOneFastSerializer, include: [:id, :body, :author_id, :author]
51
+ benchmark "Only", PostWithHasOneFastSerializer, only: [:id, :body, :author_id, :author]
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./benchmarking_support"
3
+ require_relative "./app"
4
+
5
+ class AuthorFastSerializer < Panko::Serializer
6
+ attributes :id, :name
7
+ end
8
+
9
+
10
+ class PostFastSerializer < Panko::Serializer
11
+ attributes :id, :body, :title, :author_id, :created_at
12
+ end
13
+
14
+ class PostFastWithMethodCallSerializer < Panko::Serializer
15
+ attributes :id, :body, :title, :author_id, :method_call
16
+
17
+ def method_call
18
+ object.id * 2
19
+ end
20
+ end
21
+
22
+ class PostWithHasOneFastSerializer < Panko::Serializer
23
+ attributes :id, :body, :title, :author_id
24
+
25
+ has_one :author, serializer: AuthorFastSerializer
26
+ end
27
+
28
+ class AuthorWithHasManyFastSerializer < Panko::Serializer
29
+ attributes :id, :name
30
+
31
+ has_many :posts, serializer: PostFastSerializer
32
+ end
33
+
34
+ class Post
35
+ attr_accessor :id, :body, :title, :created_at, :author, :author_id
36
+
37
+ def self.create(attrs)
38
+ p = Post.new
39
+ attrs.each do |k,v|
40
+ p.send :"#{k}=", v
41
+ end
42
+ p
43
+ end
44
+
45
+ def author=(author)
46
+ @author = author
47
+ @author_id = author_id
48
+ end
49
+ end
50
+
51
+ class Author
52
+ attr_accessor :id, :name
53
+
54
+ def self.create(attrs)
55
+ a = Author.new
56
+ attrs.each do |k,v|
57
+ a.send :"#{k}=", v
58
+ end
59
+ a
60
+ end
61
+ end
62
+
63
+ def benchmark_data
64
+ posts = []
65
+ ENV.fetch("ITEMS_COUNT", "2300").to_i.times do |i|
66
+ posts << Post.create(
67
+ id: i,
68
+ body: "something about how password restrictions are evil, and less secure, and with the math to prove it.",
69
+ title: "Your bank is does not know how to do security",
70
+ author: Author.create(id: i, name: "Preston Sego")
71
+ )
72
+ end
73
+
74
+ { all: posts, small: posts.first(50) }
75
+ end
76
+
77
+
78
+ def benchmark(prefix, serializer, options = {})
79
+ data = benchmark_data
80
+ posts = data[:all]
81
+ posts_50 = data[:small]
82
+
83
+ merged_options = options.merge(each_serializer: serializer)
84
+
85
+ Benchmark.run("Panko_Plain_#{prefix}_Posts_#{posts.count}") do
86
+ Panko::ArraySerializer.new(posts, merged_options).to_json
87
+ end
88
+
89
+ data = benchmark_data
90
+ posts = data[:all]
91
+ posts_50 = data[:small]
92
+
93
+ Benchmark.run("Panko_Plain_#{prefix}_Posts_50") do
94
+ Panko::ArraySerializer.new(posts_50, merged_options).to_json
95
+ end
96
+ end
97
+
98
+ benchmark "Simple", PostFastSerializer
99
+ benchmark "HasOne", PostWithHasOneFastSerializer
100
+ benchmark "SimpleWithMethodCall", PostFastWithMethodCallSerializer
101
+ benchmark "Except", PostWithHasOneFastSerializer, except: [:title]
102
+ benchmark "Only", PostWithHasOneFastSerializer, only: [:id, :body, :author_id, :author]
@@ -46,9 +46,6 @@ def db_panko_time
46
46
  end
47
47
 
48
48
  panko_type_convert ActiveRecord::Type::String, 1, "1"
49
- panko_type_convert ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Json, '{"a":1}', {a:1}
50
- exit
51
-
52
49
  panko_type_convert ActiveRecord::Type::Text, 1, "1"
53
50
  panko_type_convert ActiveRecord::Type::Integer, "1", 1
54
51
  panko_type_convert ActiveRecord::Type::Float, "1.23", 1.23
data/docs/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  * [Panko](../README.md)
2
- * [Getting Stated](getting-started.md)
2
+ * [Getting Started](getting-started.md)
3
3
  * Reference
4
4
  * [Attributes](attributes.md)
5
5
  * [Associations](associations.md)
@@ -1,4 +1,4 @@
1
- #include "attributes_iterator.h"
1
+ #include "active_record.h"
2
2
 
3
3
  static ID attributes_id;
4
4
  static ID types_id;
@@ -139,10 +139,10 @@ VALUE read_attribute(struct attributes attributes_ctx, Attribute attribute) {
139
139
  return value;
140
140
  }
141
141
 
142
- VALUE panko_each_attribute(VALUE obj,
143
- VALUE attributes,
144
- EachAttributeFunc func,
145
- VALUE writer) {
142
+ VALUE active_record_attributes_writer(VALUE obj,
143
+ VALUE attributes,
144
+ EachAttributeFunc func,
145
+ VALUE writer) {
146
146
  long i;
147
147
  struct attributes attributes_ctx = init_context(obj);
148
148
  volatile VALUE record_class = CLASS_OF(obj);
@@ -152,19 +152,15 @@ VALUE panko_each_attribute(VALUE obj,
152
152
  Attribute attribute = attribute_read(raw_attribute);
153
153
  attribute_try_invalidate(attribute, record_class);
154
154
 
155
- volatile VALUE name_str = attribute->name_str;
156
- if (attribute->alias_name != Qnil) {
157
- name_str = attribute->alias_name;
158
- }
159
-
160
155
  volatile VALUE value = read_attribute(attributes_ctx, attribute);
161
- func(writer, name_str, value);
156
+
157
+ func(writer, attr_name_for_serialization(attribute), value);
162
158
  }
163
159
 
164
160
  return Qnil;
165
161
  }
166
162
 
167
- void panko_init_attributes_iterator(VALUE mPanko) {
163
+ void init_active_record_attributes_writer(VALUE mPanko) {
168
164
  attributes_id = rb_intern("@attributes");
169
165
  delegate_hash_id = rb_intern("@delegate_hash");
170
166
  values_id = rb_intern("@values");
@@ -0,0 +1,15 @@
1
+ #pragma once
2
+
3
+ #include <ruby.h>
4
+ #include <stdbool.h>
5
+
6
+ #include "serialization_descriptor/attribute.h"
7
+ #include "type_cast/type_cast.h"
8
+ #include "common.h"
9
+
10
+ extern VALUE active_record_attributes_writer(VALUE object,
11
+ VALUE attributes,
12
+ EachAttributeFunc func,
13
+ VALUE context);
14
+
15
+ void init_active_record_attributes_writer(VALUE mPanko);
@@ -0,0 +1,10 @@
1
+ #include "common.h"
2
+
3
+ VALUE attr_name_for_serialization(Attribute attribute) {
4
+ volatile VALUE name_str = attribute->name_str;
5
+ if (attribute->alias_name != Qnil) {
6
+ name_str = attribute->alias_name;
7
+ }
8
+
9
+ return name_str;
10
+ }
@@ -0,0 +1,8 @@
1
+ #pragma once
2
+
3
+ #include "../serialization_descriptor/attribute.h"
4
+ #include "ruby.h"
5
+
6
+ typedef void (*EachAttributeFunc)(VALUE writer, VALUE name, VALUE value);
7
+
8
+ VALUE attr_name_for_serialization(Attribute attribute);
@@ -0,0 +1,17 @@
1
+ #include "plain.h"
2
+
3
+ VALUE plain_attributes_writer(VALUE obj,
4
+ VALUE attributes,
5
+ EachAttributeFunc func,
6
+ VALUE writer) {
7
+ long i;
8
+ for (i = 0; i < RARRAY_LEN(attributes); i++) {
9
+ volatile VALUE raw_attribute = RARRAY_AREF(attributes, i);
10
+ Attribute attribute = attribute_read(raw_attribute);
11
+ volatile VALUE value = rb_funcall(obj, attribute->name_id, 0);
12
+
13
+ func(writer, attr_name_for_serialization(attribute), value);
14
+ }
15
+
16
+ return Qnil;
17
+ }
@@ -0,0 +1,9 @@
1
+ #pragma once
2
+
3
+ #include "common.h"
4
+ #include "ruby.h"
5
+
6
+ VALUE plain_attributes_writer(VALUE obj,
7
+ VALUE attributes,
8
+ EachAttributeFunc func,
9
+ VALUE writer);
@@ -1,3 +1,5 @@
1
+ #pragma once
2
+
1
3
  #include <ruby.h>
2
4
  #include <ruby/oniguruma.h>
3
5
 
@@ -112,7 +112,8 @@ void cache_type_lookup() {
112
112
  ar_date_time_type = rb_const_get_at(ar_type, rb_intern("DateTime"));
113
113
 
114
114
  ar_type_methods = rb_class_instance_methods(0, NULL, ar_string_type);
115
- if(rb_ary_includes(ar_type_methods, rb_to_symbol(rb_str_new_cstr("deserialize")))) {
115
+ if (rb_ary_includes(ar_type_methods,
116
+ rb_to_symbol(rb_str_new_cstr("deserialize")))) {
116
117
  deserialize_from_db_id = rb_intern("deserialize");
117
118
  } else {
118
119
  deserialize_from_db_id = rb_intern("type_cast_from_database");
@@ -1,9 +1,8 @@
1
+ #pragma once
2
+
1
3
  #include <ruby.h>
2
4
  #include <stdbool.h>
3
5
 
4
- #ifndef __PANKO_TYPE_CAST_H__
5
- #define __PANKO_TYPE_CAST_H__
6
-
7
6
  /*
8
7
  * Type Casting
9
8
  *
@@ -75,5 +74,3 @@ static struct _TypeCast type_casts[] = {
75
74
 
76
75
  extern VALUE type_cast(VALUE type_metadata, VALUE value);
77
76
  void panko_init_type_cast(VALUE mPanko);
78
-
79
- #endif
@@ -0,0 +1,50 @@
1
+ #include "writer.h"
2
+
3
+ static bool types_initialized = false;
4
+ static VALUE ar_base_type = Qundef;
5
+
6
+ void init_types(VALUE v) {
7
+ if (types_initialized == true) {
8
+ return;
9
+ }
10
+
11
+ types_initialized = true;
12
+
13
+ volatile VALUE ar_type =
14
+ rb_const_get_at(rb_cObject, rb_intern("ActiveRecord"));
15
+ ar_base_type = rb_const_get_at(ar_type, rb_intern("Base"));
16
+ }
17
+
18
+ AttributesWriter create_attributes_writer(VALUE subject) {
19
+ // If ActiveRecord::Base can't be found it will throw error
20
+ int isErrored;
21
+ rb_protect(init_types, Qnil, &isErrored);
22
+
23
+ if (ar_base_type != Qundef &&
24
+ rb_obj_is_kind_of(subject, ar_base_type) == Qtrue) {
25
+ return (AttributesWriter){
26
+ .object_type = ActiveRecord,
27
+ .write_attributes = active_record_attributes_writer};
28
+ } else {
29
+ return (AttributesWriter){.object_type = Plain,
30
+ .write_attributes = plain_attributes_writer};
31
+ }
32
+
33
+ return create_empty_attributes_writer();
34
+ }
35
+
36
+ VALUE empty_write_attributes(VALUE obj,
37
+ VALUE attributes,
38
+ EachAttributeFunc func,
39
+ VALUE writer) {
40
+ return Qnil;
41
+ }
42
+
43
+ AttributesWriter create_empty_attributes_writer() {
44
+ return (AttributesWriter){.object_type = Unknown,
45
+ .write_attributes = empty_write_attributes};
46
+ }
47
+
48
+ void init_attributes_writer(VALUE mPanko) {
49
+ init_active_record_attributes_writer(mPanko);
50
+ }
@@ -0,0 +1,31 @@
1
+ #pragma once
2
+
3
+ #include <ruby.h>
4
+
5
+ #include "common.h"
6
+ #include "active_record.h"
7
+ #include "plain.h"
8
+
9
+ enum ObjectType { Unknown = 0, ActiveRecord = 1, Plain = 2 };
10
+
11
+ typedef struct _AttributesWriter {
12
+ enum ObjectType object_type;
13
+
14
+ int (*write_attributes)(VALUE object,
15
+ VALUE attributes,
16
+ EachAttributeFunc func,
17
+ VALUE context);
18
+ } AttributesWriter;
19
+
20
+ /**
21
+ * Infers the attributes writer from the subject type
22
+ */
23
+ AttributesWriter create_attributes_writer(VALUE subject);
24
+
25
+ /**
26
+ * Creates empty writer
27
+ * Useful when the writer is not known, and you need init something
28
+ */
29
+ AttributesWriter create_empty_attributes_writer();
30
+
31
+ void init_attributes_writer(VALUE mPanko);
@@ -1,8 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
  require "mkmf"
3
+ require "pathname"
3
4
 
4
5
  $CPPFLAGS += " -Wall"
5
6
 
6
7
  extension_name = "panko_serializer"
7
8
  dir_config(extension_name)
9
+
10
+ RbConfig.expand(srcdir = "$(srcdir)".dup)
11
+
12
+ # enum all source files
13
+ $srcs = Dir[File.join(srcdir, "**/*.c")]
14
+
15
+
16
+ # Get all source directories recursivley
17
+ directories = Dir[File.join(srcdir, "**/*")].select { |f| File.directory?(f) }
18
+ directories = directories.map { |d| Pathname.new(d).relative_path_from(Pathname.new(srcdir)) }
19
+ directories.each do |dir|
20
+ # add include path to the internal folder
21
+ # $(srcdir) is a root folder, where "extconf.rb" is stored
22
+ $INCFLAGS << " -I$(srcdir)/#{dir}"
23
+
24
+ # add folder, where compiler can search source files
25
+ $VPATH << "$(srcdir)/#{dir}"
26
+ end
27
+
8
28
  create_makefile("panko/panko_serializer")
@@ -41,8 +41,8 @@ void serialize_fields(VALUE subject,
41
41
  VALUE str_writer,
42
42
  SerializationDescriptor descriptor,
43
43
  VALUE context) {
44
- panko_each_attribute(subject, descriptor->attributes, write_value,
45
- str_writer);
44
+ descriptor->attributes_writer.write_attributes(
45
+ subject, descriptor->attributes, write_value, str_writer);
46
46
 
47
47
  serialize_method_fields(subject, str_writer, descriptor, context);
48
48
  }
@@ -94,6 +94,8 @@ VALUE serialize_subject(VALUE key,
94
94
  VALUE str_writer,
95
95
  SerializationDescriptor descriptor,
96
96
  VALUE context) {
97
+ sd_set_writer(descriptor, subject);
98
+
97
99
  rb_funcall(str_writer, push_object_id, 1, key);
98
100
 
99
101
  serialize_fields(subject, str_writer, descriptor, context);
@@ -141,8 +143,8 @@ VALUE serialize_subject_api(VALUE klass,
141
143
  VALUE str_writer,
142
144
  VALUE descriptor,
143
145
  VALUE context) {
144
- return serialize_subject(Qnil, subject, str_writer, sd_read(descriptor),
145
- context);
146
+ SerializationDescriptor sd = sd_read(descriptor);
147
+ return serialize_subject(Qnil, subject, str_writer, sd, context);
146
148
  }
147
149
 
148
150
  VALUE serialize_subjects_api(VALUE klass,
@@ -171,7 +173,7 @@ void Init_panko_serializer() {
171
173
  serialize_subjects_api, 4);
172
174
 
173
175
  panko_init_serialization_descriptor(mPanko);
174
- panko_init_attributes_iterator(mPanko);
176
+ init_attributes_writer(mPanko);
175
177
  panko_init_type_cast(mPanko);
176
178
  panko_init_attribute(mPanko);
177
179
  panko_init_association(mPanko);
@@ -1,10 +1,9 @@
1
1
  #include <ruby.h>
2
2
 
3
- #include "association.h"
4
- #include "attribute.h"
5
- #include "attributes_iterator.h"
6
- #include "serialization_descriptor.h"
7
- #include "type_cast.h"
3
+ #include "attributes_writer/writer.h"
4
+ #include "serialization_descriptor/serialization_descriptor.h"
5
+ #include "serialization_descriptor/association.h"
6
+ #include "serialization_descriptor/attribute.h"
8
7
 
9
8
  VALUE serialize_subject(VALUE key,
10
9
  VALUE subject,
@@ -5,6 +5,7 @@ VALUE cSerializationDescriptor;
5
5
  static ID context_id;
6
6
  static ID object_id;
7
7
 
8
+
8
9
  static void sd_free(SerializationDescriptor sd) {
9
10
  if (!sd) {
10
11
  return;
@@ -42,6 +43,8 @@ static VALUE sd_new(int argc, VALUE* argv, VALUE self) {
42
43
  sd->has_many_associations = Qnil;
43
44
  sd->aliases = Qnil;
44
45
 
46
+ sd->attributes_writer = create_empty_attributes_writer();
47
+
45
48
  return Data_Wrap_Struct(cSerializationDescriptor, sd_mark, sd_free, sd);
46
49
  }
47
50
 
@@ -49,6 +52,15 @@ SerializationDescriptor sd_read(VALUE descriptor) {
49
52
  return (SerializationDescriptor)DATA_PTR(descriptor);
50
53
  }
51
54
 
55
+
56
+ void sd_set_writer(SerializationDescriptor sd, VALUE subject) {
57
+ if (sd->attributes_writer.object_type != Unknown) {
58
+ return;
59
+ }
60
+
61
+ sd->attributes_writer = create_attributes_writer(subject);
62
+ }
63
+
52
64
  VALUE sd_build_serializer(SerializationDescriptor sd) {
53
65
  if (sd->serializer == Qnil) {
54
66
  VALUE args[0];
@@ -1,7 +1,9 @@
1
+ #pragma once
2
+
1
3
  #include <ruby.h>
4
+ #include <stdbool.h>
2
5
 
3
- #ifndef __SD_H__
4
- #define __SD_H__
6
+ #include "attributes_writer/writer.h"
5
7
 
6
8
  typedef struct _SerializationDescriptor {
7
9
  // type of the serializer, so we can create it later
@@ -15,6 +17,8 @@ typedef struct _SerializationDescriptor {
15
17
  VALUE method_fields;
16
18
  VALUE has_one_associations;
17
19
  VALUE has_many_associations;
20
+
21
+ AttributesWriter attributes_writer;
18
22
  } * SerializationDescriptor;
19
23
 
20
24
  SerializationDescriptor sd_read(VALUE descriptor);
@@ -23,7 +27,6 @@ void sd_mark(SerializationDescriptor data);
23
27
 
24
28
  VALUE sd_build_serializer(SerializationDescriptor descriptor);
25
29
  void sd_apply_serializer_config(VALUE serializer, VALUE object, VALUE context);
30
+ void sd_set_writer(SerializationDescriptor sd, VALUE subject);
26
31
 
27
32
  void panko_init_serialization_descriptor(VALUE mPanko);
28
-
29
- #endif
@@ -7,6 +7,10 @@ module Panko
7
7
  #
8
8
  def self.build(serializer, options={})
9
9
  backend = Panko::SerializationDescriptor.duplicate(serializer._descriptor)
10
+
11
+ if serializer.respond_to? :filters_for
12
+ options.merge! serializer.filters_for(options[:context])
13
+ end
10
14
  backend.apply_filters(options)
11
15
  backend
12
16
  end
data/lib/panko/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Panko
3
- VERSION = "0.3.4"
3
+ VERSION = "0.3.5"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panko_serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yosi Attias
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-06 00:00:00.000000000 Z
11
+ date: 2018-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -101,10 +101,11 @@ files:
101
101
  - benchmarks/allocs.rb
102
102
  - benchmarks/app.rb
103
103
  - benchmarks/benchmarking_support.rb
104
- - benchmarks/bm_active_model_serializers.rb
105
- - benchmarks/bm_controller.rb
104
+ - benchmarks/bm_ams_0_10.rb
105
+ - benchmarks/bm_fastjsonapi.rb
106
106
  - benchmarks/bm_panko_json.rb
107
107
  - benchmarks/bm_panko_object.rb
108
+ - benchmarks/bm_plain_object.rb
108
109
  - benchmarks/bm_serialization_descriptor.rb
109
110
  - benchmarks/bm_serializer_resolver.rb
110
111
  - benchmarks/profile.rb
@@ -121,21 +122,27 @@ files:
121
122
  - docs/getting-started.md
122
123
  - docs/performance.md
123
124
  - docs/response-bag.md
124
- - ext/panko_serializer/association.c
125
- - ext/panko_serializer/association.h
126
- - ext/panko_serializer/attribute.c
127
- - ext/panko_serializer/attribute.h
128
- - ext/panko_serializer/attributes_iterator.c
129
- - ext/panko_serializer/attributes_iterator.h
125
+ - ext/panko_serializer/attributes_writer/active_record.c
126
+ - ext/panko_serializer/attributes_writer/active_record.h
127
+ - ext/panko_serializer/attributes_writer/common.c
128
+ - ext/panko_serializer/attributes_writer/common.h
129
+ - ext/panko_serializer/attributes_writer/plain.c
130
+ - ext/panko_serializer/attributes_writer/plain.h
131
+ - ext/panko_serializer/attributes_writer/type_cast/time_conversion.c
132
+ - ext/panko_serializer/attributes_writer/type_cast/time_conversion.h
133
+ - ext/panko_serializer/attributes_writer/type_cast/type_cast.c
134
+ - ext/panko_serializer/attributes_writer/type_cast/type_cast.h
135
+ - ext/panko_serializer/attributes_writer/writer.c
136
+ - ext/panko_serializer/attributes_writer/writer.h
130
137
  - ext/panko_serializer/extconf.rb
131
138
  - ext/panko_serializer/panko_serializer.c
132
139
  - ext/panko_serializer/panko_serializer.h
133
- - ext/panko_serializer/serialization_descriptor.c
134
- - ext/panko_serializer/serialization_descriptor.h
135
- - ext/panko_serializer/time_conversion.c
136
- - ext/panko_serializer/time_conversion.h
137
- - ext/panko_serializer/type_cast.c
138
- - ext/panko_serializer/type_cast.h
140
+ - ext/panko_serializer/serialization_descriptor/association.c
141
+ - ext/panko_serializer/serialization_descriptor/association.h
142
+ - ext/panko_serializer/serialization_descriptor/attribute.c
143
+ - ext/panko_serializer/serialization_descriptor/attribute.h
144
+ - ext/panko_serializer/serialization_descriptor/serialization_descriptor.c
145
+ - ext/panko_serializer/serialization_descriptor/serialization_descriptor.h
139
146
  - lib/panko/array_serializer.rb
140
147
  - lib/panko/attribute.rb
141
148
  - lib/panko/response.rb
@@ -165,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
172
  version: '0'
166
173
  requirements: []
167
174
  rubyforge_project:
168
- rubygems_version: 2.7.4
175
+ rubygems_version: 2.7.5
169
176
  signing_key:
170
177
  specification_version: 4
171
178
  summary: Fast serialization for ActiveModel
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "./benchmarking_support"
3
- require_relative "./app"
4
- require_relative "./setup"
5
-
6
- class AmsAuthorFastSerializer < ActiveModel::Serializer
7
- attributes :id, :name
8
- end
9
-
10
- class AmsPostFastSerializer < ActiveModel::Serializer
11
- attributes :id, :body, :title, :author_id, :created_at
12
- end
13
-
14
- class AmsPostWithHasOneFastSerializer < ActiveModel::Serializer
15
- attributes :id, :body, :title, :author_id
16
-
17
- has_one :author, serializer: AmsAuthorFastSerializer
18
- end
19
-
20
- def benchmark_ams(prefix, serializer, options = {})
21
- merged_options = options.merge(each_serializer: serializer)
22
-
23
- data = Benchmark.data
24
- posts = data[:all]
25
- posts_50 = data[:small]
26
-
27
-
28
- Benchmark.run("AMS_#{prefix}_Posts_#{posts.count}") do
29
- ActiveModel::ArraySerializer.new(posts, merged_options).serializable_object
30
- end
31
-
32
- data = Benchmark.data
33
- posts = data[:all]
34
- posts_50 = data[:small]
35
-
36
- Benchmark.run("AMS_#{prefix}_Posts_50") do
37
- ActiveModel::ArraySerializer.new(posts_50, merged_options).serializable_object
38
- end
39
- end
40
-
41
-
42
- benchmark_ams "Simple", AmsPostFastSerializer
43
- benchmark_ams "HasOne", AmsPostWithHasOneFastSerializer
44
- benchmark_ams "Except", AmsPostWithHasOneFastSerializer, except: [:title]
45
- benchmark_ams "Include", AmsPostWithHasOneFastSerializer, include: [:id, :body, :author_id, :author]
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "./benchmarking_support"
3
- require_relative "./app"
4
- require_relative "./setup"
5
-
6
- class NullLogger < Logger
7
- def initialize(*args)
8
- end
9
-
10
- def add(*args, &block)
11
- end
12
- end
13
-
14
- class BenchmarkApp < Rails::Application
15
- routes.append do
16
- get "/simple" => "main#simple"
17
- get "/text" => "main#text"
18
-
19
- get "/serialize_to_string" => "main#serialize_to_string"
20
- end
21
-
22
- config.secret_token = "s"*30
23
- config.secret_key_base = "foo"
24
- config.consider_all_requests_local = false
25
-
26
- # simulate production
27
- config.cache_classes = true
28
- config.eager_load = true
29
- config.action_controller.perform_caching = true
30
-
31
- # otherwise deadlock occured
32
- config.middleware.delete "Rack::Lock"
33
-
34
- # to disable log files
35
- config.logger = NullLogger.new
36
- config.active_support.deprecation = :log
37
- end
38
-
39
- BenchmarkApp.initialize!
40
-
41
- class AuthorFastSerializer < Panko::Serializer
42
- attributes :id, :name
43
- end
44
-
45
- class PostWithHasOneFastSerializer < Panko::Serializer
46
- attributes :id, :body, :title, :author_id
47
-
48
- has_one :author, serializer: AuthorFastSerializer
49
- end
50
-
51
- class MainController < ActionController::Base
52
- def text
53
- render text: '{"ok":true}'.freeze, content_type: "application/json".freeze
54
- end
55
-
56
- def simple
57
- render json: { ok: true }
58
- end
59
-
60
- def serialize_to_string
61
- data = Benchmark.data[:all]
62
- render text: Panko::ArraySerializer.new(data, each_serializer: PostWithHasOneFastSerializer).to_json, content_type: "application/json".freeze
63
- end
64
- end
65
-
66
- class RouteNotFoundError < StandardError;end
67
-
68
-
69
- def request(method, path)
70
- response = Rack::MockRequest.new(BenchmarkApp).send(method, path)
71
- if response.status.in?([404, 500])
72
- raise RouteNotFoundError.new, 'not found #{method.to_s.upcase} #{path}'
73
- end
74
- response
75
- end
76
-
77
-
78
- Benchmark.run("text") { request(:get, "/text") }
79
- Benchmark.run("simple") { request(:get, "/simple") }
80
-
81
- Benchmark.run("serialize_to_string") { request(:get, "/serialize_to_string") }
@@ -1,15 +0,0 @@
1
- #include <ruby.h>
2
- #include <stdbool.h>
3
-
4
- #include "attribute.h"
5
- #include "serialization_descriptor.h"
6
- #include "type_cast.h"
7
-
8
- typedef void (*EachAttributeFunc)(VALUE writer, VALUE name, VALUE value);
9
-
10
- extern VALUE panko_each_attribute(VALUE object,
11
- VALUE attributes,
12
- EachAttributeFunc func,
13
- VALUE context);
14
-
15
- void panko_init_attributes_iterator(VALUE mPanko);