panko_serializer 0.3.2 → 0.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e9e1120b6231ae836d11ca59c17443518f5ccb80
4
- data.tar.gz: 2b38dd527027204628714825d38d5f8c83b0d4a9
2
+ SHA256:
3
+ metadata.gz: eef3c77d8a03018af85326b4c07890d6d543909925c664f6baf6f20f634033e2
4
+ data.tar.gz: 28c88dea3cd021f1ba3cb5e31b21e03f3ddb9cdbf4851cccafa78770d742a994
5
5
  SHA512:
6
- metadata.gz: 9642ae4e418b15a0051d2bb08b352bb035afabfb3f0eb115ded715fb6edc5db91a3a17a4dbd76793607b3c6db020eb2ea08625bf11e5b85311835cb8eebb428d
7
- data.tar.gz: 9b0225b6e43c743425b8cbb0e105541ec646aa50a7fcaf634631ddc427ecc808cf1f96ac3a3ebe96bccdff292708f9a473f16be8481e4959917b79fc9b95d985
6
+ metadata.gz: edbe1e16a0216d8cd9e8af32289e6b173565ad7d1b50c3177f90a2cc683fd00d26cf7248e0d563e4a76916e481d0723b23041fe6973c5df05b180227e1da82fe
7
+ data.tar.gz: d0883a7ba84ff19d57738d56846ca9fc9b4ed70d6343c6280225ccfa83abe6489d611b64cea4be294ce4ddccfc8e992be55545fd6ff18bf65936fae91db94ce3
data/.gitignore CHANGED
@@ -8,6 +8,10 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /vendor/bundle/
11
+ .byebug_history
12
+ *.bundle
11
13
 
12
14
  # rspec failure tracking
13
15
  .rspec_status
16
+ _docpress
17
+ node_modules
data/.travis.yml CHANGED
@@ -3,9 +3,22 @@ cache: bundler
3
3
  language: ruby
4
4
  rvm:
5
5
  - 2.4.2
6
+
7
+ env:
8
+ global:
9
+ - GIT_NAME: Travis CI
10
+ - GIT_EMAIL: nobody@nobody.org
11
+
6
12
  install: bundle install --path=vendor/bundle --retry=3 --jobs=3
7
- before_install: gem install bundler
8
- after_success: bundle exec rake benchmarks
13
+
14
+ before_install:
15
+ - gem install bundler
16
+ - nvm install 9
17
+
18
+ after_success:
19
+ - npm install docpress && $(npm bin)/docpress build
20
+ - if [ -n "$TRAVIS_TAG" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm install git-update-ghpages && $(npm bin)/git-update-ghpages yosiat/panko_serializer _docpress; fi
21
+ - bundle exec rake benchmarks
9
22
 
10
23
  env:
11
24
  matrix:
data/README.md CHANGED
@@ -4,101 +4,24 @@
4
4
 
5
5
  Panko is library which is inspired by ActiveModelSerializers 0.9 for serializing ActiveRecord objects to JSON strings, fast.
6
6
 
7
- To achieve it's performance:
7
+ To achieve it's [performance](https://yosiat.github.io/panko_serializer/performance.html):
8
8
 
9
9
  * Oj - Panko relies Oj since it's fast and allow to to serialize incrementally using `Oj::StringWriter`
10
10
  * Serialization Descriptor - Panko computes most of the metadata ahead of time, to save time later in serialization.
11
11
  * Type casting — Panko does type casting by it's self, instead of relying ActiveRecord.
12
12
 
13
- To dig deeper about the performance choices, read [Design Choices](https://github.com/yosiat/panko_serializer/wiki/Design-Choices).
13
+ To dig deeper about the performance choices, read [Design Choices](https://yosiat.github.io/panko_serializer/design-choices.html).
14
14
 
15
- ### Status
16
15
 
17
- Panko is not ready for official release - it's missing documentation, tests which all be done incrementally.
18
- If you want to start using Panko to see if it helps you, you are welcome! but check it well before deploying to Production.
16
+ Support
17
+ -------
19
18
 
19
+ - [Documentation](https://yosiat.github.io/panko_serializer)
20
+ - [Getting Started](https://yosiat.github.io/panko_serializer/getting-started.html)
21
+ - Join our [slack community](https://pankoserializer.herokuapp.com/)
20
22
 
21
- ## Installation
23
+ License
24
+ -------
22
25
 
23
- To install Panko, all you need is to add it to your Gemfile:
24
-
25
- ```ruby
26
- gem "panko_serializer"
27
- ```
28
-
29
- Then, install it on the command line:
30
-
31
- ```
32
- > bundle install
33
- ```
34
-
35
-
36
-
37
- ## Usage
38
-
39
- ### Getting Started
40
-
41
- Let's create serializer and use it inside Rails controller.
42
-
43
- ```ruby
44
- class PostSerializer < Panko::Serializer
45
- attributes :title
46
- end
47
-
48
- class UserSerializer < Panko::Serializer
49
- attributes :id, :name, :age
50
-
51
- has_many :posts, serializer: PostSerializer
52
- end
53
- ```
54
-
55
- As you can see, defining serializers is simple and resembles ActiveModelSerializers 0.9,
56
- To utilize the `UserSerializer` inside a Rails controller and serialize some users, all we need to do is:
57
-
58
- ```ruby
59
- class UsersController < ApplicationController
60
- def index
61
- users = User.includes(:posts).all
62
- render json: Panko::ArraySerializer(users, each_serializer: UserSerializer).to_json
63
- end
64
- end
65
- ```
66
-
67
- And voila, we have endpoint which serialize users using Panko!
68
-
69
-
70
- ## Features
71
-
72
- ### Attributes
73
-
74
- Attributes allow you to specify which record attributes you want to serialize,
75
- There are two types of attributes:
76
-
77
- * Field - simple columns defined on the record it self.
78
- * Virtual/Method - this allows to include properties beyond simple fields.
79
-
80
- Example:
81
-
82
- ```ruby
83
- class UserSerializer < Panko::Serializer
84
- attributes :full_name
85
-
86
- def full_name
87
- "#{object.first_name} #{object.last_name}"
88
- end
89
- end
90
- ```
91
-
92
- As you can see, in order to access the serialized record, you need to access `object`.
93
- If you want to pass data to the serializer, beyond the serialized record, you can pass `context` to the serializer (both in single and array serializer).
94
-
95
- #### TODO:
96
- Finished feature, will add documentation sson:
97
- - Realtionships - `has_one`, `has_many`
98
- - Filters & Nested Filters
99
- - Reponse bag
100
-
101
- ## License
102
26
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
103
27
 
104
-
@@ -8,7 +8,7 @@ class AmsAuthorFastSerializer < ActiveModel::Serializer
8
8
  end
9
9
 
10
10
  class AmsPostFastSerializer < ActiveModel::Serializer
11
- attributes :id, :body, :title, :author_id
11
+ attributes :id, :body, :title, :author_id, :created_at
12
12
  end
13
13
 
14
14
  class AmsPostWithHasOneFastSerializer < ActiveModel::Serializer
@@ -39,7 +39,7 @@ def benchmark_ams(prefix, serializer, options = {})
39
39
  end
40
40
 
41
41
 
42
- benchmark_ams "HasOne", AmsPostWithHasOneFastSerializer
43
42
  benchmark_ams "Simple", AmsPostFastSerializer
43
+ benchmark_ams "HasOne", AmsPostWithHasOneFastSerializer
44
44
  benchmark_ams "Except", AmsPostWithHasOneFastSerializer, except: [:title]
45
45
  benchmark_ams "Include", AmsPostWithHasOneFastSerializer, include: [:id, :body, :author_id, :author]
@@ -9,7 +9,7 @@ end
9
9
 
10
10
 
11
11
  class PostFastSerializer < Panko::Serializer
12
- attributes :id, :body, :title, :author_id
12
+ attributes :id, :body, :title, :author_id, :created_at
13
13
  end
14
14
 
15
15
  class PostFastWithMethodCallSerializer < Panko::Serializer
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./benchmarking_support"
3
+ require_relative "./app"
4
+
5
+ class NotSerializer
6
+ end
7
+
8
+ class RealSerializer < Panko::Serializer
9
+ end
10
+
11
+ Benchmark.run("CantFindConst") do
12
+ Panko::SerializerResolver.resolve("cant_find_const")
13
+ end
14
+
15
+ Benchmark.run("NotSerializer") do
16
+ Panko::SerializerResolver.resolve("not")
17
+ end
18
+
19
+ Benchmark.run("RealSerializer") do
20
+ Panko::SerializerResolver.resolve("real")
21
+ end
data/docs/README.md ADDED
@@ -0,0 +1,11 @@
1
+ * [Panko](../README.md)
2
+ * [Getting Stated](getting-started.md)
3
+ * Reference
4
+ * [Attributes](attributes.md)
5
+ * [Associations](associations.md)
6
+ * [Response bag](response-bag.md)
7
+ * [Performance](performance.md)
8
+ * [Design Choices](design-choices.md)
9
+
10
+
11
+
@@ -0,0 +1,76 @@
1
+ # Associations
2
+
3
+ A serializer can define it's own associations - both `has_many` and `has_one` to serializer under the context of the object.
4
+
5
+ For example:
6
+
7
+ ```ruby
8
+ class PostSerializer < Panko::Serailizer
9
+ attributes :title, :body
10
+
11
+ has_one :author, serializer: AuthorSerializer
12
+ has_many :comments, each_serializer: CommentSerializer
13
+ end
14
+ ```
15
+
16
+ ### Inference
17
+
18
+ Panko can find the type of the serializer by looking at the realtionship name, so instead specifying
19
+ the serializer at the above example, we can -
20
+
21
+ ```ruby
22
+ class PostSerializer < Panko::Serailizer
23
+ attributes :title, :body
24
+
25
+ has_one :author
26
+ has_many :comments
27
+ end
28
+ ```
29
+
30
+ The logic of inferencing is -
31
+ - Take the name of the relationship (for example - `:author` / `:comments`) singularize and camelize it
32
+ - Look for const defined with the name aboe and "Serializer" suffix (by using `Object.const_get`)
33
+
34
+ > If Panko can't find the serializer it will throw an error on startup time, for example: `Can't find serializer for PostSerializer.author has_one relationship`
35
+
36
+ ## Nested Filters
37
+
38
+ As talked before, Panko allows you to filter the attributes of a serializer.
39
+ But Panko let you take that step further, and filters the attributes of you associations so you can re-use your serializers in your application.
40
+
41
+ For example, let's say one portion of the application needs to serializer list of posts and serializer their - `title`, `body`, author's id and comments id.
42
+
43
+ We can declare tailored serializer for this, or we can re-use the above defined serializer - `PostSerializer` and use nested filters.
44
+
45
+ ```ruby
46
+ posts = Post.all
47
+
48
+ Panko::ArraySerializer.new(posts, only: {
49
+ instance: [:title, :body, :author, :comments],
50
+ author: [:id],
51
+ comments: [:id],
52
+ })
53
+ ```
54
+
55
+ Let's disect `only` option we passed -
56
+ * `instance` - list of attributes (and associations) we want to serializer for current instance of the serializer, in this case - `PostSerializer`.
57
+ * `author`, `comments` - here we specify the list of attributes we want to serialize for each association.
58
+
59
+ It's important to note that Nested Filters, are recursive, in other words, we can filter the association's associations.
60
+
61
+ For example, `CommentSerializer` have has_one association `Author`, and for each `comments.author` we only it's name.
62
+
63
+ ```ruby
64
+ posts = Post.all
65
+
66
+ Panko::ArraySerializer.new(posts, only: {
67
+ instance: [:title, :body, :author, :comments],
68
+ author: [:id],
69
+ comments: {
70
+ instance: [:id, :author],
71
+ author: [:name]
72
+ }
73
+ })
74
+ ```
75
+
76
+ As you see now in `comments` the `instance` have differenet meaning, the `CommentSerializer`.
@@ -0,0 +1,104 @@
1
+ # Attributes
2
+
3
+ Attributes allow you to specify which record attributes you want to serialize,
4
+ There are two types of attributes:
5
+
6
+ * Field - simple columns defined on the record it self.
7
+ * Virtual/Method - this allows to include properties beyond simple fields.
8
+
9
+
10
+ ```ruby
11
+ class UserSerializer < Panko::Serializer
12
+ attributes :full_name
13
+
14
+ def full_name
15
+ "#{object.first_name} #{object.last_name}"
16
+ end
17
+ end
18
+ ```
19
+
20
+ ## Field Attributes
21
+
22
+ Using field attributes you can control which columns of the given ActiveRecord object you want to serialize.
23
+
24
+ Instead of relying ActiveRecord to do it's type casting, Panko does on it's own for performance reasons (read more in [Design Choices](design-choices.md#type-casting)).
25
+
26
+
27
+ ## Method Attributes
28
+
29
+ Method attributes are used when your serialized values can be derived from the object you are serializing.
30
+
31
+ The serializer's attribute methods can access the object being serialized as `object` -
32
+
33
+ ```ruby
34
+ class PostSerializer < Panko::Serializer
35
+ def author_name
36
+ "#{object.author.first_name} #{object.author.last_name}"
37
+ end
38
+ end
39
+ ```
40
+
41
+ Another useful, thing you can pass your serializer is `context`, a `context` is a bag of data whom your serializer may need.
42
+
43
+ For example, here we will pass the current user:
44
+ ```ruby
45
+ class UserSerializer < Panko::Serializer
46
+ attributes :id, :email
47
+
48
+ def feature_flags
49
+ context[:feature_flags]
50
+ end
51
+ end
52
+
53
+ serializer = UserSerializer.new(context: {
54
+ feature_flags: FeatureFlags.all
55
+ })
56
+
57
+ serializer.serialize(User.first)
58
+ ```
59
+
60
+ ## Filters
61
+
62
+ Filters allows us to reduce the amount of attributes we can serialize, therefore reduce the data usage & performance of serializing.
63
+
64
+ There are two types of filters:
65
+ * only - use those attributes **only** and nothing else
66
+ * except - all attributes **except** those attributes
67
+
68
+ Usage example:
69
+ ```ruby
70
+ class UserSerializer < Panko::Serializer
71
+ attributes :id, :name, :email
72
+ end
73
+
74
+ # this line will return { 'name': '..' }
75
+ UserSerializer.new(only: [:name]).serialize(User.first)
76
+
77
+ # this line will return { 'id': '..', 'email': ... }
78
+ UserSerializer.new(except: [:name]).serialize(User.first)
79
+ ```
80
+
81
+ ## Aliases
82
+
83
+ Let's say we have attribute name that we want to expose to client as different name, the current way of doing so is using method attribute, for example:
84
+
85
+ ```ruby
86
+ class PostSerializer < Panko::Serializer
87
+ attributes :published_at
88
+
89
+ def published_at
90
+ object.created_at
91
+ end
92
+ end
93
+ ```
94
+
95
+ The downside of this approach is that `created_at` skips Panko's type casting, therefore we get direct hit on performance.
96
+
97
+ To fix this, we can use aliases -
98
+
99
+ ```ruby
100
+ class PostSerializer < Panko::Serializer
101
+ aliases created_at: :published_at
102
+ end
103
+ ```
104
+
@@ -0,0 +1,122 @@
1
+ # Design Choices
2
+
3
+ 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).
4
+
5
+ Its performance is achieved by:
6
+
7
+ * `Oj::StringWriter` - I will elaborate later.
8
+ * Type casting — instead of relying on ActiveRecord to do its type cast, Panko is doing it by itself.
9
+ * Figuring out the metadata, ahead of time — therefore, we ask less questions during the `serialization loop`.
10
+
11
+
12
+ ## Serialization overview
13
+
14
+ First, let's start with overview. Let's say we want to serialize `User` object, which has
15
+ `first_name`, `last_name`, `age`, and `email` properties.
16
+
17
+ The serializer definition will be something like this:
18
+
19
+ ```ruby
20
+ class UserSerializer < Panko::Serializer
21
+ attributes :name, :age, :email
22
+
23
+ def name
24
+ "#{object.first_name} #{object.last_name}"
25
+ end
26
+ end
27
+ ```
28
+
29
+ And the usage of this serializer will be:
30
+
31
+ ```ruby
32
+ # fetch user from database
33
+ user = User.first
34
+
35
+ # create serializer, with empty options
36
+ serializer = UserSerilizer.new
37
+
38
+ # serialize to JSON
39
+ serializer.serialize_to_json(user)
40
+ ```
41
+
42
+ Let's go over the steps that Panko will execute behind the scenes for this flow.
43
+ _I will skip the serializer definition part, because it's fairly simple and straightforward (see `lib/panko/serializer.rb`)_
44
+
45
+ First step, while initializing the UserSerializer, we will create a **Serialization Descriptor** for this class.
46
+ Serialization Descriptor's goal is to answer those questions:
47
+
48
+ * Which fields do we have? In our case, `:age`, `:email`
49
+ * Which method fields do we have? In our case `:name`
50
+ * Which associations do we have (and their serialization descriptors)
51
+
52
+ The serialization description is also responsible for filtering the attributes (`only` \ `except`).
53
+
54
+ 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.
55
+
56
+ In C land, we take the `user` object and the serialization descriptor, and start the serialization process which is separated to 4 parts:
57
+
58
+ * Serializing Fields - looping through serialization descriptor's `fields` and read them from the ActiveRecord object (see `Type Casting`) and write them to the writer.
59
+ * Serializing Method Fields - creating (a cached) serializer instance, setting its `@object` and `@context`, calling all the method fields and writing them to the writer.
60
+ * Serializing associations — this is simple, once we have fields + method fields, we just repeat the process.
61
+
62
+ Once this is finished, we have nice JSON string.
63
+ Now let's dig deeper.
64
+
65
+ ## Interesting parts
66
+
67
+ ### Oj::StringWriter
68
+
69
+ If you read the code of ActiveRecord serialization code in Ruby, you will observe this flow:
70
+
71
+ 1. Get an array of ActiveRecord objects (`User.all` for example)
72
+ 2. Build new array of hashes where each hash is `User` with the attributes we selected
73
+ 3. The JSON serializer, takes this array of hashes and loop them, and converts it to JSON string
74
+
75
+ This entire process is expensive in terms of Memory & CPU, and this where the combination of Panko and Oj::StringWriter really shines.
76
+
77
+ In Panko, the serialization process of the above is:
78
+
79
+ 1. Get an array of ActiveRecord objects (`User.all` for example)
80
+ 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.
81
+ 3. Get from `Oj::StringWriter` the completed JSON string — which is a no-op, since `Oj::StringWriter` already built the string.
82
+
83
+ ### Figuring out the metadata, ahead of time.
84
+
85
+ Another observation I noticed in the ruby serializers is that they ask and do a lot in a serialization loop:
86
+
87
+ * Is this field a method? is it a property?
88
+ * Which fields and associations do I need for the serializer to consider the `only` and `except` options
89
+ * What is the serializer of this has_one association?
90
+
91
+ Panko tries to ask the bare minimum in serialization by building `Serialization Descriptor` for each serialization and caching it.
92
+
93
+ 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`)
94
+
95
+
96
+ ### Type Casting
97
+
98
+ This is the final part, which helped yield most of the performance improvements.
99
+ In ActiveRecord, when we read a value of attribute, it does type casting of the DB value to its real ruby type.
100
+
101
+ For example, time strings are converted to Time objects, Strings are duplicated, and Integers are converts from their values to Number.
102
+
103
+ 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".
104
+
105
+ 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.
106
+
107
+ What Panko does is that if we have ActiveRecord type string, we won't duplicate it.
108
+ If we have an integer string value, we will convert it to an integer, and the same goes for other types.
109
+
110
+ All of these conversions are done in C, which of course yields a big performance improvement.
111
+
112
+ #### Time type casting
113
+ While you read Panko source code, you will encounter the time type casting and immediately you will have a "WTF?" moment.
114
+
115
+ 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.
116
+
117
+ The time type casting works as follows:
118
+
119
+ * If it's a string that ends with `Z`, and the strings matches the UTC ISO8601 regex, then we just return the string.
120
+ * 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).
121
+ * If it's none of the above, I will let ActiveRecord type casting do it's magic.
122
+
@@ -0,0 +1,4 @@
1
+ {
2
+ "docs": "docs",
3
+ "github": "yosiat/panko_serializer"
4
+ }
@@ -0,0 +1,47 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ To install Panko, all you need is to add it to your Gemfile:
6
+
7
+ ```ruby
8
+ gem "panko_serializer"
9
+ ```
10
+
11
+ Then, install it on the command line:
12
+
13
+ ```
14
+ > bundle install
15
+ ```
16
+
17
+
18
+ ## Creating your first serializer
19
+
20
+ Let's create serializer and use it inside Rails controller.
21
+
22
+ ```ruby
23
+ class PostSerializer < Panko::Serializer
24
+ attributes :title
25
+ end
26
+
27
+ class UserSerializer < Panko::Serializer
28
+ attributes :id, :name, :age
29
+
30
+ has_many :posts, serializer: PostSerializer
31
+ end
32
+ ```
33
+
34
+ As you can see, defining serializers is simple and resembles ActiveModelSerializers 0.9,
35
+ To utilize the `UserSerializer` inside a Rails controller and serialize some users, all we need to do is:
36
+
37
+ ```ruby
38
+ class UsersController < ApplicationController
39
+ def index
40
+ users = User.includes(:posts).all
41
+ render json: Panko::ArraySerializer.new(users, each_serializer: UserSerializer).to_json
42
+ end
43
+ end
44
+ ```
45
+
46
+ And voila, we have endpoint which serialize users using Panko!
47
+
@@ -0,0 +1,35 @@
1
+ # Performance
2
+
3
+ The performance of Panko is measured using microbenchmarks and load testing.
4
+
5
+ ## Microbenchmarks
6
+
7
+ The following microbenchmarks are run on MacBook Pro (Retina, 15-inch, Mid 2015), Ruby 2.4 with Rails 4.2
8
+ demonstrating the performance of ActiveModelSerializers 0.9 and Panko 0.3.3
9
+
10
+
11
+ Benchmark | AMS ip/s | Panko ip/s
12
+ ---------------------------------------|----------|-----------------
13
+ | Simple_Posts_2300 | 25.81 | 135.29 |
14
+ | Simple_Posts_50 | 1,248.39 | 6,518.68 |
15
+ | HasOne_Posts_2300 | 11.33 | 73.42 |
16
+ | HasOne_Posts_50 | 523.14 | 4,985.41 |
17
+
18
+ > The corresponding benchmarks are `benchmarks/bm_active_model_serializers.rb` and `benchmarks/bm_panko_json.rb`
19
+
20
+
21
+ ## Real-world benchmark
22
+
23
+ The real-world benchmark here is endpoint which serializes 7,884 entries with 48 attributes and no associations.
24
+ The benchmark took place in environment that simulates production environment and run using `wrk` from machine on the same cluster.
25
+
26
+
27
+ Metric | AMS | Panko
28
+ ------------ |------------ | -------------
29
+ Avg Response Time| 4.89s| 1.48s|
30
+ Max Response Time| 5.42s| 1.83s|
31
+ 99th Response Time| 5.42s| 1.74s|
32
+ Total Requests| 61| 202|
33
+
34
+
35
+ *Thanks to [Bringg](https://www.bringg.com) for providing the infrastructrue for the benchmarks*
@@ -0,0 +1,53 @@
1
+ # Response
2
+
3
+ Let's say you have some JSON payload which can is constructed using Panko serialization result,
4
+ like this:
5
+
6
+ ```ruby
7
+ class PostsController < ApplicationController
8
+ def index
9
+ posts = Post.all
10
+ render json: {
11
+ success: true,
12
+ total_count: posts.count,
13
+ posts: Panko::ArraySerializer.new(posts, each_serializer: PostSerializer).to_json
14
+ }
15
+ end
16
+ end
17
+ ```
18
+
19
+ The output of the above will be json string (for `posts`) inside json string and this were `Panko::Response` shines.
20
+
21
+ ```ruby
22
+ class PostsController < ApplicationController
23
+ def index
24
+ posts = Post.all
25
+ render json: Panko::Response.new(
26
+ success: true,
27
+ total_count: posts.count,
28
+ posts: Panko::ArraySerializer.new(posts, each_serializer: PostSerializer)
29
+ )
30
+ end
31
+ end
32
+ ```
33
+
34
+ And everything will work as expected!
35
+
36
+ ## JsonValue
37
+
38
+ Let's take the above example further, we serialized the posts and cached it as JSON string in our Cache.
39
+ Now, you can wrap the cached value with `Panko::JsonValue`, like here -
40
+
41
+ ```ruby
42
+ class PostsController < ApplicationController
43
+ def index
44
+ posts = Cache.get("/posts")
45
+
46
+ render json: Panko::Response.new(
47
+ success: true,
48
+ total_count: posts.count,
49
+ posts: Panko::JsonValue.from(posts)
50
+ )
51
+ end
52
+ end
53
+ ```
@@ -56,6 +56,11 @@ VALUE association_name_sym_ref(VALUE self) {
56
56
  return association->name_sym;
57
57
  }
58
58
 
59
+ VALUE association_name_str_ref(VALUE self) {
60
+ Association association = (Association)DATA_PTR(self);
61
+ return association->name_str;
62
+ }
63
+
59
64
  VALUE association_descriptor_ref(VALUE self) {
60
65
  Association association = (Association)DATA_PTR(self);
61
66
  return association->rb_descriptor;
@@ -76,6 +81,7 @@ void panko_init_association(VALUE mPanko) {
76
81
  rb_define_module_function(cAssociation, "new", association_new, -1);
77
82
 
78
83
  rb_define_method(cAssociation, "name_sym", association_name_sym_ref, 0);
84
+ rb_define_method(cAssociation, "name_str", association_name_str_ref, 0);
79
85
  rb_define_method(cAssociation, "descriptor", association_descriptor_ref, 0);
80
86
  rb_define_method(cAssociation, "descriptor=", association_decriptor_aset, 1);
81
87
  }
@@ -20,9 +20,7 @@ module Panko
20
20
 
21
21
  backend.type = descriptor.type
22
22
 
23
- backend.attributes = descriptor.attributes.map do |attr|
24
- Attribute.create(attr.name, alias_name: attr.alias_name)
25
- end
23
+ backend.attributes = descriptor.attributes.dup
26
24
 
27
25
  backend.method_fields = descriptor.method_fields.dup
28
26
 
@@ -30,21 +28,8 @@ module Panko
30
28
  backend.serializer = descriptor.serializer.reset
31
29
  end
32
30
 
33
- backend.has_many_associations = descriptor.has_many_associations.map do |assoc|
34
- Panko::Association.new(
35
- assoc.name_sym,
36
- assoc.name_sym.to_s,
37
- Panko::SerializationDescriptor.duplicate(assoc.descriptor)
38
- )
39
- end
40
-
41
- backend.has_one_associations = descriptor.has_one_associations.map do |assoc|
42
- Panko::Association.new(
43
- assoc.name_sym,
44
- assoc.name_sym.to_s,
45
- Panko::SerializationDescriptor.duplicate(assoc.descriptor)
46
- )
47
- end
31
+ backend.has_many_associations = descriptor.has_many_associations.dup
32
+ backend.has_one_associations = descriptor.has_one_associations.dup
48
33
 
49
34
  backend
50
35
  end
@@ -58,7 +43,8 @@ module Panko
58
43
  attributes_only_filters, associations_only_filters = resolve_filters(options, :only)
59
44
  attributes_except_filters, associations_except_filters = resolve_filters(options, :except)
60
45
 
61
- apply_attribute_filters!(
46
+ self.attributes = apply_attribute_filters(
47
+ self.attributes,
62
48
  attributes_only_filters,
63
49
  attributes_except_filters
64
50
  )
@@ -120,10 +106,17 @@ module Panko
120
106
  filters = {}
121
107
  filters[:only] = only_filter unless only_filter.nil?
122
108
  filters[:except] = except_filter unless except_filter.nil?
123
- descriptor.apply_filters(filters) unless filters.empty?
109
+
110
+ unless filters.empty?
111
+ next Panko::Association.new(
112
+ name,
113
+ association.name_str,
114
+ Panko::SerializationDescriptor.build(descriptor.type, filters)
115
+ )
116
+ end
124
117
 
125
118
  association
126
- end.compact
119
+ end
127
120
  end
128
121
 
129
122
  def resolve_filters(options, filter)
@@ -152,9 +145,9 @@ module Panko
152
145
  fields
153
146
  end
154
147
 
155
- def apply_attribute_filters!(only, except)
148
+ def apply_attribute_filters(attributes, only, except)
156
149
  unless only.empty?
157
- self.attributes.select! do |attribute|
150
+ attributes = attributes.select do |attribute|
158
151
  name_to_check = attribute.name
159
152
  name_to_check = attribute.alias_name unless attribute.alias_name.nil?
160
153
 
@@ -163,13 +156,15 @@ module Panko
163
156
  end
164
157
 
165
158
  unless except.empty?
166
- self.attributes.reject! do |attribute|
159
+ attributes = attributes.reject do |attribute|
167
160
  name_to_check = attribute.name
168
161
  name_to_check = attribute.alias_name unless attribute.alias_name.nil?
169
162
 
170
163
  except.include?(name_to_check.to_sym)
171
164
  end
172
165
  end
166
+
167
+ attributes
173
168
  end
174
169
  end
175
170
  end
@@ -36,8 +36,13 @@ module Panko
36
36
  @_descriptor.method_fields << method
37
37
  end
38
38
 
39
- def has_one(name, options)
39
+ def has_one(name, options = {})
40
40
  serializer_const = options[:serializer]
41
+ serializer_const = Panko::SerializerResolver.resolve(name.to_s) if serializer_const.nil?
42
+
43
+ if serializer_const.nil?
44
+ raise "Can't find serializer for #{self.name}.#{name} has_one relationship."
45
+ end
41
46
 
42
47
  @_descriptor.has_one_associations << Panko::Association.new(
43
48
  name,
@@ -46,8 +51,13 @@ module Panko
46
51
  )
47
52
  end
48
53
 
49
- def has_many(name, options)
54
+ def has_many(name, options = {})
50
55
  serializer_const = options[:serializer] || options[:each_serializer]
56
+ serializer_const = Panko::SerializerResolver.resolve(name.to_s) if serializer_const.nil?
57
+
58
+ if serializer_const.nil?
59
+ raise "Can't find serializer for #{self.name}.#{name} has_many relationship."
60
+ end
51
61
 
52
62
  @_descriptor.has_many_associations << Panko::Association.new(
53
63
  name,
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require "byebug"
3
+
4
+ class Panko::SerializerResolver
5
+
6
+ def self.resolve(name)
7
+ serializer_name = "#{name.singularize.camelize}Serializer"
8
+ serializer_const = self.safe_const_get(serializer_name)
9
+
10
+ return nil if serializer_const.nil?
11
+ return nil unless self.is_serializer(serializer_const)
12
+
13
+ serializer_const
14
+ end
15
+
16
+
17
+ private
18
+
19
+ def self.is_serializer(const)
20
+ const < Panko::Serializer
21
+ end
22
+
23
+ def self.safe_const_get(name)
24
+ Object.const_get(name)
25
+ rescue NameError
26
+ nil
27
+ end
28
+
29
+ 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.2"
3
+ VERSION = "0.3.3"
4
4
  end
@@ -4,6 +4,7 @@ require "panko/attribute"
4
4
  require "panko/serializer"
5
5
  require "panko/array_serializer"
6
6
  require "panko/response"
7
+ require "panko/serializer_resolver"
7
8
 
8
9
 
9
10
  # C Extension
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.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yosi Attias
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-04 00:00:00.000000000 Z
11
+ date: 2017-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -97,7 +97,6 @@ files:
97
97
  - LICENSE.txt
98
98
  - README.md
99
99
  - Rakefile
100
- - _config.yml
101
100
  - benchmarks/BENCHMARKS.md
102
101
  - benchmarks/allocs.rb
103
102
  - benchmarks/app.rb
@@ -107,12 +106,21 @@ files:
107
106
  - benchmarks/bm_panko_json.rb
108
107
  - benchmarks/bm_panko_object.rb
109
108
  - benchmarks/bm_serialization_descriptor.rb
109
+ - benchmarks/bm_serializer_resolver.rb
110
110
  - benchmarks/profile.rb
111
111
  - benchmarks/sanity.rb
112
112
  - benchmarks/setup.rb
113
113
  - benchmarks/type_casts/bm_active_record.rb
114
114
  - benchmarks/type_casts/bm_panko.rb
115
115
  - benchmarks/type_casts/support.rb
116
+ - docs/README.md
117
+ - docs/associations.md
118
+ - docs/attributes.md
119
+ - docs/design-choices.md
120
+ - docs/docpress.json
121
+ - docs/getting-started.md
122
+ - docs/performance.md
123
+ - docs/response-bag.md
116
124
  - ext/panko_serializer/association.c
117
125
  - ext/panko_serializer/association.h
118
126
  - ext/panko_serializer/attribute.c
@@ -133,6 +141,7 @@ files:
133
141
  - lib/panko/response.rb
134
142
  - lib/panko/serialization_descriptor.rb
135
143
  - lib/panko/serializer.rb
144
+ - lib/panko/serializer_resolver.rb
136
145
  - lib/panko/version.rb
137
146
  - lib/panko_serializer.rb
138
147
  - panko_serializer.gemspec
@@ -156,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
165
  version: '0'
157
166
  requirements: []
158
167
  rubyforge_project:
159
- rubygems_version: 2.6.14
168
+ rubygems_version: 2.7.3
160
169
  signing_key:
161
170
  specification_version: 4
162
171
  summary: Fast serialization for ActiveModel
data/_config.yml DELETED
@@ -1 +0,0 @@
1
- theme: jekyll-theme-cayman