panko_serializer 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
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);