alba 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/perf.yml +21 -0
- data/.rubocop.yml +7 -7
- data/CHANGELOG.md +9 -0
- data/Gemfile +3 -2
- data/README.md +111 -3
- data/alba.gemspec +1 -1
- data/gemfiles/all.gemfile +1 -1
- data/gemfiles/without_active_support.gemfile +1 -1
- data/gemfiles/without_oj.gemfile +1 -1
- data/lib/alba.rb +48 -18
- data/lib/alba/association.rb +6 -17
- data/lib/alba/default_inflector.rb +3 -3
- data/lib/alba/key_transform_factory.rb +1 -1
- data/lib/alba/resource.rb +108 -60
- data/lib/alba/typed_attribute.rb +3 -6
- data/lib/alba/version.rb +1 -1
- data/script/perf_check.rb +174 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01d43d93e589197104d9ee166e6a9dcf727fd204524b52145d7d93b45ba79cd2
|
4
|
+
data.tar.gz: 023f2e0f8ff17bc78a01e2dc1eb1f98d9c41b0cd353e311f350c2704a9ffa602
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99992932a0f6a3d589e77e1b7dc4225be2e083a15320fa00a96f07a4cd9bbe9803454b94ecae409d439809a53225fb54ce283fcca074c0aed479e7d8b808d545
|
7
|
+
data.tar.gz: c77e99af9cf98781a2e961817b5c60e31fe178e6eef7bb8bd77b1af199043b8acd8395a27dd7cb10680ae82d28ec57e98106fef42f57055cb23752d63605a7b9
|
@@ -0,0 +1,21 @@
|
|
1
|
+
name: Performance Check
|
2
|
+
|
3
|
+
on: [pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
ruby: [2.5, 2.6, 2.7, 3.0]
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v2
|
14
|
+
- name: Set up Ruby
|
15
|
+
uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: ${{ matrix.ruby }}
|
18
|
+
bundler-cache: true
|
19
|
+
- name: Run benchmark
|
20
|
+
run: |
|
21
|
+
ruby script/perf_check.rb
|
data/.rubocop.yml
CHANGED
@@ -13,12 +13,13 @@ AllCops:
|
|
13
13
|
- 'Rakefile'
|
14
14
|
- 'alba.gemspec'
|
15
15
|
- 'benchmark/**/*.rb'
|
16
|
+
- 'script/**/*.rb'
|
16
17
|
NewCops: enable
|
17
18
|
EnabledByDefault: true
|
18
19
|
TargetRubyVersion: 2.5
|
19
20
|
|
20
|
-
#
|
21
|
-
Bundler/
|
21
|
+
# Items in Gemfile is dev dependencies and we don't have to specify versions.
|
22
|
+
Bundler/GemVersion:
|
22
23
|
Enabled: false
|
23
24
|
|
24
25
|
# We'd like to write something like:
|
@@ -44,9 +45,6 @@ Metrics:
|
|
44
45
|
Exclude:
|
45
46
|
- 'test/**/*.rb'
|
46
47
|
|
47
|
-
Metrics/MethodLength:
|
48
|
-
Max: 15
|
49
|
-
|
50
48
|
# `Resource` module is a core module and its length tends to be long...
|
51
49
|
Metrics/ModuleLength:
|
52
50
|
Exclude:
|
@@ -55,7 +53,6 @@ Metrics/ModuleLength:
|
|
55
53
|
# Resource class includes DSLs, which tend to accept long list of parameters
|
56
54
|
Metrics/ParameterLists:
|
57
55
|
Exclude:
|
58
|
-
- 'lib/alba/resource.rb'
|
59
56
|
- 'test/**/*.rb'
|
60
57
|
|
61
58
|
# We need to eval resource code to test errors on resource classes
|
@@ -81,7 +78,10 @@ Style/InlineComment:
|
|
81
78
|
Enabled: false
|
82
79
|
|
83
80
|
Style/MethodCallWithArgsParentheses:
|
84
|
-
|
81
|
+
IgnoredMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send']
|
82
|
+
Exclude:
|
83
|
+
# There are so many `attributes` call without parenthese and that's absolutely fine
|
84
|
+
- 'test/**/*.rb'
|
85
85
|
|
86
86
|
# There are so many cases we just want `if` expression!
|
87
87
|
Style/MissingElse:
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [1.4.0] 2021-06-30
|
10
|
+
|
11
|
+
- [Feat] Add a config method to set encoder directly
|
12
|
+
- [Feat] Implement `meta` method and option for metadata
|
13
|
+
- [Feat] Add `root_key` option to `Resource#serialize`
|
14
|
+
- [Feat] Enable setting key for collection with `root_key`
|
15
|
+
- [Feat] Add `Resource.root_key` and `Resource.root_key!`
|
16
|
+
- [Feat] `Alba.serialize` now infers resource class
|
17
|
+
|
9
18
|
## [1.3.0] 2021-05-31
|
10
19
|
|
11
20
|
- [Perf] Improve performance for `many` [641d8f9]
|
data/Gemfile
CHANGED
@@ -5,16 +5,17 @@ gemspec
|
|
5
5
|
|
6
6
|
gem 'activesupport', require: false # For backend
|
7
7
|
gem 'ffaker', require: false # For testing
|
8
|
+
gem 'inch', require: false # For inline documents
|
8
9
|
gem 'minitest', '~> 5.14' # For test
|
9
10
|
gem 'rake', '~> 13.0' # For test and automation
|
10
11
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
11
|
-
gem 'rubocop-minitest', '~> 0.
|
12
|
+
gem 'rubocop-minitest', '~> 0.13.0', require: false # For lint
|
12
13
|
gem 'rubocop-performance', '~> 1.11.0', require: false # For lint
|
13
14
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
14
15
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
15
16
|
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
16
17
|
gem 'simplecov-cobertura', require: false # For test coverage
|
17
|
-
gem 'yard', require: false
|
18
|
+
gem 'yard', require: false # For documentation
|
18
19
|
|
19
20
|
platforms :ruby do
|
20
21
|
gem 'oj', '~> 3.11', require: false # For backend
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
[![CI](https://github.com/okuramasafumi/alba/actions/workflows/main.yml/badge.svg)](https://github.com/okuramasafumi/alba/actions/workflows/main.yml)
|
3
3
|
[![codecov](https://codecov.io/gh/okuramasafumi/alba/branch/master/graph/badge.svg?token=3D3HEZ5OXT)](https://codecov.io/gh/okuramasafumi/alba)
|
4
4
|
[![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
|
5
|
+
[![Inline docs](http://inch-ci.org/github/okuramasafumi/alba.svg?branch=main)](http://inch-ci.org/github/okuramasafumi/alba)
|
5
6
|
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
|
6
7
|
![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
|
7
8
|
|
@@ -98,6 +99,16 @@ You can set a backend like this:
|
|
98
99
|
Alba.backend = :oj
|
99
100
|
```
|
100
101
|
|
102
|
+
#### Encoder configuration
|
103
|
+
|
104
|
+
You can also set JSON encoder directly with a Proc.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
Alba.encoder = ->(object) { JSON.generate(object) }
|
108
|
+
```
|
109
|
+
|
110
|
+
You can consider setting a backend with Symbol as a shortcut to set encoder.
|
111
|
+
|
101
112
|
#### Inference configuration
|
102
113
|
|
103
114
|
You can enable inference feature using `enable_inference!` method.
|
@@ -198,6 +209,35 @@ UserResource.new(user).serialize
|
|
198
209
|
# => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
|
199
210
|
```
|
200
211
|
|
212
|
+
You can define associations inline if you don't need a class for association.
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
class ArticleResource
|
216
|
+
include Alba::Resource
|
217
|
+
|
218
|
+
attributes :title
|
219
|
+
end
|
220
|
+
|
221
|
+
class UserResource
|
222
|
+
include Alba::Resource
|
223
|
+
|
224
|
+
attributes :id
|
225
|
+
|
226
|
+
many :articles, resource: ArticleResource
|
227
|
+
end
|
228
|
+
|
229
|
+
# This class works the same as `UserResource`
|
230
|
+
class AnotherUserResource
|
231
|
+
include Alba::Resource
|
232
|
+
|
233
|
+
attributes :id
|
234
|
+
|
235
|
+
many :articles do
|
236
|
+
attributes :title
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
201
241
|
### Inline definition with `Alba.serialize`
|
202
242
|
|
203
243
|
`Alba.serialize` method is a shortcut to define everything inline.
|
@@ -212,6 +252,13 @@ end
|
|
212
252
|
# => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
|
213
253
|
```
|
214
254
|
|
255
|
+
`Alba.serialize` can be used when you don't know what kind of object you serialize. For example:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
Alba.serialize(something)
|
259
|
+
# => Same as `FooResource.new(something).serialize` when `something` is an instance of `Foo`.
|
260
|
+
```
|
261
|
+
|
215
262
|
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
216
263
|
|
217
264
|
### Inheritance and Ignorance
|
@@ -235,13 +282,12 @@ class GenericFooResource
|
|
235
282
|
attributes :id, :name, :body
|
236
283
|
end
|
237
284
|
|
238
|
-
class
|
285
|
+
class RestrictedFooResource < GenericFooResource
|
239
286
|
ignoring :id, :body
|
240
287
|
end
|
241
288
|
|
242
|
-
|
289
|
+
RestrictedFooResource.new(foo).serialize
|
243
290
|
# => '{"name":"my foo"}'
|
244
|
-
end
|
245
291
|
```
|
246
292
|
|
247
293
|
### Key transformation
|
@@ -402,6 +448,20 @@ user = User.new(1, nil, nil)
|
|
402
448
|
UserResource.new(user).serialize # => '{"id":1}'
|
403
449
|
```
|
404
450
|
|
451
|
+
### Default
|
452
|
+
|
453
|
+
Alba doesn't support default value for attributes, but it's easy to set a default value.
|
454
|
+
|
455
|
+
```ruby
|
456
|
+
class FooResource
|
457
|
+
attribute :bar do |foo|
|
458
|
+
foo.bar || 'default bar'
|
459
|
+
end
|
460
|
+
end
|
461
|
+
```
|
462
|
+
|
463
|
+
We believe this is clearer than using some (not implemented yet) DSL such as `default` because there are some conditions where default values should be applied (`nil`, `blank?`, `empty?` etc.)
|
464
|
+
|
405
465
|
### Inference
|
406
466
|
|
407
467
|
After `Alba.enable_inference!` called, Alba tries to infer root key and association resource name.
|
@@ -510,6 +570,54 @@ Alba.on_error do |error, object, key, attribute, resource_class|
|
|
510
570
|
end
|
511
571
|
```
|
512
572
|
|
573
|
+
### Metadata
|
574
|
+
|
575
|
+
You can set a metadata with `meta` DSL or `meta` option.
|
576
|
+
|
577
|
+
```ruby
|
578
|
+
class UserResource
|
579
|
+
include Alba::Resource
|
580
|
+
|
581
|
+
root_key :user, :users
|
582
|
+
|
583
|
+
attributes :id, :name
|
584
|
+
|
585
|
+
meta do
|
586
|
+
if object.is_a?(Enumerable)
|
587
|
+
{size: object.size}
|
588
|
+
else
|
589
|
+
{foo: :bar}
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
595
|
+
UserResource.new([user]).serialize
|
596
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1}}'
|
597
|
+
|
598
|
+
# You can merge metadata with `meta` option
|
599
|
+
|
600
|
+
UserResource.new([user]).serialize(meta: {foo: :bar})
|
601
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1,"foo":"bar"}}'
|
602
|
+
|
603
|
+
# You can set metadata with `meta` option alone
|
604
|
+
|
605
|
+
class UserResourceWithoutMeta
|
606
|
+
include Alba::Resource
|
607
|
+
|
608
|
+
root_key :user, :users
|
609
|
+
|
610
|
+
attributes :id, :name
|
611
|
+
end
|
612
|
+
|
613
|
+
UserResource.new([user]).serialize(meta: {foo: :bar})
|
614
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"foo":"bar"}}'
|
615
|
+
```
|
616
|
+
|
617
|
+
You can use `object` method to access the underlying object and `params` to access the params in `meta` block.
|
618
|
+
|
619
|
+
Note that setting root key is required when setting a metadata.
|
620
|
+
|
513
621
|
### Circular associations control
|
514
622
|
|
515
623
|
**Note that this feature works correctly since version 1.3. In previous versions it doesn't work as expected.**
|
data/alba.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
|
15
15
|
spec.metadata['homepage_uri'] = spec.homepage
|
16
16
|
spec.metadata['source_code_uri'] = 'https://github.com/okuramasafumi/alba'
|
17
|
-
spec.metadata['changelog_uri'] = 'https://github.com/okuramasafumi/alba/blob/
|
17
|
+
spec.metadata['changelog_uri'] = 'https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md'
|
18
18
|
|
19
19
|
# Specify which files should be added to the gem when it is released.
|
20
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
data/gemfiles/all.gemfile
CHANGED
@@ -11,7 +11,7 @@ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
|
11
11
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
12
12
|
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
13
13
|
gem 'simplecov-cobertura', require: false # For test coverage
|
14
|
-
gem 'yard', require: false
|
14
|
+
gem 'yard', require: false # For documentation
|
15
15
|
|
16
16
|
platforms :ruby do
|
17
17
|
gem 'oj', '~> 3.11', require: false # For backend
|
@@ -9,7 +9,7 @@ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
|
9
9
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
10
10
|
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
11
11
|
gem 'simplecov-cobertura', require: false # For test coverage
|
12
|
-
gem 'yard', require: false
|
12
|
+
gem 'yard', require: false # For documentation
|
13
13
|
|
14
14
|
platforms :ruby do
|
15
15
|
gem 'oj', '~> 3.11', require: false # For backend
|
data/gemfiles/without_oj.gemfile
CHANGED
@@ -10,7 +10,7 @@ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
|
10
10
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
11
11
|
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
12
12
|
gem 'simplecov-cobertura', require: false # For test coverage
|
13
|
-
gem 'yard', require: false
|
13
|
+
gem 'yard', require: false # For documentation
|
14
14
|
|
15
15
|
platforms :ruby do
|
16
16
|
gem 'ruby-prof', require: false # For performance profiling
|
data/lib/alba.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'json'
|
1
2
|
require_relative 'alba/version'
|
2
3
|
require_relative 'alba/resource'
|
3
4
|
|
@@ -14,6 +15,8 @@ module Alba
|
|
14
15
|
|
15
16
|
class << self
|
16
17
|
attr_reader :backend, :encoder, :inferring, :_on_error, :transforming_root_key
|
18
|
+
|
19
|
+
# Accessor for inflector, a module responsible for incflecting strings
|
17
20
|
attr_accessor :inflector
|
18
21
|
|
19
22
|
# Set the backend, which actually serializes object into JSON
|
@@ -24,24 +27,35 @@ module Alba
|
|
24
27
|
# @raise [Alba::UnsupportedBackend] if backend is not supported
|
25
28
|
def backend=(backend)
|
26
29
|
@backend = backend&.to_sym
|
27
|
-
|
30
|
+
set_encoder_from_backend
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set encoder, a Proc object that accepts an object and generates JSON from it
|
34
|
+
# Set backend as `:custom` which indicates no preset encoder is used
|
35
|
+
#
|
36
|
+
# @param encoder [Proc]
|
37
|
+
# @raise [ArgumentError] if given encoder is not a Proc or its arity is not one
|
38
|
+
def encoder=(encoder)
|
39
|
+
raise ArgumentError, 'Encoder must be a Proc accepting one argument' unless encoder.is_a?(Proc) && encoder.arity == 1
|
40
|
+
|
41
|
+
@encoder = encoder
|
42
|
+
@backend = :custom
|
28
43
|
end
|
29
44
|
|
30
45
|
# Serialize the object with inline definitions
|
31
46
|
#
|
32
47
|
# @param object [Object] the object to be serialized
|
33
|
-
# @param key [Symbol]
|
48
|
+
# @param key [Symbol, nil, true] DEPRECATED, use root_key instead
|
49
|
+
# @param root_key [Symbol, nil, true]
|
34
50
|
# @param block [Block] resource block
|
35
51
|
# @return [String] serialized JSON string
|
36
52
|
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
37
|
-
def serialize(object, key: nil, &block)
|
38
|
-
|
53
|
+
def serialize(object, key: nil, root_key: nil, &block)
|
54
|
+
warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
55
|
+
klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
|
39
56
|
|
40
|
-
klass = Class.new
|
41
|
-
klass.include(Alba::Resource)
|
42
|
-
klass.class_eval(&block)
|
43
57
|
resource = klass.new(object)
|
44
|
-
resource.serialize(
|
58
|
+
resource.serialize(root_key: root_key || key)
|
45
59
|
end
|
46
60
|
|
47
61
|
# Enable inference for key and resource name
|
@@ -63,6 +77,9 @@ module Alba
|
|
63
77
|
#
|
64
78
|
# @param [Symbol] handler
|
65
79
|
# @param [Block]
|
80
|
+
# @raise [ArgumentError] if both handler and block params exist
|
81
|
+
# @raise [ArgumentError] if both handler and block params don't exist
|
82
|
+
# @return [void]
|
66
83
|
def on_error(handler = nil, &block)
|
67
84
|
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
68
85
|
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
@@ -80,18 +97,32 @@ module Alba
|
|
80
97
|
@transforming_root_key = false
|
81
98
|
end
|
82
99
|
|
100
|
+
# @param block [Block] resource body
|
101
|
+
# @return [Class<Alba::Resource>] resource class
|
102
|
+
def resource_class(&block)
|
103
|
+
klass = Class.new
|
104
|
+
klass.include(Alba::Resource)
|
105
|
+
klass.class_eval(&block)
|
106
|
+
klass
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param name [String] a String Alba infers resource name with
|
110
|
+
# @param nesting [String, nil] namespace Alba tries to find resource class in
|
111
|
+
# @return [Class<Alba::Resource>] resource class
|
112
|
+
def infer_resource_class(name, nesting: nil)
|
113
|
+
enable_inference!
|
114
|
+
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
115
|
+
const_parent.const_get("#{ActiveSupport::Inflector.classify(name)}Resource")
|
116
|
+
end
|
117
|
+
|
83
118
|
private
|
84
119
|
|
85
|
-
def
|
120
|
+
def set_encoder_from_backend
|
86
121
|
@encoder = case @backend
|
87
|
-
when :oj, :oj_strict
|
88
|
-
|
89
|
-
when :
|
90
|
-
|
91
|
-
when :active_support
|
92
|
-
try_active_support
|
93
|
-
when nil, :default, :json
|
94
|
-
default_encoder
|
122
|
+
when :oj, :oj_strict then try_oj
|
123
|
+
when :oj_rails then try_oj(mode: :rails)
|
124
|
+
when :active_support then try_active_support
|
125
|
+
when nil, :default, :json then default_encoder
|
95
126
|
else
|
96
127
|
raise Alba::UnsupportedBackend, "Unsupported backend, #{backend}"
|
97
128
|
end
|
@@ -115,7 +146,6 @@ module Alba
|
|
115
146
|
|
116
147
|
def default_encoder
|
117
148
|
lambda do |hash|
|
118
|
-
require 'json'
|
119
149
|
JSON.dump(hash)
|
120
150
|
end
|
121
151
|
end
|
data/lib/alba/association.rb
CHANGED
@@ -4,9 +4,10 @@ module Alba
|
|
4
4
|
class Association
|
5
5
|
attr_reader :object, :name
|
6
6
|
|
7
|
-
# @param name [Symbol] name of the method to fetch association
|
8
|
-
# @param condition [Proc] a proc filtering data
|
9
|
-
# @param resource [Class<Alba::Resource
|
7
|
+
# @param name [Symbol, String] name of the method to fetch association
|
8
|
+
# @param condition [Proc, nil] a proc filtering data
|
9
|
+
# @param resource [Class<Alba::Resource>, nil] a resource class for the association
|
10
|
+
# @param nesting [String] a namespace where source class is inferred with
|
10
11
|
# @param block [Block] used to define resource when resource arg is absent
|
11
12
|
def initialize(name:, condition: nil, resource: nil, nesting: nil, &block)
|
12
13
|
@name = name
|
@@ -31,24 +32,12 @@ module Alba
|
|
31
32
|
|
32
33
|
def assign_resource(nesting)
|
33
34
|
@resource = if @block
|
34
|
-
resource_class
|
35
|
+
Alba.resource_class(&@block)
|
35
36
|
elsif Alba.inferring
|
36
|
-
|
37
|
+
Alba.infer_resource_class(@name, nesting: nesting)
|
37
38
|
else
|
38
39
|
raise ArgumentError, 'When Alba.inferring is false, either resource or block is required'
|
39
40
|
end
|
40
41
|
end
|
41
|
-
|
42
|
-
def resource_class
|
43
|
-
klass = Class.new
|
44
|
-
klass.include(Alba::Resource)
|
45
|
-
klass.class_eval(&@block)
|
46
|
-
klass
|
47
|
-
end
|
48
|
-
|
49
|
-
def resource_class_with_nesting(nesting)
|
50
|
-
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
51
|
-
const_parent.const_get("#{ActiveSupport::Inflector.classify(@name)}Resource")
|
52
|
-
end
|
53
42
|
end
|
54
43
|
end
|
@@ -11,7 +11,7 @@ module Alba
|
|
11
11
|
|
12
12
|
# Camelizes a key
|
13
13
|
#
|
14
|
-
# @
|
14
|
+
# @param key [String] key to be camelized
|
15
15
|
# @return [String] camelized key
|
16
16
|
def camelize(key)
|
17
17
|
ActiveSupport::Inflector.camelize(key)
|
@@ -19,7 +19,7 @@ module Alba
|
|
19
19
|
|
20
20
|
# Camelizes a key, 1st letter lowercase
|
21
21
|
#
|
22
|
-
# @
|
22
|
+
# @param key [String] key to be camelized
|
23
23
|
# @return [String] camelized key
|
24
24
|
def camelize_lower(key)
|
25
25
|
ActiveSupport::Inflector.camelize(key, false)
|
@@ -27,7 +27,7 @@ module Alba
|
|
27
27
|
|
28
28
|
# Dasherizes a key
|
29
29
|
#
|
30
|
-
# @
|
30
|
+
# @param key [String] key to be dasherized
|
31
31
|
# @return [String] dasherized key
|
32
32
|
def dasherize(key)
|
33
33
|
ActiveSupport::Inflector.dasherize(key)
|
@@ -4,7 +4,7 @@ module Alba
|
|
4
4
|
class << self
|
5
5
|
# Create key transform function for given transform_type
|
6
6
|
#
|
7
|
-
# @
|
7
|
+
# @param transform_type [Symbol] transform type
|
8
8
|
# @return [Proc] transform function
|
9
9
|
# @raise [Alba::Error] when transform_type is not supported
|
10
10
|
def create(transform_type)
|
data/lib/alba/resource.rb
CHANGED
@@ -8,7 +8,7 @@ module Alba
|
|
8
8
|
module Resource
|
9
9
|
# @!parse include InstanceMethods
|
10
10
|
# @!parse extend ClassMethods
|
11
|
-
DSLS = {_attributes: {}, _key: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil}.freeze
|
11
|
+
DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil}.freeze # rubocop:disable Layout/LineLength
|
12
12
|
private_constant :DSLS
|
13
13
|
|
14
14
|
WITHIN_DEFAULT = Object.new.freeze
|
@@ -33,7 +33,7 @@ module Alba
|
|
33
33
|
|
34
34
|
# @param object [Object] the object to be serialized
|
35
35
|
# @param params [Hash] user-given Hash for arbitrary data
|
36
|
-
# @param within [
|
36
|
+
# @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
|
37
37
|
def initialize(object, params: {}, within: WITHIN_DEFAULT)
|
38
38
|
@object = object
|
39
39
|
@params = params.freeze
|
@@ -43,11 +43,19 @@ module Alba
|
|
43
43
|
|
44
44
|
# Serialize object into JSON string
|
45
45
|
#
|
46
|
-
# @param key [Symbol]
|
46
|
+
# @param key [Symbol, nil, true] DEPRECATED, use root_key instead
|
47
|
+
# @param root_key [Symbol, nil, true]
|
48
|
+
# @param meta [Hash] metadata for this seialization
|
47
49
|
# @return [String] serialized JSON string
|
48
|
-
def serialize(key: nil)
|
49
|
-
key
|
50
|
-
|
50
|
+
def serialize(key: nil, root_key: nil, meta: {})
|
51
|
+
warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
52
|
+
key = key.nil? && root_key.nil? ? fetch_key : root_key || key
|
53
|
+
hash = if key && key != ''
|
54
|
+
h = {key.to_s => serializable_hash}
|
55
|
+
hash_with_metadata(h, meta)
|
56
|
+
else
|
57
|
+
serializable_hash
|
58
|
+
end
|
51
59
|
Alba.encoder.call(hash)
|
52
60
|
end
|
53
61
|
|
@@ -61,15 +69,29 @@ module Alba
|
|
61
69
|
|
62
70
|
private
|
63
71
|
|
72
|
+
def hash_with_metadata(hash, meta)
|
73
|
+
base = @_meta ? instance_eval(&@_meta) : {}
|
74
|
+
metadata = base.merge(meta)
|
75
|
+
hash[:meta] = metadata unless metadata.empty?
|
76
|
+
hash
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_key
|
80
|
+
collection? ? _key_for_collection : _key
|
81
|
+
end
|
82
|
+
|
83
|
+
def _key_for_collection
|
84
|
+
return @_key_for_collection.to_s unless @_key_for_collection == true && Alba.inferring
|
85
|
+
|
86
|
+
key = resource_name.pluralize
|
87
|
+
transforming_root_key? ? transform_key(key) : key
|
88
|
+
end
|
89
|
+
|
64
90
|
# @return [String]
|
65
91
|
def _key
|
66
92
|
return @_key.to_s unless @_key == true && Alba.inferring
|
67
93
|
|
68
|
-
transforming_root_key? ? transform_key(
|
69
|
-
end
|
70
|
-
|
71
|
-
def key_from_resource_name
|
72
|
-
collection? ? resource_name.pluralize : resource_name
|
94
|
+
transforming_root_key? ? transform_key(resource_name) : resource_name
|
73
95
|
end
|
74
96
|
|
75
97
|
def resource_name
|
@@ -105,14 +127,11 @@ module Alba
|
|
105
127
|
def conditional_attribute(object, key, attribute)
|
106
128
|
condition = attribute.last
|
107
129
|
arity = condition.arity
|
130
|
+
# We can return early to skip fetch_attribute
|
108
131
|
return [] if arity <= 1 && !instance_exec(object, &condition)
|
109
132
|
|
110
133
|
fetched_attribute = fetch_attribute(object, attribute.first)
|
111
|
-
attr =
|
112
|
-
attribute.first.object
|
113
|
-
else
|
114
|
-
fetched_attribute
|
115
|
-
end
|
134
|
+
attr = attribute.first.is_a?(Alba::Association) ? attribute.first.object : fetched_attribute
|
116
135
|
return [] if arity >= 2 && !instance_exec(object, attr, &condition)
|
117
136
|
|
118
137
|
[key, fetched_attribute]
|
@@ -121,14 +140,10 @@ module Alba
|
|
121
140
|
def handle_error(error, object, key, attribute)
|
122
141
|
on_error = @_on_error || Alba._on_error
|
123
142
|
case on_error
|
124
|
-
when :raise, nil
|
125
|
-
|
126
|
-
when :
|
127
|
-
|
128
|
-
when :ignore
|
129
|
-
[]
|
130
|
-
when Proc
|
131
|
-
on_error.call(error, object, key, attribute, self.class)
|
143
|
+
when :raise, nil then raise
|
144
|
+
when :nullify then [key, nil]
|
145
|
+
when :ignore then []
|
146
|
+
when Proc then on_error.call(error, object, key, attribute, self.class)
|
132
147
|
else
|
133
148
|
raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
|
134
149
|
end
|
@@ -143,34 +158,27 @@ module Alba
|
|
143
158
|
|
144
159
|
def fetch_attribute(object, attribute)
|
145
160
|
case attribute
|
146
|
-
when Symbol
|
147
|
-
|
148
|
-
when
|
149
|
-
|
150
|
-
when Alba::One, Alba::Many
|
151
|
-
within = check_within(attribute.name.to_sym)
|
152
|
-
return unless within
|
153
|
-
|
154
|
-
attribute.to_hash(object, params: params, within: within)
|
155
|
-
when TypedAttribute
|
156
|
-
attribute.value(object)
|
161
|
+
when Symbol then object.public_send attribute
|
162
|
+
when Proc then instance_exec(object, &attribute)
|
163
|
+
when Alba::One, Alba::Many then yield_if_within(attribute.name.to_sym) { |within| attribute.to_hash(object, params: params, within: within) }
|
164
|
+
when TypedAttribute then attribute.value(object)
|
157
165
|
else
|
158
166
|
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
159
167
|
end
|
160
168
|
end
|
161
169
|
|
170
|
+
def yield_if_within(association_name)
|
171
|
+
within = check_within(association_name)
|
172
|
+
yield(within) if within
|
173
|
+
end
|
174
|
+
|
162
175
|
def check_within(association_name)
|
163
176
|
case @within
|
164
|
-
when WITHIN_DEFAULT # Default value, doesn't check within tree
|
165
|
-
|
166
|
-
when
|
167
|
-
|
168
|
-
when
|
169
|
-
@within.find { |item| item.to_sym == association_name }
|
170
|
-
when Symbol # within tree could end with Symbol
|
171
|
-
@within == association_name
|
172
|
-
when nil, true, false # In these cases, Alba stops serialization here.
|
173
|
-
false
|
177
|
+
when WITHIN_DEFAULT then WITHIN_DEFAULT # Default value, doesn't check within tree
|
178
|
+
when Hash then @within.fetch(association_name, nil) # Traverse within tree
|
179
|
+
when Array then @within.find { |item| item.to_sym == association_name }
|
180
|
+
when Symbol then @within == association_name
|
181
|
+
when nil, true, false then false # Stop here
|
174
182
|
else
|
175
183
|
raise Alba::Error, "Unknown type for within option: #{@within.class}"
|
176
184
|
end
|
@@ -191,11 +199,16 @@ module Alba
|
|
191
199
|
DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
|
192
200
|
end
|
193
201
|
|
202
|
+
# Defining methods for DSLs and disable parameter number check since for users' benefits increasing params is fine
|
203
|
+
# rubocop:disable Metrics/ParameterLists
|
204
|
+
|
194
205
|
# Set multiple attributes at once
|
195
206
|
#
|
196
207
|
# @param attrs [Array<String, Symbol>]
|
197
|
-
# @param if [
|
198
|
-
# @param attrs_with_types [Hash]
|
208
|
+
# @param if [Proc] condition to decide if it should serialize these attributes
|
209
|
+
# @param attrs_with_types [Hash<[Symbol, String], [Array<Symbol, Proc>, Symbol]>]
|
210
|
+
# attributes with name in its key and type and optional type converter in its value
|
211
|
+
# @return [void]
|
199
212
|
def attributes(*attrs, if: nil, **attrs_with_types) # rubocop:disable Naming/MethodParameterName
|
200
213
|
if_value = binding.local_variable_get(:if)
|
201
214
|
assign_attributes(attrs, if_value)
|
@@ -224,9 +237,11 @@ module Alba
|
|
224
237
|
# Set an attribute with the given block
|
225
238
|
#
|
226
239
|
# @param name [String, Symbol] key name
|
227
|
-
# @param options [Hash]
|
240
|
+
# @param options [Hash<Symbol, Proc>]
|
241
|
+
# @option options [Proc] if a condition to decide if this attribute should be serialized
|
228
242
|
# @param block [Block] the block called during serialization
|
229
243
|
# @raise [ArgumentError] if block is absent
|
244
|
+
# @return [void]
|
230
245
|
def attribute(name, **options, &block)
|
231
246
|
raise ArgumentError, 'No block given in attribute method' unless block
|
232
247
|
|
@@ -235,12 +250,14 @@ module Alba
|
|
235
250
|
|
236
251
|
# Set One association
|
237
252
|
#
|
238
|
-
# @param name [String, Symbol]
|
239
|
-
# @param condition [Proc]
|
240
|
-
# @param resource [Class<Alba::Resource
|
241
|
-
# @param key [String, Symbol] used as key when given
|
242
|
-
# @param options [Hash]
|
253
|
+
# @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
|
254
|
+
# @param condition [Proc, nil] a Proc to modify the association
|
255
|
+
# @param resource [Class<Alba::Resource>, String, nil] representing resource for this association
|
256
|
+
# @param key [String, Symbol, nil] used as key when given
|
257
|
+
# @param options [Hash<Symbol, Proc>]
|
258
|
+
# @option options [Proc] if a condition to decide if this association should be serialized
|
243
259
|
# @param block [Block]
|
260
|
+
# @return [void]
|
244
261
|
# @see Alba::One#initialize
|
245
262
|
def one(name, condition = nil, resource: nil, key: nil, **options, &block)
|
246
263
|
nesting = self.name&.rpartition('::')&.first
|
@@ -251,12 +268,14 @@ module Alba
|
|
251
268
|
|
252
269
|
# Set Many association
|
253
270
|
#
|
254
|
-
# @param name [String, Symbol]
|
255
|
-
# @param condition [Proc]
|
256
|
-
# @param resource [Class<Alba::Resource
|
257
|
-
# @param key [String, Symbol] used as key when given
|
258
|
-
# @param options [Hash]
|
271
|
+
# @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
|
272
|
+
# @param condition [Proc, nil] a Proc to filter the collection
|
273
|
+
# @param resource [Class<Alba::Resource>, String, nil] representing resource for this association
|
274
|
+
# @param key [String, Symbol, nil] used as key when given
|
275
|
+
# @param options [Hash<Symbol, Proc>]
|
276
|
+
# @option options [Proc] if a condition to decide if this association should be serialized
|
259
277
|
# @param block [Block]
|
278
|
+
# @return [void]
|
260
279
|
# @see Alba::Many#initialize
|
261
280
|
def many(name, condition = nil, resource: nil, key: nil, **options, &block)
|
262
281
|
nesting = self.name&.rpartition('::')&.first
|
@@ -268,14 +287,40 @@ module Alba
|
|
268
287
|
# Set key
|
269
288
|
#
|
270
289
|
# @param key [String, Symbol]
|
290
|
+
# @deprecated Use {#root_key} instead
|
271
291
|
def key(key)
|
292
|
+
warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
|
272
293
|
@_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
273
294
|
end
|
274
295
|
|
296
|
+
# Set root key
|
297
|
+
#
|
298
|
+
# @param key [String, Symbol]
|
299
|
+
# @param key_for_collection [String, Symbol]
|
300
|
+
# @raise [NoMethodError] when key doesn't respond to `to_sym` method
|
301
|
+
def root_key(key, key_for_collection = nil)
|
302
|
+
@_key = key.to_sym
|
303
|
+
@_key_for_collection = key_for_collection&.to_sym
|
304
|
+
end
|
305
|
+
|
275
306
|
# Set key to true
|
276
307
|
#
|
308
|
+
# @deprecated Use {#root_key!} instead
|
277
309
|
def key!
|
310
|
+
warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
|
278
311
|
@_key = true
|
312
|
+
@_key_for_collection = true
|
313
|
+
end
|
314
|
+
|
315
|
+
# Set root key to true
|
316
|
+
def root_key!
|
317
|
+
@_key = true
|
318
|
+
@_key_for_collection = true
|
319
|
+
end
|
320
|
+
|
321
|
+
# Set metadata
|
322
|
+
def meta(&block)
|
323
|
+
@_meta = block
|
279
324
|
end
|
280
325
|
|
281
326
|
# Delete attributes
|
@@ -298,15 +343,18 @@ module Alba
|
|
298
343
|
end
|
299
344
|
|
300
345
|
# Set error handler
|
346
|
+
# If this is set it's used as a error handler overriding global one
|
301
347
|
#
|
302
|
-
# @param [Symbol]
|
303
|
-
# @param [Block]
|
348
|
+
# @param handler [Symbol] `:raise`, `:ignore` or `:nullify`
|
349
|
+
# @param block [Block]
|
304
350
|
def on_error(handler = nil, &block)
|
305
351
|
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
306
352
|
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
307
353
|
|
308
354
|
@_on_error = handler || block
|
309
355
|
end
|
356
|
+
|
357
|
+
# rubocop:enable Metrics/ParameterLists
|
310
358
|
end
|
311
359
|
end
|
312
360
|
end
|
data/lib/alba/typed_attribute.rb
CHANGED
@@ -28,12 +28,9 @@ module Alba
|
|
28
28
|
def check(object)
|
29
29
|
value = object.public_send(@name)
|
30
30
|
type_correct = case @type
|
31
|
-
when :String, ->(klass) { klass == String }
|
32
|
-
|
33
|
-
when :
|
34
|
-
value.is_a?(Integer)
|
35
|
-
when :Boolean
|
36
|
-
[true, false].include?(value)
|
31
|
+
when :String, ->(klass) { klass == String } then value.is_a?(String)
|
32
|
+
when :Integer, ->(klass) { klass == Integer } then value.is_a?(Integer)
|
33
|
+
when :Boolean then [true, false].include?(value)
|
37
34
|
else
|
38
35
|
raise Alba::UnsupportedType, "Unknown type: #{@type}"
|
39
36
|
end
|
data/lib/alba/version.rb
CHANGED
@@ -0,0 +1,174 @@
|
|
1
|
+
# Benchmark script to run varieties of JSON serializers
|
2
|
+
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
3
|
+
# exit(status)
|
4
|
+
|
5
|
+
# --- Bundle dependencies ---
|
6
|
+
|
7
|
+
require "bundler/inline"
|
8
|
+
|
9
|
+
gemfile(true) do
|
10
|
+
source "https://rubygems.org"
|
11
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
12
|
+
|
13
|
+
gem "activerecord", "~> 6.1.3"
|
14
|
+
gem "alba", path: '../'
|
15
|
+
gem "benchmark-ips"
|
16
|
+
gem "blueprinter"
|
17
|
+
gem "jbuilder"
|
18
|
+
gem "multi_json"
|
19
|
+
gem "oj"
|
20
|
+
gem "sqlite3"
|
21
|
+
end
|
22
|
+
|
23
|
+
# --- Test data model setup ---
|
24
|
+
|
25
|
+
require "active_record"
|
26
|
+
require "oj"
|
27
|
+
require "sqlite3"
|
28
|
+
Oj.optimize_rails
|
29
|
+
|
30
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
31
|
+
|
32
|
+
ActiveRecord::Schema.define do
|
33
|
+
create_table :posts, force: true do |t|
|
34
|
+
t.string :body
|
35
|
+
end
|
36
|
+
|
37
|
+
create_table :comments, force: true do |t|
|
38
|
+
t.integer :post_id
|
39
|
+
t.string :body
|
40
|
+
t.integer :commenter_id
|
41
|
+
end
|
42
|
+
|
43
|
+
create_table :users, force: true do |t|
|
44
|
+
t.string :name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Post < ActiveRecord::Base
|
49
|
+
has_many :comments
|
50
|
+
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
51
|
+
|
52
|
+
def attributes
|
53
|
+
{id: nil, body: nil, commenter_names: commenter_names}
|
54
|
+
end
|
55
|
+
|
56
|
+
def commenter_names
|
57
|
+
commenters.pluck(:name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Comment < ActiveRecord::Base
|
62
|
+
belongs_to :post
|
63
|
+
belongs_to :commenter, class_name: 'User'
|
64
|
+
|
65
|
+
def attributes
|
66
|
+
{id: nil, body: nil}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class User < ActiveRecord::Base
|
71
|
+
has_many :comments
|
72
|
+
end
|
73
|
+
|
74
|
+
# --- Alba serializers ---
|
75
|
+
|
76
|
+
require "alba"
|
77
|
+
|
78
|
+
class AlbaCommentResource
|
79
|
+
include ::Alba::Resource
|
80
|
+
attributes :id, :body
|
81
|
+
end
|
82
|
+
|
83
|
+
class AlbaPostResource
|
84
|
+
include ::Alba::Resource
|
85
|
+
attributes :id, :body
|
86
|
+
attribute :commenter_names do |post|
|
87
|
+
post.commenters.pluck(:name)
|
88
|
+
end
|
89
|
+
many :comments, resource: AlbaCommentResource
|
90
|
+
end
|
91
|
+
|
92
|
+
# --- Blueprint serializers ---
|
93
|
+
|
94
|
+
require "blueprinter"
|
95
|
+
|
96
|
+
class CommentBlueprint < Blueprinter::Base
|
97
|
+
fields :id, :body
|
98
|
+
end
|
99
|
+
|
100
|
+
class PostBlueprint < Blueprinter::Base
|
101
|
+
fields :id, :body, :commenter_names
|
102
|
+
association :comments, blueprint: CommentBlueprint
|
103
|
+
|
104
|
+
def commenter_names
|
105
|
+
commenters.pluck(:name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# --- JBuilder serializers ---
|
110
|
+
|
111
|
+
require "jbuilder"
|
112
|
+
|
113
|
+
class Post
|
114
|
+
def to_builder
|
115
|
+
Jbuilder.new do |post|
|
116
|
+
post.call(self, :id, :body, :commenter_names, :comments)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def commenter_names
|
121
|
+
commenters.pluck(:name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Comment
|
126
|
+
def to_builder
|
127
|
+
Jbuilder.new do |comment|
|
128
|
+
comment.call(self, :id, :body)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# --- Test data creation ---
|
134
|
+
|
135
|
+
100.times do |i|
|
136
|
+
post = Post.create!(body: "post#{i}")
|
137
|
+
user1 = User.create!(name: "John#{i}")
|
138
|
+
user2 = User.create!(name: "Jane#{i}")
|
139
|
+
10.times do |n|
|
140
|
+
post.comments.create!(commenter: user1, body: "Comment1_#{i}_#{n}")
|
141
|
+
post.comments.create!(commenter: user2, body: "Comment2_#{i}_#{n}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
posts = Post.all.to_a
|
146
|
+
|
147
|
+
# --- Store the serializers in procs ---
|
148
|
+
|
149
|
+
alba = Proc.new { AlbaPostResource.new(posts).serialize }
|
150
|
+
blueprinter = Proc.new { PostBlueprint.render(posts) }
|
151
|
+
jbuilder = Proc.new do
|
152
|
+
Jbuilder.new do |json|
|
153
|
+
json.array!(posts) do |post|
|
154
|
+
json.post post.to_builder
|
155
|
+
end
|
156
|
+
end.target!
|
157
|
+
end
|
158
|
+
|
159
|
+
# --- Run the benchmarks ---
|
160
|
+
|
161
|
+
require 'benchmark/ips'
|
162
|
+
result = Benchmark.ips do |x|
|
163
|
+
x.report(:alba, &alba)
|
164
|
+
x.report(:blueprinter, &blueprinter)
|
165
|
+
x.report(:jbuilder, &jbuilder)
|
166
|
+
end
|
167
|
+
|
168
|
+
entries = result.entries.map {|entry| [entry.label, entry.iterations]}
|
169
|
+
alba_ips = entries.find {|e| e.first == :alba }.last
|
170
|
+
blueprinter_ips = entries.find {|e| e.first == :blueprinter }.last
|
171
|
+
jbuidler_ips = entries.find {|e| e.first == :jbuilder }.last
|
172
|
+
# Alba should be as fast as jbuilder and faster than blueprinter
|
173
|
+
alba_is_fast_enough = (alba_ips - jbuidler_ips) > -10.0 && (alba_ips - blueprinter_ips) > 10.0
|
174
|
+
exit(alba_is_fast_enough)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKURA Masafumi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
|
14
14
|
flexibility and usability.
|
@@ -22,6 +22,7 @@ files:
|
|
22
22
|
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
23
23
|
- ".github/dependabot.yml"
|
24
24
|
- ".github/workflows/main.yml"
|
25
|
+
- ".github/workflows/perf.yml"
|
25
26
|
- ".gitignore"
|
26
27
|
- ".rubocop.yml"
|
27
28
|
- ".yardopts"
|
@@ -50,6 +51,7 @@ files:
|
|
50
51
|
- lib/alba/resource.rb
|
51
52
|
- lib/alba/typed_attribute.rb
|
52
53
|
- lib/alba/version.rb
|
54
|
+
- script/perf_check.rb
|
53
55
|
- sider.yml
|
54
56
|
homepage: https://github.com/okuramasafumi/alba
|
55
57
|
licenses:
|
@@ -57,7 +59,7 @@ licenses:
|
|
57
59
|
metadata:
|
58
60
|
homepage_uri: https://github.com/okuramasafumi/alba
|
59
61
|
source_code_uri: https://github.com/okuramasafumi/alba
|
60
|
-
changelog_uri: https://github.com/okuramasafumi/alba/blob/
|
62
|
+
changelog_uri: https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md
|
61
63
|
post_install_message:
|
62
64
|
rdoc_options: []
|
63
65
|
require_paths:
|