panko_serializer 0.8.3 → 0.8.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/ext/panko_serializer/attributes_writer/active_record.c +2 -29
  4. data/ext/panko_serializer/attributes_writer/type_cast/type_cast.c +24 -13
  5. data/lib/panko/version.rb +1 -1
  6. metadata +3 -72
  7. data/.clang-format +0 -3
  8. data/.github/dependabot.yml +0 -6
  9. data/.github/workflows/docs.yml +0 -39
  10. data/.github/workflows/lint.yml +0 -30
  11. data/.github/workflows/ruby.yml +0 -51
  12. data/.gitignore +0 -20
  13. data/.rspec +0 -2
  14. data/.rubocop.yml +0 -37
  15. data/Appraisals +0 -29
  16. data/Gemfile +0 -36
  17. data/Rakefile +0 -93
  18. data/benchmarks/BENCHMARKS.md +0 -48
  19. data/benchmarks/allocs.rb +0 -23
  20. data/benchmarks/app.rb +0 -11
  21. data/benchmarks/benchmarking_support.rb +0 -44
  22. data/benchmarks/bm_ams_0_10.rb +0 -43
  23. data/benchmarks/bm_object_writer.rb +0 -65
  24. data/benchmarks/bm_panko_json.rb +0 -60
  25. data/benchmarks/bm_panko_object.rb +0 -46
  26. data/benchmarks/bm_plain_object.rb +0 -99
  27. data/benchmarks/bm_serialization_descriptor.rb +0 -76
  28. data/benchmarks/bm_serializer_resolver.rb +0 -22
  29. data/benchmarks/bm_to_object.rb +0 -81
  30. data/benchmarks/profile.rb +0 -86
  31. data/benchmarks/sanity.rb +0 -83
  32. data/benchmarks/setup.rb +0 -56
  33. data/benchmarks/type_casts/bm_active_record.rb +0 -58
  34. data/benchmarks/type_casts/bm_panko.rb +0 -65
  35. data/benchmarks/type_casts/support.rb +0 -27
  36. data/docs/docs/associations.md +0 -107
  37. data/docs/docs/attributes.md +0 -143
  38. data/docs/docs/design-choices.md +0 -127
  39. data/docs/docs/getting-started.md +0 -70
  40. data/docs/docs/introduction.md +0 -13
  41. data/docs/docs/performance.md +0 -32
  42. data/docs/docs/response-bag.md +0 -83
  43. data/docs/docusaurus.config.js +0 -86
  44. data/docs/package-lock.json +0 -15994
  45. data/docs/package.json +0 -21
  46. data/docs/sidebars.json +0 -15
  47. data/docs/src/css/customTheme.css +0 -9
  48. data/docs/static/.DS_Store +0 -0
  49. data/docs/static/CNAME +0 -1
  50. data/docs/static/css/custom.css +0 -51
  51. data/docs/static/img/favicon.ico +0 -0
  52. data/docs/static/img/oss_logo.png +0 -0
  53. data/docs/static/img/undraw_code_review.svg +0 -1
  54. data/docs/static/img/undraw_monitor.svg +0 -1
  55. data/docs/static/img/undraw_note_list.svg +0 -1
  56. data/docs/static/img/undraw_online.svg +0 -1
  57. data/docs/static/img/undraw_open_source.svg +0 -1
  58. data/docs/static/img/undraw_operating_system.svg +0 -1
  59. data/docs/static/img/undraw_react.svg +0 -1
  60. data/docs/static/img/undraw_tweetstorm.svg +0 -1
  61. data/docs/static/img/undraw_youtube_tutorial.svg +0 -1
  62. data/docs/static/index.html +0 -14
  63. data/gemfiles/7.0.0.gemfile +0 -39
  64. data/gemfiles/7.0.0.gemfile.lock +0 -176
  65. data/gemfiles/7.1.0.gemfile +0 -39
  66. data/gemfiles/7.1.0.gemfile.lock +0 -198
  67. data/gemfiles/7.2.0.gemfile +0 -39
  68. data/gemfiles/7.2.0.gemfile.lock +0 -198
  69. data/gemfiles/8.0.0.gemfile +0 -39
  70. data/gemfiles/8.0.0.gemfile.lock +0 -219
  71. data/panko_serializer.gemspec +0 -36
@@ -1,127 +0,0 @@
1
- ---
2
- id: design-choices
3
- title: Design Choices
4
- sidebar_label: Design Choices
5
- ---
6
- In short, Panko, is a serializer for ActiveRecord objects (it can't serialize any other object), which strives for high performance & simple API (which is inspired by ActiveModelSerializers).
7
-
8
- Its performance is achieved by:
9
-
10
- - `Oj::StringWriter` - I will elaborate later.
11
- - Type casting — instead of relying on ActiveRecord to do its type cast, Panko is doing it by itself.
12
- - Figuring out the metadata, ahead of time — therefore, we ask less questions during the `serialization loop`.
13
-
14
- ## Serialization overview
15
-
16
- First, let's start with overview. Let's say we want to serialize `User` object, which has
17
- `first_name`, `last_name`, `age`, and `email` properties.
18
-
19
- The serializer definition will be something like this:
20
-
21
- ```ruby
22
-
23
- class UserSerializer < Panko::Serializer
24
- attributes :name, :age, :email
25
-
26
- def name
27
- "#{object.first_name} #{object.last_name}"
28
- end
29
- end
30
-
31
- ```
32
-
33
- And the usage of this serializer will be:
34
-
35
- ```ruby
36
-
37
- # fetch user from database
38
- user = User.first
39
-
40
- # create serializer, with empty options
41
- serializer = UserSerializer.new
42
-
43
- # serialize to JSON
44
- serializer.serialize_to_json(user)
45
-
46
- ```
47
-
48
- Let's go over the steps that Panko will execute behind the scenes for this flow.
49
- _I will skip the serializer definition part, because it's fairly simple and straightforward (see `lib/panko/serializer.rb`)_
50
-
51
- First step, while initializing the UserSerializer, we will create a **Serialization Descriptor** for this class.
52
- Serialization Descriptor's goal is to answer those questions:
53
-
54
- - Which fields do we have? In our case, `:age`, `:email`
55
- - Which method fields do we have? In our case `:name`
56
- - Which associations do we have (and their serialization descriptors)
57
-
58
- The serialization description is also responsible for filtering the attributes (`only` \\ `except`).
59
-
60
- Now, that we have the serialization descriptor, we are finished with the Ruby part of Panko, and all we did here is done in _initialization time_ and now we move to C code.
61
-
62
- In C land, we take the `user` object and the serialization descriptor, and start the serialization process which is separated to 4 parts:
63
-
64
- - Serializing Fields - looping through serialization descriptor's `fields` and read them from the ActiveRecord object (see `Type Casting`) and write them to the writer.
65
- - Serializing Method Fields - creating (a cached) serializer instance, setting its `@object` and `@context`, calling all the method fields and writing them to the writer.
66
- - Serializing associations — this is simple, once we have fields + method fields, we just repeat the process.
67
-
68
- Once this is finished, we have nice JSON string.
69
- Now let's dig deeper.
70
-
71
- ## Interesting parts
72
-
73
- ### Oj::StringWriter
74
-
75
- If you read the code of ActiveRecord serialization code in Ruby, you will observe this flow:
76
-
77
- 1. Get an array of ActiveRecord objects (`User.all` for example)
78
- 2. Build new array of hashes where each hash is `User` with the attributes we selected
79
- 3. The JSON serializer, takes this array of hashes and loop them, and converts it to JSON string
80
-
81
- This entire process is expensive in terms of Memory & CPU, and this where the combination of Panko and Oj::StringWriter really shines.
82
-
83
- In Panko, the serialization process of the above is:
84
-
85
- 1. Get an array of ActiveRecord objects (`User.all` for example)
86
- 2. Create `Oj::StringWriter` and feed the values to it, via `push_value` / `push_object` / `push_object` and behind the scene, `Oj::StringWriter` will serialize the objects incrementally into a string.
87
- 3. Get from `Oj::StringWriter` the completed JSON string — which is a no-op, since `Oj::StringWriter` already built the string.
88
-
89
- ### Figuring out the metadata, ahead of time.
90
-
91
- Another observation I noticed in the ruby serializers is that they ask and do a lot in a serialization loop:
92
-
93
- - Is this field a method? is it a property?
94
- - Which fields and associations do I need for the serializer to consider the `only` and `except` options
95
- - What is the serializer of this has_one association?
96
-
97
- Panko tries to ask the bare minimum in serialization by building `Serialization Descriptor` for each serialization and caching it.
98
-
99
- The Serialization Descriptor will do the filtering of `only` and `except` and will check if a field is a method or not (therefore Panko doesn't have list of `attributes`)
100
-
101
- ### Type Casting
102
-
103
- This is the final part, which helped yield most of the performance improvements.
104
- In ActiveRecord, when we read a value of attribute, it does type casting of the DB value to its real ruby type.
105
-
106
- For example, time strings are converted to Time objects, Strings are duplicated, and Integers are converts from their values to Number.
107
-
108
- This type casting is really expensive, as it's responsible for most of the allocations in the serialization flow and most of them can be "relaxed".
109
-
110
- If we think about it, we don't need to duplicate strings or convert time strings to time objects or even parse JSON strings for the JSON serialization process.
111
-
112
- What Panko does is that if we have ActiveRecord type string, we won't duplicate it.
113
- If we have an integer string value, we will convert it to an integer, and the same goes for other types.
114
-
115
- All of these conversions are done in C, which of course yields a big performance improvement.
116
-
117
- #### Time type casting
118
-
119
- While you read Panko source code, you will encounter the time type casting and immediately you will have a "WTF?" moment.
120
-
121
- The idea behind the time type casting code relies on the end result of JSON type casting — what we need in order to serialize Time to JSON? UTC ISO8601 time format representation.
122
-
123
- The time type casting works as follows:
124
-
125
- - If it's a string that ends with `Z`, and the strings matches the UTC ISO8601 regex, then we just return the string.
126
- - If it's a string and it doesn't follow the rules above, we check if it's a timestamp in database format and convert it via regex + string concat to UTC ISO8601 - Yes, there is huge assumption here, that the database returns UTC timestamps — this will be configureable (before Panko official release).
127
- - If it's none of the above, I will let ActiveRecord type casting do it's magic.
@@ -1,70 +0,0 @@
1
- ---
2
- id: getting-started
3
- title: Getting Started
4
- sidebar_label: Getting Started
5
- ---
6
- ## Installation
7
-
8
- To install Panko, all you need is to add it to your Gemfile:
9
-
10
- ```ruby
11
-
12
- gem "panko_serializer"
13
-
14
- ```
15
-
16
- Then, install it on the command line:
17
-
18
- ```
19
-
20
- bundle install
21
-
22
- ```
23
-
24
- ## Creating your first serializer
25
-
26
- Let's create serializer and use it inside Rails controller.
27
-
28
- ```ruby
29
- class PostSerializer < Panko::Serializer
30
- attributes :title
31
- end
32
-
33
- class UserSerializer < Panko::Serializer
34
- attributes :id, :name, :age
35
-
36
- has_many :posts, serializer: PostSerializer
37
- end
38
- ```
39
-
40
- ### Serializing an object
41
-
42
- And now serialize a single object
43
-
44
- ```ruby
45
-
46
- # Using Oj serializer
47
- PostSerializer.new.serialize_to_json(Post.first)
48
-
49
- # or, similar to #serializable_hash
50
- PostSerializer.new.serialize(Post.first).to_json
51
-
52
- ```
53
-
54
- ### Using the serializers in a controller
55
-
56
- As you can see, defining serializers is simple and resembles ActiveModelSerializers 0.9,
57
- To utilize the `UserSerializer` inside a Rails controller and serialize some users, all we need to do is:
58
-
59
- ```ruby
60
-
61
- class UsersController < ApplicationController
62
- def index
63
- users = User.includes(:posts).all
64
- render json: Panko::ArraySerializer.new(users, each_serializer: UserSerializer).to_json
65
- end
66
- end
67
-
68
- ```
69
-
70
- And voila, we have endpoint which serialize users using Panko!
@@ -1,13 +0,0 @@
1
- ---
2
- id: index
3
- title: Introduction
4
- sidebar_label: Introduction
5
- slug: /
6
- ---
7
- Panko is library which is inspired by ActiveModelSerializers 0.9 for serializing ActiveRecord/Ruby objects to JSON strings, fast.
8
-
9
- To achieve it's [performance](https://panko.dev/docs/performance/):
10
-
11
- - Oj - Panko relies Oj since it's fast and allow to serialize incrementally using `Oj::StringWriter`
12
- - Serialization Descriptor - Panko computes most of the metadata ahead of time, to save time later in serialization.
13
- - Type casting — Panko does type casting by it's self, instead of relying ActiveRecord.
@@ -1,32 +0,0 @@
1
- ---
2
- id: performance
3
- title: Performance
4
- sidebar_label: Performance
5
- ---
6
- The performance of Panko is measured using microbenchmarks and load testing.
7
-
8
- ## Microbenchmarks
9
-
10
- The following microbenchmarks are run on MacBook Pro (16-inch, 2021, M1 Max), Ruby 3.2.0 with Rails 7.0.5
11
- demonstrating the performance of ActiveModelSerializers 0.10.13 and Panko 0.8.0
12
-
13
- | Benchmark | AMS ip/s | Panko ip/s |
14
- | ----------------- | -------- | ---------- |
15
- | Simple_Posts_2300 | 11.72 | 523.05 |
16
- | Simple_Posts_50 | 557.29 | 23,011.9 |
17
- | HasOne_Posts_2300 | 5.91 | 233.44 |
18
- | HasOne_Posts_50 | 285.8 | 10,362.79 |
19
-
20
- ## Real-world benchmark
21
-
22
- The real-world benchmark here is endpoint which serializes 7,884 entries with 48 attributes and no associations.
23
- The benchmark took place in environment that simulates production environment and run using `wrk` from machine on the same cluster.
24
-
25
- | Metric | AMS | Panko |
26
- | ------------------ | ----- | ----- |
27
- | Avg Response Time | 4.89s | 1.48s |
28
- | Max Response Time | 5.42s | 1.83s |
29
- | 99th Response Time | 5.42s | 1.74s |
30
- | Total Requests | 61 | 202 |
31
-
32
- _Thanks to [Bringg](https://www.bringg.com) for providing the infrastructure for the benchmarks_
@@ -1,83 +0,0 @@
1
- ---
2
- id: response-bag
3
- title: Response
4
- sidebar_label: Response
5
- ---
6
- Let's say you have some JSON payload which can is constructed using Panko serialization result,
7
- like this:
8
-
9
- ```ruby
10
-
11
- class PostsController < ApplicationController
12
- def index
13
- posts = Post.all
14
- render json: {
15
- success: true,
16
- total_count: posts.count,
17
- posts: Panko::ArraySerializer.new(posts, each_serializer: PostSerializer).to_json
18
- }
19
- end
20
- end
21
-
22
- ```
23
-
24
- The output of the above will be json string (for `posts`) inside json string and this were `Panko::Response` shines.
25
-
26
- ```ruby
27
-
28
- class PostsController < ApplicationController
29
- def index
30
- posts = Post.all
31
- render json: Panko::Response.new(
32
- success: true,
33
- total_count: posts.count,
34
- posts: Panko::ArraySerializer.new(posts, each_serializer: PostSerializer)
35
- )
36
- end
37
- end
38
-
39
- ```
40
-
41
- And everything will work as expected!
42
-
43
- For a single object serialization, we need to use a different API (since `Panko::Serializer` don't accept an object in it's constructor):
44
-
45
- ```ruby
46
-
47
- class PostsController < ApplicationController
48
- def show
49
- post = Post.find(params[:id])
50
-
51
- render(
52
- json: Panko::Response.create do |r|
53
- {
54
- success: true,
55
- post: r.serializer(post, PostSerializer)
56
- }
57
- end
58
- )
59
- end
60
- end
61
-
62
- ```
63
-
64
- ## JsonValue
65
-
66
- Let's take the above example further, we serialized the posts and cached it as JSON string in our Cache.
67
- Now, you can wrap the cached value with `Panko::JsonValue`, like here -
68
-
69
- ```ruby
70
-
71
- class PostsController < ApplicationController
72
- def index
73
- posts = Cache.get("/posts")
74
-
75
- render json: Panko::Response.new(
76
- success: true,
77
- total_count: posts.count,
78
- posts: Panko::JsonValue.from(posts)
79
- )
80
- end
81
- end
82
-
83
- ```
@@ -1,86 +0,0 @@
1
- module.exports = {
2
- "title": "Panko Serializers",
3
- "tagline": "High Performance JSON Serialization for ActiveRecord & Ruby Objects",
4
- "url": "https://panko.dev",
5
- "baseUrl": "/",
6
- "organizationName": "yosiat",
7
- "projectName": "panko_serializer",
8
- "favicon": "favicon.ico",
9
- "customFields": {
10
- "repoPath": "yosiat/panko_serializer",
11
- "repoUrl": "https://github.com/yosiat/panko_serializer",
12
- },
13
- "onBrokenLinks": "log",
14
- "onBrokenMarkdownLinks": "log",
15
- "presets": [
16
- [
17
- "@docusaurus/preset-classic",
18
- {
19
- "docs": {
20
- "path": "./docs",
21
- "showLastUpdateAuthor": false,
22
- "showLastUpdateTime": false,
23
- "sidebarPath": "./sidebars.json",
24
- "routeBasePath": process.env.NODE_ENV === 'production' ? '/docs' : '/',
25
- },
26
- "blog": false,
27
- "pages": false,
28
- "theme": {
29
- "customCss": "./src/css/customTheme.css"
30
- }
31
- }
32
- ]
33
- ],
34
- "plugins": [],
35
- "themeConfig": {
36
- colorMode: {
37
- defaultMode: 'light',
38
- disableSwitch: true,
39
- },
40
- "navbar": {
41
- "title": "Panko Serializers",
42
- "items": [
43
- {
44
- "to": "introduction",
45
- "label": "Docs",
46
- "position": "left"
47
- }
48
- ]
49
- },
50
- "image": "img/undraw_online.svg",
51
- "footer": {
52
- "links": [
53
- {
54
- title: "GitHub",
55
- items: [
56
- {
57
- label: "Repository",
58
- href: "https://github.com/yosiat/panko_serializer"
59
- },
60
- {
61
- label: "Discussions",
62
- href: "https://github.com/yosiat/panko_serializer/discussions"
63
- },
64
- {
65
- label: "Issues",
66
- href: "https://github.com/yosiat/panko_serializer/issues"
67
- },
68
- {
69
- "html": `
70
- <iframe
71
- src="https://ghbtns.com/github-btn.html?user=yosiat&amp;repo=panko_serializer&amp;type=star&amp;count=true&amp;size=medium"
72
- title="GitHub Stars"
73
- />`
74
- }
75
- ]
76
- }
77
- ],
78
- "copyright": `Copyright © ${new Date().getFullYear()} Panko Serializer`,
79
- },
80
- prism: {
81
- theme: require('prism-react-renderer/themes/github'), // Optional: Customize theme
82
- darkTheme: require('prism-react-renderer/themes/dracula'), // Optional: Dark theme
83
- additionalLanguages: ['ruby'], // Add Ruby as an additional language
84
- },
85
- }
86
- }