fast_serializer 1.1.1 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/MIT_LICENSE +1 -1
- data/README.md +31 -1
- data/VERSION +1 -1
- data/fast_serializer.gemspec +29 -19
- data/lib/fast_serializer/array_serializer.rb +13 -5
- data/lib/fast_serializer/cache/active_support_cache.rb +8 -6
- data/lib/fast_serializer/cache.rb +14 -0
- data/lib/fast_serializer/serialization_context.rb +17 -2
- data/lib/fast_serializer/serialized_field.rb +32 -11
- data/lib/fast_serializer/serializer.rb +56 -29
- data/lib/fast_serializer.rb +14 -12
- metadata +18 -67
- data/.gitignore +0 -17
- data/HISTORY.md +0 -25
- data/Rakefile +0 -18
- data/spec/array_serializer_spec.rb +0 -67
- data/spec/cache/active_support_cache_spec.rb +0 -24
- data/spec/fast_serializer_spec.rb +0 -40
- data/spec/serialization_context_spec.rb +0 -32
- data/spec/serialized_field_spec.rb +0 -71
- data/spec/serializer_spec.rb +0 -177
- data/spec/spec_helper.rb +0 -18
- data/spec/support/test_models.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7cb104ff4ece3439d2fcd7b6276866359a9ead6a511765fbef327d4bd33e168
|
4
|
+
data.tar.gz: dfff11c222e5cf0dc6352097fc3de8603f437fdc92636030c0aef4576b697445
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 891c9cfde95aa08e4e2f73646f3cde78c4a1730673e9e74d518356dda869ff4848d1c9fc0d81bbcda936147faedd5e32775870a006e99679d34918f554252c70
|
7
|
+
data.tar.gz: 68814c806dd3b6a3b9e519b8ed5bf8c948ca2d12cb143a64a25c71aaac93690a66f5d85ea2ff71953d1bade1479f619b29c400ebab8137de947fb55053928d21
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## 1.1.3
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Optimize object shapes for the Ruby interpreter by declaring instance variables in constructors.
|
11
|
+
|
12
|
+
## 1.1.2
|
13
|
+
|
14
|
+
### Added
|
15
|
+
- Sanity check for unsupported options
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
- Handle converting ActiveSupport::TimeWithZone to a Time so it can be better dumped to non-JSON formats.
|
19
|
+
|
20
|
+
## 1.1.1
|
21
|
+
|
22
|
+
### Added
|
23
|
+
- Add `array` class method to serializers.
|
24
|
+
|
25
|
+
## 1.1.0
|
26
|
+
|
27
|
+
### Added
|
28
|
+
- Add helper method for scope option.
|
29
|
+
- Pass serialization options to child serializers.
|
30
|
+
- Add `if` option to conditionally include fields.
|
31
|
+
- Better cache keys handling for more complex objects.
|
32
|
+
|
33
|
+
## 1.0.2
|
34
|
+
|
35
|
+
### Added
|
36
|
+
- Better integration with ActiveSupport caching.
|
37
|
+
|
38
|
+
## 1.0.1
|
39
|
+
|
40
|
+
### Fixed
|
41
|
+
- Compatibility with change to fetch_multi in ActiveSupport 4.2.
|
42
|
+
|
43
|
+
## 1.0.0
|
44
|
+
|
45
|
+
### Added
|
46
|
+
- Initial release
|
data/MIT_LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
[![Continuous Integration](https://github.com/bdurand/fast_serializer/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/fast_serializer/actions/workflows/continuous_integration.yml)
|
2
|
+
[![Regression Test](https://github.com/bdurand/fast_serializer/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/fast_serializer/actions/workflows/regression_test.yml)
|
3
|
+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
4
|
+
|
1
5
|
This gem provides a highly optimized framework for serializing Ruby objects into hashes suitable for serialization to some other format (i.e. JSON). It provides many of the same features as other serialization frameworks like active_model_serializers, but it is designed to emphasize code efficiency over feature set and syntactic surgar.
|
2
6
|
|
3
7
|
## Examples
|
@@ -229,7 +233,33 @@ You can also use the `array` helper class method on a serializer to do the same
|
|
229
233
|
PersonSerializer.array([a, b, c, d])
|
230
234
|
```
|
231
235
|
|
232
|
-
|
233
236
|
## Performance
|
234
237
|
|
235
238
|
Your mileage may vary. In many cases the performance of the serialization code doesn't particularly matter and this gem performs just about as well as other solutions. However, if you do have high throughput API or can utilize the caching features or have heavily nested models in your JSON responses, then the performance increase may be noticeable.
|
239
|
+
## Installation
|
240
|
+
|
241
|
+
Add this line to your application's Gemfile:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
gem 'fast_serializer'
|
245
|
+
```
|
246
|
+
|
247
|
+
And then execute:
|
248
|
+
```bash
|
249
|
+
$ bundle
|
250
|
+
```
|
251
|
+
|
252
|
+
Or install it yourself as:
|
253
|
+
```bash
|
254
|
+
$ gem install fast_serializer
|
255
|
+
```
|
256
|
+
|
257
|
+
## Contributing
|
258
|
+
|
259
|
+
Open a pull request on GitHub.
|
260
|
+
|
261
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
262
|
+
|
263
|
+
## License
|
264
|
+
|
265
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.3
|
data/fast_serializer.gemspec
CHANGED
@@ -1,24 +1,34 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
|
5
1
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
10
|
-
|
11
|
-
spec.summary
|
12
|
-
spec.homepage
|
13
|
-
spec.license
|
2
|
+
spec.name = "fast_serializer"
|
3
|
+
spec.version = File.read(File.expand_path("VERSION", __dir__)).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = "Super fast object serialization for API's combining a simple DSL with many optimizations under the hood."
|
8
|
+
spec.homepage = "https://github.com/bdurand/fast_serializer"
|
9
|
+
spec.license = "MIT"
|
10
|
+
|
11
|
+
# Specify which files should be added to the gem when it is released.
|
12
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
+
ignore_files = %w[
|
14
|
+
.
|
15
|
+
Appraisals
|
16
|
+
Gemfile
|
17
|
+
Gemfile.lock
|
18
|
+
Rakefile
|
19
|
+
bin/
|
20
|
+
gemfiles/
|
21
|
+
spec/
|
22
|
+
]
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
25
|
+
end
|
14
26
|
|
15
|
-
spec.files = `git ls-files`.split($/)
|
16
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
27
|
spec.require_paths = ["lib"]
|
19
28
|
|
20
|
-
spec.
|
21
|
-
|
22
|
-
spec.
|
23
|
-
|
29
|
+
spec.required_ruby_version = ">= 2.5"
|
30
|
+
|
31
|
+
spec.add_dependency("redis")
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler"
|
24
34
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FastSerializer
|
2
4
|
# Serializer implementation for serializing an array of objects.
|
3
5
|
# This class allows taking advantage of a single SerializationContext
|
@@ -8,9 +10,11 @@ module FastSerializer
|
|
8
10
|
serialize :array
|
9
11
|
|
10
12
|
def initialize(object, options = nil)
|
13
|
+
@_array = nil
|
11
14
|
super(Array(object), options)
|
12
15
|
end
|
13
16
|
|
17
|
+
# @return [String]
|
14
18
|
def cache_key
|
15
19
|
if option(:serializer)
|
16
20
|
array.collect(&:cache_key)
|
@@ -19,6 +23,7 @@ module FastSerializer
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
26
|
+
# @return [Boolean]
|
22
27
|
def cacheable?
|
23
28
|
if option(:cacheable) || self.class.cacheable?
|
24
29
|
true
|
@@ -29,6 +34,7 @@ module FastSerializer
|
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
37
|
+
# @return [Numeric, Boolean]
|
32
38
|
def cache_ttl
|
33
39
|
if option(:cache_ttl)
|
34
40
|
true
|
@@ -39,6 +45,7 @@ module FastSerializer
|
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
48
|
+
# @return [FastSerializer::Cache, Boolean]
|
42
49
|
def cache
|
43
50
|
if option(:cache)
|
44
51
|
true
|
@@ -49,6 +56,7 @@ module FastSerializer
|
|
49
56
|
end
|
50
57
|
end
|
51
58
|
|
59
|
+
# @return [Hash]
|
52
60
|
def as_json(*args)
|
53
61
|
if array.nil?
|
54
62
|
nil
|
@@ -61,14 +69,14 @@ module FastSerializer
|
|
61
69
|
|
62
70
|
undef :to_hash
|
63
71
|
undef :to_h
|
64
|
-
|
72
|
+
alias_method :to_a, :as_json
|
65
73
|
|
66
74
|
protected
|
67
75
|
|
68
76
|
def load_from_cache
|
69
77
|
if cache
|
70
|
-
values = cache.fetch_all(array, cache_ttl){|serializer| serializer.as_json}
|
71
|
-
{:
|
78
|
+
values = cache.fetch_all(array, cache_ttl) { |serializer| serializer.as_json }
|
79
|
+
{array: values}
|
72
80
|
else
|
73
81
|
load_hash
|
74
82
|
end
|
@@ -77,11 +85,11 @@ module FastSerializer
|
|
77
85
|
private
|
78
86
|
|
79
87
|
def array
|
80
|
-
|
88
|
+
if @_array.nil?
|
81
89
|
serializer = option(:serializer)
|
82
90
|
if serializer
|
83
91
|
serializer_options = option(:serializer_options)
|
84
|
-
@_array = object.collect{|obj| serializer.new(obj, serializer_options)}
|
92
|
+
@_array = object.collect { |obj| serializer.new(obj, serializer_options) }
|
85
93
|
else
|
86
94
|
@_array = object
|
87
95
|
end
|
@@ -1,22 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FastSerializer
|
2
4
|
# ActiveSupport compatible cache implementation.
|
3
5
|
class Cache::ActiveSupportCache < Cache
|
4
6
|
attr_reader :cache
|
5
|
-
|
7
|
+
|
6
8
|
def initialize(cache)
|
7
9
|
@cache = cache
|
8
10
|
end
|
9
|
-
|
11
|
+
|
10
12
|
def fetch(serializer, ttl)
|
11
|
-
@cache.fetch(serializer.cache_key, :
|
13
|
+
@cache.fetch(serializer.cache_key, expires_in: ttl) do
|
12
14
|
yield(serializer)
|
13
15
|
end
|
14
16
|
end
|
15
|
-
|
17
|
+
|
16
18
|
def fetch_all(serializers, ttl)
|
17
|
-
results = @cache.fetch_multi(*serializers){|serializer| yield(serializer)}
|
19
|
+
results = @cache.fetch_multi(*serializers) { |serializer| yield(serializer) }
|
18
20
|
if results.is_a?(Hash)
|
19
|
-
serializers.collect{|serializer| results[serializer]}
|
21
|
+
serializers.collect { |serializer| results[serializer] }
|
20
22
|
else
|
21
23
|
results
|
22
24
|
end
|
@@ -1,7 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FastSerializer
|
2
4
|
# Base class for cache implementations for storing cacheable serializers.
|
3
5
|
# Implementations must implement the +fetch+ method.
|
4
6
|
class Cache
|
7
|
+
# Fetch a serialized value from the cache. If the value is not cached, the
|
8
|
+
# block will be yielded to to generate the value.
|
9
|
+
#
|
10
|
+
# @param serializer [FastSerializer::Serializer] The serializer to fetch the value for.
|
11
|
+
# @param ttl [Numeric] The time to live for the cached value.
|
12
|
+
# @yieldparam serializer [FastSerializer::Serializer] The serializer to generate the value for.
|
13
|
+
# @return [Object] The serialized value.
|
5
14
|
def fetch(serializer, ttl, &block)
|
6
15
|
raise NotImplementedError
|
7
16
|
end
|
@@ -11,6 +20,11 @@ module FastSerializer
|
|
11
20
|
# if the cache can return multiple values at once.
|
12
21
|
#
|
13
22
|
# The block to this method will be yielded to with each uncached serializer.
|
23
|
+
#
|
24
|
+
# @param serializers [Array<FastSerializer::Serializer>] The serializers to fetch the values for.
|
25
|
+
# @param ttl [Numeric] The time to live for the cached values.
|
26
|
+
# @yieldparam serializer [FastSerializer::Serializer] A serializer to generate the value for.
|
27
|
+
# @return [Array<Object>] The serialized values.
|
14
28
|
def fetch_all(serializers, ttl)
|
15
29
|
serializers.collect do |serializer|
|
16
30
|
fetch(serializer, ttl) do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FastSerializer
|
2
4
|
# This class provides a context for creating serializers that allows
|
3
5
|
# duplicate serializers to be re-used within the context. This then
|
@@ -7,6 +9,8 @@ module FastSerializer
|
|
7
9
|
# Use a context or create one for use within a block. Any serializers
|
8
10
|
# based on the same object with the same options within the block will be
|
9
11
|
# re-used instead of creating duplicates.
|
12
|
+
#
|
13
|
+
# @return [Object] The return value of the block.
|
10
14
|
def use
|
11
15
|
if Thread.current[:fast_serializer_context]
|
12
16
|
yield
|
@@ -21,6 +25,8 @@ module FastSerializer
|
|
21
25
|
end
|
22
26
|
|
23
27
|
# Return the current context or nil if none is in use.
|
28
|
+
#
|
29
|
+
# @return [FastSerializer::SerializationContext, nil]
|
24
30
|
def current
|
25
31
|
Thread.current[:fast_serializer_context]
|
26
32
|
end
|
@@ -31,9 +37,14 @@ module FastSerializer
|
|
31
37
|
@references = nil
|
32
38
|
end
|
33
39
|
|
34
|
-
# Returns a serializer from the context cache if
|
40
|
+
# Returns a serializer from the context cache if one has already
|
35
41
|
# been created. Otherwise creates the serializer and adds it to the
|
36
42
|
# cache.
|
43
|
+
#
|
44
|
+
# @param serializer_class [Class] The serializer class to create.
|
45
|
+
# @param object [Object] The object to serialize.
|
46
|
+
# @param options [Hash] The options to pass to the serializer.
|
47
|
+
# @return [FastSerializer::Serializer] The serializer.
|
37
48
|
def load(serializer_class, object, options = nil)
|
38
49
|
key = [serializer_class, object, options]
|
39
50
|
serializer = nil
|
@@ -44,7 +55,7 @@ module FastSerializer
|
|
44
55
|
unless serializer
|
45
56
|
serializer = serializer_class.allocate
|
46
57
|
serializer.send(:initialize, object, options)
|
47
|
-
@cache
|
58
|
+
@cache = {}
|
48
59
|
@cache[key] = serializer
|
49
60
|
end
|
50
61
|
|
@@ -52,6 +63,10 @@ module FastSerializer
|
|
52
63
|
end
|
53
64
|
|
54
65
|
# Maintain reference stack to avoid circular references.
|
66
|
+
#
|
67
|
+
# @param object [Object] The object to check for circular references.
|
68
|
+
# @yield The block to execute.
|
69
|
+
# @return The return value of the block.
|
55
70
|
def with_reference(object)
|
56
71
|
if @references
|
57
72
|
raise CircularReferenceError.new(object) if @references.include?(object)
|
@@ -1,10 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FastSerializer
|
2
4
|
# Data structure used internally for maintaining a field to be serialized.
|
3
5
|
class SerializedField
|
4
6
|
attr_reader :name, :condition
|
5
7
|
|
8
|
+
# Create a new serialized field.
|
9
|
+
#
|
10
|
+
# @param name [Symbol] the name of the field
|
11
|
+
# @param optional [Boolean] whether the field is optional
|
12
|
+
# @param serializer [Class] the serializer to use for the field
|
13
|
+
# @param serializer_options [Hash] the options to pass to the serializer
|
14
|
+
# @param enumerable [Boolean] whether the field is enumerable
|
15
|
+
# @param condition [Proc] a condition to determine whether the field should be serialized
|
6
16
|
def initialize(name, optional: false, serializer: nil, serializer_options: nil, enumerable: false, condition: nil)
|
7
|
-
@name = name
|
17
|
+
@name = name.to_sym
|
8
18
|
@optional = !!optional
|
9
19
|
@condition = condition
|
10
20
|
if serializer
|
@@ -14,22 +24,27 @@ module FastSerializer
|
|
14
24
|
end
|
15
25
|
end
|
16
26
|
|
27
|
+
# @return [Boolean] true if the field is optional
|
17
28
|
def optional?
|
18
29
|
@optional
|
19
30
|
end
|
20
31
|
|
21
32
|
# Wrap a value in the serializer if one has been set. Otherwise just returns the raw value.
|
33
|
+
#
|
34
|
+
# @param value [Object] the value to serialize
|
35
|
+
# @param options [Hash] the options to pass to the serializer
|
36
|
+
# @return [Object] the serialized value
|
22
37
|
def serialize(value, options = nil)
|
23
38
|
if value && @serializer
|
24
39
|
serializer = nil
|
25
|
-
if @enumerable
|
26
|
-
|
40
|
+
serializer = if @enumerable
|
41
|
+
ArraySerializer.new(value, serializer: @serializer, serializer_options: serializer_options(options))
|
27
42
|
else
|
28
|
-
|
43
|
+
@serializer.new(value, serializer_options(options))
|
29
44
|
end
|
30
45
|
context = SerializationContext.current
|
31
46
|
if context
|
32
|
-
context.with_reference(value){ serializer.as_json }
|
47
|
+
context.with_reference(value) { serializer.as_json }
|
33
48
|
else
|
34
49
|
serializer.as_json
|
35
50
|
end
|
@@ -56,10 +71,10 @@ module FastSerializer
|
|
56
71
|
retval = {}
|
57
72
|
merge_hash.each do |key, merge_value|
|
58
73
|
value = hash[key]
|
59
|
-
if value.is_a?(Hash) && merge_value.is_a?(Hash)
|
60
|
-
|
74
|
+
retval[key] = if value.is_a?(Hash) && merge_value.is_a?(Hash)
|
75
|
+
deep_merge(value, merge_value)
|
61
76
|
else
|
62
|
-
|
77
|
+
merge_value
|
63
78
|
end
|
64
79
|
end
|
65
80
|
retval
|
@@ -67,8 +82,14 @@ module FastSerializer
|
|
67
82
|
|
68
83
|
# Convert the value to primitive data types: string, number, boolean, symbol, time, date, array, hash.
|
69
84
|
def serialize_value(value)
|
70
|
-
if value.is_a?(String) || value.is_a?(Numeric) || value
|
85
|
+
if value.is_a?(String) || value.is_a?(Numeric) || value.nil? || value == true || value == false || value.is_a?(Symbol)
|
71
86
|
value
|
87
|
+
elsif value.is_a?(Time) || value.is_a?(Date)
|
88
|
+
if defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone)
|
89
|
+
value.to_time
|
90
|
+
else
|
91
|
+
value
|
92
|
+
end
|
72
93
|
elsif value.is_a?(Hash)
|
73
94
|
serialize_hash(value)
|
74
95
|
elsif value.is_a?(Enumerable)
|
@@ -89,7 +110,7 @@ module FastSerializer
|
|
89
110
|
value.each do |k, v|
|
90
111
|
val = serialize_value(v)
|
91
112
|
if val.object_id != v.object_id
|
92
|
-
hash
|
113
|
+
hash ||= value.dup
|
93
114
|
hash[k] = val
|
94
115
|
end
|
95
116
|
end
|
@@ -101,7 +122,7 @@ module FastSerializer
|
|
101
122
|
value.each_with_index do |v, i|
|
102
123
|
val = serialize_value(v)
|
103
124
|
if val.object_id != v.object_id
|
104
|
-
array
|
125
|
+
array ||= value.dup
|
105
126
|
array[i] = val
|
106
127
|
end
|
107
128
|
end
|