alba 1.1.0 → 1.5.0
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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/dependabot.yml +26 -0
- data/.github/workflows/perf.yml +21 -0
- data/.rubocop.yml +18 -8
- data/.yardopts +2 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +4 -3
- data/README.md +377 -14
- data/SECURITY.md +12 -0
- data/alba.gemspec +2 -2
- data/benchmark/collection.rb +441 -0
- data/benchmark/{local.rb → single_resource.rb} +120 -15
- data/codecov.yml +3 -0
- data/docs/migrate_from_active_model_serializers.md +359 -0
- data/docs/migrate_from_jbuilder.md +223 -0
- data/gemfiles/all.gemfile +1 -1
- data/gemfiles/without_active_support.gemfile +1 -1
- data/gemfiles/without_oj.gemfile +1 -1
- data/lib/alba/association.rb +14 -17
- data/lib/alba/default_inflector.rb +36 -0
- data/lib/alba/deprecation.rb +14 -0
- data/lib/alba/key_transform_factory.rb +33 -0
- data/lib/alba/many.rb +1 -1
- data/lib/alba/resource.rb +226 -83
- data/lib/alba/typed_attribute.rb +61 -0
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +82 -21
- data/script/perf_check.rb +174 -0
- data/sider.yml +2 -4
- metadata +20 -9
- data/lib/alba/key_transformer.rb +0 -32
data/lib/alba.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'json'
|
1
2
|
require_relative 'alba/version'
|
2
3
|
require_relative 'alba/resource'
|
4
|
+
require_relative 'alba/deprecation'
|
3
5
|
|
4
6
|
# Core module
|
5
7
|
module Alba
|
@@ -9,8 +11,14 @@ module Alba
|
|
9
11
|
# Error class for backend which is not supported
|
10
12
|
class UnsupportedBackend < Error; end
|
11
13
|
|
14
|
+
# Error class for type which is not supported
|
15
|
+
class UnsupportedType < Error; end
|
16
|
+
|
12
17
|
class << self
|
13
|
-
attr_reader :backend, :encoder, :inferring, :_on_error
|
18
|
+
attr_reader :backend, :encoder, :inferring, :_on_error, :_on_nil, :transforming_root_key
|
19
|
+
|
20
|
+
# Accessor for inflector, a module responsible for incflecting strings
|
21
|
+
attr_accessor :inflector
|
14
22
|
|
15
23
|
# Set the backend, which actually serializes object into JSON
|
16
24
|
#
|
@@ -20,24 +28,35 @@ module Alba
|
|
20
28
|
# @raise [Alba::UnsupportedBackend] if backend is not supported
|
21
29
|
def backend=(backend)
|
22
30
|
@backend = backend&.to_sym
|
23
|
-
|
31
|
+
set_encoder_from_backend
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set encoder, a Proc object that accepts an object and generates JSON from it
|
35
|
+
# Set backend as `:custom` which indicates no preset encoder is used
|
36
|
+
#
|
37
|
+
# @param encoder [Proc]
|
38
|
+
# @raise [ArgumentError] if given encoder is not a Proc or its arity is not one
|
39
|
+
def encoder=(encoder)
|
40
|
+
raise ArgumentError, 'Encoder must be a Proc accepting one argument' unless encoder.is_a?(Proc) && encoder.arity == 1
|
41
|
+
|
42
|
+
@encoder = encoder
|
43
|
+
@backend = :custom
|
24
44
|
end
|
25
45
|
|
26
46
|
# Serialize the object with inline definitions
|
27
47
|
#
|
28
48
|
# @param object [Object] the object to be serialized
|
29
|
-
# @param key [Symbol]
|
49
|
+
# @param key [Symbol, nil, true] DEPRECATED, use root_key instead
|
50
|
+
# @param root_key [Symbol, nil, true]
|
30
51
|
# @param block [Block] resource block
|
31
52
|
# @return [String] serialized JSON string
|
32
53
|
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
33
|
-
def serialize(object, key: nil, &block)
|
34
|
-
|
54
|
+
def serialize(object, key: nil, root_key: nil, &block)
|
55
|
+
Alba::Deprecation.warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
56
|
+
klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
|
35
57
|
|
36
|
-
klass = Class.new
|
37
|
-
klass.include(Alba::Resource)
|
38
|
-
klass.class_eval(&block)
|
39
58
|
resource = klass.new(object)
|
40
|
-
resource.serialize(
|
59
|
+
resource.serialize(root_key: root_key || key)
|
41
60
|
end
|
42
61
|
|
43
62
|
# Enable inference for key and resource name
|
@@ -59,6 +78,9 @@ module Alba
|
|
59
78
|
#
|
60
79
|
# @param [Symbol] handler
|
61
80
|
# @param [Block]
|
81
|
+
# @raise [ArgumentError] if both handler and block params exist
|
82
|
+
# @raise [ArgumentError] if both handler and block params don't exist
|
83
|
+
# @return [void]
|
62
84
|
def on_error(handler = nil, &block)
|
63
85
|
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
64
86
|
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
@@ -66,18 +88,59 @@ module Alba
|
|
66
88
|
@_on_error = handler || block
|
67
89
|
end
|
68
90
|
|
91
|
+
# Set nil handler
|
92
|
+
#
|
93
|
+
# @param block [Block]
|
94
|
+
# @return [void]
|
95
|
+
def on_nil(&block)
|
96
|
+
@_on_nil = block
|
97
|
+
end
|
98
|
+
|
99
|
+
# Enable root key transformation
|
100
|
+
def enable_root_key_transformation!
|
101
|
+
@transforming_root_key = true
|
102
|
+
end
|
103
|
+
|
104
|
+
# Disable root key transformation
|
105
|
+
def disable_root_key_transformation!
|
106
|
+
@transforming_root_key = false
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param block [Block] resource body
|
110
|
+
# @return [Class<Alba::Resource>] resource class
|
111
|
+
def resource_class(&block)
|
112
|
+
klass = Class.new
|
113
|
+
klass.include(Alba::Resource)
|
114
|
+
klass.class_eval(&block)
|
115
|
+
klass
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param name [String] a String Alba infers resource name with
|
119
|
+
# @param nesting [String, nil] namespace Alba tries to find resource class in
|
120
|
+
# @return [Class<Alba::Resource>] resource class
|
121
|
+
def infer_resource_class(name, nesting: nil)
|
122
|
+
enable_inference!
|
123
|
+
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
124
|
+
const_parent.const_get("#{ActiveSupport::Inflector.classify(name)}Resource")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Reset config variables
|
128
|
+
# Useful for test cleanup
|
129
|
+
def reset!
|
130
|
+
@encoder = default_encoder
|
131
|
+
@_on_error = :raise
|
132
|
+
@_on_nil = nil
|
133
|
+
@transforming_root_key = false # TODO: This will be true since 2.0
|
134
|
+
end
|
135
|
+
|
69
136
|
private
|
70
137
|
|
71
|
-
def
|
138
|
+
def set_encoder_from_backend
|
72
139
|
@encoder = case @backend
|
73
|
-
when :oj, :oj_strict
|
74
|
-
|
75
|
-
when :
|
76
|
-
|
77
|
-
when :active_support
|
78
|
-
try_active_support
|
79
|
-
when nil, :default, :json
|
80
|
-
default_encoder
|
140
|
+
when :oj, :oj_strict then try_oj
|
141
|
+
when :oj_rails then try_oj(mode: :rails)
|
142
|
+
when :active_support then try_active_support
|
143
|
+
when nil, :default, :json then default_encoder
|
81
144
|
else
|
82
145
|
raise Alba::UnsupportedBackend, "Unsupported backend, #{backend}"
|
83
146
|
end
|
@@ -101,12 +164,10 @@ module Alba
|
|
101
164
|
|
102
165
|
def default_encoder
|
103
166
|
lambda do |hash|
|
104
|
-
require 'json'
|
105
167
|
JSON.dump(hash)
|
106
168
|
end
|
107
169
|
end
|
108
170
|
end
|
109
171
|
|
110
|
-
|
111
|
-
@_on_error = :raise
|
172
|
+
reset!
|
112
173
|
end
|
@@ -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)
|
data/sider.yml
CHANGED
@@ -49,10 +49,8 @@ linter:
|
|
49
49
|
# norc: true
|
50
50
|
|
51
51
|
# # https://help.sider.review/getting-started/custom-configuration#ignore
|
52
|
-
|
53
|
-
|
54
|
-
# - "*.mp4"
|
55
|
-
# - "images/**"
|
52
|
+
ignore:
|
53
|
+
- 'test/**/*'
|
56
54
|
|
57
55
|
# # https://help.sider.review/getting-started/custom-configuration#branchesexclude
|
58
56
|
# branches:
|
metadata
CHANGED
@@ -1,25 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.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-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Alba is
|
14
|
-
|
15
|
-
do similar things. The internal is so simple that it's easy to hack and maintain.
|
13
|
+
description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
|
14
|
+
flexibility and usability.
|
16
15
|
email:
|
17
16
|
- masafumi.o1988@gmail.com
|
18
17
|
executables: []
|
19
18
|
extensions: []
|
20
19
|
extra_rdoc_files: []
|
21
20
|
files:
|
21
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
22
|
+
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
23
|
+
- ".github/dependabot.yml"
|
22
24
|
- ".github/workflows/main.yml"
|
25
|
+
- ".github/workflows/perf.yml"
|
23
26
|
- ".gitignore"
|
24
27
|
- ".rubocop.yml"
|
25
28
|
- ".yardopts"
|
@@ -29,21 +32,29 @@ files:
|
|
29
32
|
- LICENSE.txt
|
30
33
|
- README.md
|
31
34
|
- Rakefile
|
35
|
+
- SECURITY.md
|
32
36
|
- alba.gemspec
|
33
|
-
- benchmark/
|
37
|
+
- benchmark/collection.rb
|
38
|
+
- benchmark/single_resource.rb
|
34
39
|
- bin/console
|
35
40
|
- bin/setup
|
36
41
|
- codecov.yml
|
42
|
+
- docs/migrate_from_active_model_serializers.md
|
43
|
+
- docs/migrate_from_jbuilder.md
|
37
44
|
- gemfiles/all.gemfile
|
38
45
|
- gemfiles/without_active_support.gemfile
|
39
46
|
- gemfiles/without_oj.gemfile
|
40
47
|
- lib/alba.rb
|
41
48
|
- lib/alba/association.rb
|
42
|
-
- lib/alba/
|
49
|
+
- lib/alba/default_inflector.rb
|
50
|
+
- lib/alba/deprecation.rb
|
51
|
+
- lib/alba/key_transform_factory.rb
|
43
52
|
- lib/alba/many.rb
|
44
53
|
- lib/alba/one.rb
|
45
54
|
- lib/alba/resource.rb
|
55
|
+
- lib/alba/typed_attribute.rb
|
46
56
|
- lib/alba/version.rb
|
57
|
+
- script/perf_check.rb
|
47
58
|
- sider.yml
|
48
59
|
homepage: https://github.com/okuramasafumi/alba
|
49
60
|
licenses:
|
@@ -51,7 +62,7 @@ licenses:
|
|
51
62
|
metadata:
|
52
63
|
homepage_uri: https://github.com/okuramasafumi/alba
|
53
64
|
source_code_uri: https://github.com/okuramasafumi/alba
|
54
|
-
changelog_uri: https://github.com/okuramasafumi/alba/blob/
|
65
|
+
changelog_uri: https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md
|
55
66
|
post_install_message:
|
56
67
|
rdoc_options: []
|
57
68
|
require_paths:
|
@@ -67,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
78
|
- !ruby/object:Gem::Version
|
68
79
|
version: '0'
|
69
80
|
requirements: []
|
70
|
-
rubygems_version: 3.2.
|
81
|
+
rubygems_version: 3.2.22
|
71
82
|
signing_key:
|
72
83
|
specification_version: 4
|
73
84
|
summary: Alba is the fastest JSON serializer for Ruby.
|
data/lib/alba/key_transformer.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Alba
|
2
|
-
# Transform keys using `ActiveSupport::Inflector`
|
3
|
-
module KeyTransformer
|
4
|
-
begin
|
5
|
-
require 'active_support/inflector'
|
6
|
-
rescue LoadError
|
7
|
-
raise ::Alba::Error, 'To use transform_keys, please install `ActiveSupport` gem.'
|
8
|
-
end
|
9
|
-
|
10
|
-
module_function
|
11
|
-
|
12
|
-
# Transform key as given transform_type
|
13
|
-
#
|
14
|
-
# @params key [String] key to be transformed
|
15
|
-
# @params transform_type [Symbol] transform type
|
16
|
-
# @return [String] transformed key
|
17
|
-
# @raise [Alba::Error] when transform_type is not supported
|
18
|
-
def transform(key, transform_type)
|
19
|
-
key = key.to_s
|
20
|
-
case transform_type
|
21
|
-
when :camel
|
22
|
-
ActiveSupport::Inflector.camelize(key)
|
23
|
-
when :lower_camel
|
24
|
-
ActiveSupport::Inflector.camelize(key, false)
|
25
|
-
when :dash
|
26
|
-
ActiveSupport::Inflector.dasherize(key)
|
27
|
-
else
|
28
|
-
raise ::Alba::Error, "Unknown transform_type: #{transform_type}. Supported transform_type are :camel, :lower_camel and :dash."
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|