alba 0.10.2 → 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +25 -0
- data/.rubocop.yml +6 -5
- data/.yardopts +2 -0
- data/Gemfile +6 -4
- data/Gemfile.lock +42 -39
- data/README.md +106 -7
- data/benchmark/local.rb +198 -0
- data/lib/alba.rb +22 -6
- data/lib/alba/association.rb +5 -0
- data/lib/alba/key_transformer.rb +31 -0
- data/lib/alba/many.rb +9 -2
- data/lib/alba/one.rb +9 -2
- data/lib/alba/resource.rb +89 -17
- data/lib/alba/serializer.rb +21 -5
- data/lib/alba/version.rb +1 -1
- data/sider.yml +1 -0
- metadata +7 -4
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 993f4b887c19f4e1149ddb850cd07a8cb8c2042cde0fb3974b1380537707a55d
|
4
|
+
data.tar.gz: acf77a0aa35f7fe23fc3f98843064d2800e0b4e0739e587dd29aab65f06269ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b8e52dcaf4ca33cba0685c2a0a06c16df69cd04b6d399c092e920548f5baa6a21aeab8097f90e92ab35ac9c260d83858c19462a9ec2a689d97c6999daef5e3a
|
7
|
+
data.tar.gz: e525a66c30477cab6741436128398a6f0309a0f786040fe946a9f2783d08f619df00d85157d04c5876cc579e19cceabf22dcb3556addf6ad061d44d1f0cfa888
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
11
|
+
ruby: [2.5, 2.6, 2.7, 3.0, head, truffleruby]
|
12
|
+
exclude:
|
13
|
+
- os: windows-latest
|
14
|
+
ruby: truffleruby
|
15
|
+
runs-on: ${{ matrix.os }}
|
16
|
+
steps:
|
17
|
+
- uses: actions/checkout@v2
|
18
|
+
- name: Set up Ruby
|
19
|
+
uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{ matrix.ruby }}
|
22
|
+
bundler-cache: true
|
23
|
+
- name: Run the default task
|
24
|
+
run: |
|
25
|
+
bundle exec rake
|
data/.rubocop.yml
CHANGED
@@ -6,11 +6,13 @@ inherit_gem:
|
|
6
6
|
require:
|
7
7
|
- rubocop-minitest
|
8
8
|
- rubocop-performance
|
9
|
+
- rubocop-rake
|
9
10
|
|
10
11
|
AllCops:
|
11
12
|
Exclude:
|
12
13
|
- 'Rakefile'
|
13
14
|
- 'alba.gemspec'
|
15
|
+
- 'benchmark/**/*.rb'
|
14
16
|
NewCops: enable
|
15
17
|
EnabledByDefault: true
|
16
18
|
|
@@ -18,9 +20,6 @@ AllCops:
|
|
18
20
|
Bundler/GemComment:
|
19
21
|
Enabled: false
|
20
22
|
|
21
|
-
Layout/ClassStructure:
|
22
|
-
Enabled: true
|
23
|
-
|
24
23
|
Layout/SpaceInsideHashLiteralBraces:
|
25
24
|
EnforcedStyle: no_space
|
26
25
|
|
@@ -38,12 +37,14 @@ Metrics/MethodLength:
|
|
38
37
|
Max: 15
|
39
38
|
|
40
39
|
Style/ConstantVisibility:
|
41
|
-
|
40
|
+
Exclude:
|
41
|
+
- 'lib/alba/version.rb'
|
42
42
|
|
43
43
|
Style/Copyright:
|
44
44
|
Enabled: false
|
45
45
|
|
46
|
-
|
46
|
+
# I know what I do :)
|
47
|
+
Style/DisableCopsWithinSourceCodeDirective:
|
47
48
|
Enabled: false
|
48
49
|
|
49
50
|
Style/FrozenStringLiteralComment:
|
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -5,10 +5,12 @@ gemspec
|
|
5
5
|
|
6
6
|
gem 'activesupport', require: false # For backend
|
7
7
|
gem 'coveralls', require: false # For test coverage
|
8
|
-
gem 'minitest', '~> 5.
|
9
|
-
gem 'oj', '~> 3.
|
8
|
+
gem 'minitest', '~> 5.14' # For test
|
9
|
+
gem 'oj', '~> 3.11', platform: :ruby, require: false # For backend
|
10
10
|
gem 'rake', '~> 13.0' # For test and automation
|
11
11
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
12
|
-
gem 'rubocop-minitest', '~> 0.10.
|
13
|
-
gem 'rubocop-performance', '~> 1.
|
12
|
+
gem 'rubocop-minitest', '~> 0.10.3', require: false # For lint
|
13
|
+
gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
|
14
|
+
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
14
15
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
16
|
+
gem 'yard', require: false
|
data/Gemfile.lock
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
alba (0.
|
4
|
+
alba (0.13.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
activesupport (6.
|
9
|
+
activesupport (6.1.3)
|
10
10
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
11
|
-
i18n (>=
|
12
|
-
minitest (
|
13
|
-
tzinfo (~>
|
14
|
-
zeitwerk (~> 2.
|
15
|
-
ast (2.4.
|
16
|
-
|
17
|
-
concurrent-ruby (1.1.6)
|
11
|
+
i18n (>= 1.6, < 2)
|
12
|
+
minitest (>= 5.1)
|
13
|
+
tzinfo (~> 2.0)
|
14
|
+
zeitwerk (~> 2.3)
|
15
|
+
ast (2.4.2)
|
16
|
+
concurrent-ruby (1.1.8)
|
18
17
|
coveralls (0.8.23)
|
19
18
|
json (>= 1.8, < 3)
|
20
19
|
simplecov (~> 0.16.1)
|
@@ -22,37 +21,39 @@ GEM
|
|
22
21
|
thor (>= 0.19.4, < 2.0)
|
23
22
|
tins (~> 1.6)
|
24
23
|
docile (1.3.2)
|
25
|
-
i18n (1.8.
|
24
|
+
i18n (1.8.9)
|
26
25
|
concurrent-ruby (~> 1.0)
|
27
26
|
json (2.3.1)
|
28
|
-
minitest (5.14.
|
29
|
-
oj (3.
|
30
|
-
|
31
|
-
|
32
|
-
parser (2.7.1.4)
|
27
|
+
minitest (5.14.3)
|
28
|
+
oj (3.11.2)
|
29
|
+
parallel (1.20.1)
|
30
|
+
parser (3.0.0.0)
|
33
31
|
ast (~> 2.4.1)
|
34
32
|
rainbow (3.0.0)
|
35
|
-
rake (13.0.
|
36
|
-
regexp_parser (1.
|
33
|
+
rake (13.0.3)
|
34
|
+
regexp_parser (2.1.1)
|
37
35
|
rexml (3.2.4)
|
38
|
-
rubocop (
|
36
|
+
rubocop (1.11.0)
|
39
37
|
parallel (~> 1.10)
|
40
|
-
parser (>=
|
38
|
+
parser (>= 3.0.0.0)
|
41
39
|
rainbow (>= 2.2.2, < 4.0)
|
42
|
-
regexp_parser (>= 1.
|
40
|
+
regexp_parser (>= 1.8, < 3.0)
|
43
41
|
rexml
|
44
|
-
rubocop-ast (>=
|
42
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
45
43
|
ruby-progressbar (~> 1.7)
|
46
|
-
unicode-display_width (>= 1.4.0, <
|
47
|
-
rubocop-ast (
|
48
|
-
parser (>= 2.7.1.
|
49
|
-
rubocop-minitest (0.10.
|
50
|
-
rubocop (>= 0.87)
|
51
|
-
rubocop-performance (1.
|
52
|
-
rubocop (>= 0.
|
44
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
45
|
+
rubocop-ast (1.4.1)
|
46
|
+
parser (>= 2.7.1.5)
|
47
|
+
rubocop-minitest (0.10.3)
|
48
|
+
rubocop (>= 0.87, < 2.0)
|
49
|
+
rubocop-performance (1.10.1)
|
50
|
+
rubocop (>= 0.90.0, < 2.0)
|
51
|
+
rubocop-ast (>= 0.4.0)
|
52
|
+
rubocop-rake (0.5.1)
|
53
|
+
rubocop
|
53
54
|
rubocop-sensible (0.3.0)
|
54
55
|
rubocop (>= 0.60.0)
|
55
|
-
ruby-progressbar (1.
|
56
|
+
ruby-progressbar (1.11.0)
|
56
57
|
simplecov (0.16.1)
|
57
58
|
docile (~> 1.1)
|
58
59
|
json (>= 1.8, < 3)
|
@@ -62,13 +63,13 @@ GEM
|
|
62
63
|
term-ansicolor (1.7.1)
|
63
64
|
tins (~> 1.0)
|
64
65
|
thor (1.0.1)
|
65
|
-
thread_safe (0.3.6)
|
66
66
|
tins (1.25.0)
|
67
67
|
sync
|
68
|
-
tzinfo (
|
69
|
-
|
70
|
-
unicode-display_width (
|
71
|
-
|
68
|
+
tzinfo (2.0.4)
|
69
|
+
concurrent-ruby (~> 1.0)
|
70
|
+
unicode-display_width (2.0.0)
|
71
|
+
yard (0.9.26)
|
72
|
+
zeitwerk (2.4.2)
|
72
73
|
|
73
74
|
PLATFORMS
|
74
75
|
ruby
|
@@ -77,13 +78,15 @@ DEPENDENCIES
|
|
77
78
|
activesupport
|
78
79
|
alba!
|
79
80
|
coveralls
|
80
|
-
minitest (~> 5.
|
81
|
-
oj (~> 3.
|
81
|
+
minitest (~> 5.14)
|
82
|
+
oj (~> 3.11)
|
82
83
|
rake (~> 13.0)
|
83
84
|
rubocop (>= 0.79.0)
|
84
|
-
rubocop-minitest (~> 0.10.
|
85
|
-
rubocop-performance (~> 1.
|
85
|
+
rubocop-minitest (~> 0.10.3)
|
86
|
+
rubocop-performance (~> 1.10.1)
|
87
|
+
rubocop-rake (>= 0.5.1)
|
86
88
|
rubocop-sensible (~> 0.3.0)
|
89
|
+
yard
|
87
90
|
|
88
91
|
BUNDLED WITH
|
89
|
-
2.
|
92
|
+
2.2.6
|
data/README.md
CHANGED
@@ -2,26 +2,28 @@
|
|
2
2
|
[![Build Status](https://travis-ci.com/okuramasafumi/alba.svg?branch=master)](https://travis-ci.com/okuramasafumi/alba)
|
3
3
|
[![Coverage Status](https://coveralls.io/repos/github/okuramasafumi/alba/badge.svg?branch=master)](https://coveralls.io/github/okuramasafumi/alba?branch=master)
|
4
4
|
[![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
|
5
|
+
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
|
6
|
+
![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
|
5
7
|
|
6
8
|
# Alba
|
7
9
|
|
8
10
|
`Alba` is the fastest JSON serializer for Ruby.
|
9
11
|
|
10
|
-
|
12
|
+
## Why yet another JSON serializer?
|
11
13
|
|
12
14
|
We know that there are several other JSON serializers for Ruby around, but none of them made us satisfied.
|
13
15
|
|
14
16
|
Alba has some advantages over other JSON serializers which we've wanted to have.
|
15
17
|
|
16
|
-
|
18
|
+
### Easy to understand
|
17
19
|
|
18
20
|
DSL is great. It makes the coding experience natural and intuitive. However, remembering lots of DSL requires us a lot of effort. Unfortunately, most of the existing libraries have implemented their features via DSL and it's not easy to understand how they behave entirely. Alba's core DSL are only four (`attributes`, `attribute`, `one` and `many`) so it's easy to understand how to use.
|
19
21
|
|
20
|
-
Alba is also understandable internally. The codebase is much smaller than the alternatives. In fact, it's
|
22
|
+
Alba is also understandable internally. The codebase is much smaller than the alternatives. In fact, it's about 330 lines of code. Look at the code on [GitHub](https://github.com/okuramasafumi/alba/tree/master/lib) and you'll be surprised how simple it is!
|
21
23
|
|
22
|
-
|
24
|
+
### Performance
|
23
25
|
|
24
|
-
Alba is faster than most of the alternatives. We have a [benchmark](https://
|
26
|
+
Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
|
25
27
|
|
26
28
|
## Installation
|
27
29
|
|
@@ -43,6 +45,33 @@ Or install it yourself as:
|
|
43
45
|
|
44
46
|
Alba supports CRuby 2.5.7 and higher and latest TruffleRuby.
|
45
47
|
|
48
|
+
## Documentation
|
49
|
+
|
50
|
+
You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramasafumi/alba).
|
51
|
+
|
52
|
+
## Features
|
53
|
+
|
54
|
+
* Resource-based serialization
|
55
|
+
* Arbitrary attribute definition
|
56
|
+
* One and many association with the ability to define them inline
|
57
|
+
* Adding condition and filter to association
|
58
|
+
* Parameters can be injected and used in attributes and associations
|
59
|
+
* Setting root key separately in Serializer
|
60
|
+
* Adding metadata
|
61
|
+
* Selectable backend
|
62
|
+
* Key transformation
|
63
|
+
* No runtime dependencies
|
64
|
+
|
65
|
+
## Anti features
|
66
|
+
|
67
|
+
* Sorting keys
|
68
|
+
* Class level support of parameters
|
69
|
+
* Supporting all existing JSON encoder/decoder
|
70
|
+
* Cache
|
71
|
+
* [JSON:API](https://jsonapi.org) support
|
72
|
+
* Association name inflection
|
73
|
+
* And many others
|
74
|
+
|
46
75
|
## Usage
|
47
76
|
|
48
77
|
### Configuration
|
@@ -129,7 +158,7 @@ class ArticleResource
|
|
129
158
|
attributes :title
|
130
159
|
end
|
131
160
|
|
132
|
-
class
|
161
|
+
class UserResource
|
133
162
|
include Alba::Resource
|
134
163
|
|
135
164
|
attributes :id
|
@@ -143,7 +172,7 @@ user.articles << article1
|
|
143
172
|
article2 = Article.new(2, 'Super nice', 'Really nice!')
|
144
173
|
user.articles << article2
|
145
174
|
|
146
|
-
|
175
|
+
UserResource.new(user).serialize
|
147
176
|
# => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
|
148
177
|
```
|
149
178
|
|
@@ -193,6 +222,76 @@ RestrictedFooResouce.new(foo).serialize
|
|
193
222
|
end
|
194
223
|
```
|
195
224
|
|
225
|
+
### Attribute key transformation
|
226
|
+
|
227
|
+
** Note: You need to install `active_support` gem to use `transform_keys` DSL.
|
228
|
+
|
229
|
+
With `active_support` installed, you can transform attribute keys.
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
class User
|
233
|
+
attr_reader :id, :first_name, :last_name
|
234
|
+
|
235
|
+
def initialize(id, first_name, last_name)
|
236
|
+
@id = id
|
237
|
+
@first_name = first_name
|
238
|
+
@last_name = last_name
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
class UserResource
|
243
|
+
include Alba::Resource
|
244
|
+
|
245
|
+
attributes :id, :first_name, :last_name
|
246
|
+
|
247
|
+
transform_keys :lower_camel
|
248
|
+
end
|
249
|
+
|
250
|
+
user = User.new(1, 'Masafumi', 'Okura')
|
251
|
+
UserResourceCamel.new(user).serialize
|
252
|
+
# => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
|
253
|
+
```
|
254
|
+
|
255
|
+
Supported transformation types are :camel, :lower_camel and :dash.
|
256
|
+
|
257
|
+
### Filtering attributes
|
258
|
+
|
259
|
+
You can filter attributes by overriding `Alba::Resource#converter` method, but it's a bit tricky.
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
class User
|
263
|
+
attr_accessor :id, :name, :email, :created_at, :updated_at
|
264
|
+
|
265
|
+
def initialize(id, name, email)
|
266
|
+
@id = id
|
267
|
+
@name = name
|
268
|
+
@email = email
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class UserResource
|
273
|
+
include Alba::Resource
|
274
|
+
|
275
|
+
attributes :id, :name, :email
|
276
|
+
|
277
|
+
private
|
278
|
+
|
279
|
+
# Here using `Proc#>>` method to compose a proc from `super`
|
280
|
+
def converter
|
281
|
+
super >> proc { |hash| hash.compact }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
user = User.new(1, nil, nil)
|
286
|
+
UserResource.new(user).serialize # => '{"id":1}'
|
287
|
+
|
288
|
+
|
289
|
+
```
|
290
|
+
|
291
|
+
The key part is the use of `Proc#>>` since `Alba::Resource#converter` returns a `Proc` which contains the basic logic and it's impossible to change its behavior by just overriding the method.
|
292
|
+
|
293
|
+
It's not recommended to swap the whole conversion logic. It's recommended to always call `super` when you override `converter`.
|
294
|
+
|
196
295
|
## Comparison
|
197
296
|
|
198
297
|
Alba is faster than alternatives.
|
data/benchmark/local.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
# Benchmark script to run varieties of JSON serializers
|
2
|
+
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
3
|
+
|
4
|
+
require "bundler/inline"
|
5
|
+
|
6
|
+
gemfile(true) do
|
7
|
+
source "https://rubygems.org"
|
8
|
+
|
9
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
10
|
+
|
11
|
+
gem "activerecord", "6.1.3"
|
12
|
+
gem "sqlite3"
|
13
|
+
gem "jbuilder"
|
14
|
+
gem "active_model_serializers"
|
15
|
+
gem "blueprinter"
|
16
|
+
gem "representable"
|
17
|
+
gem "alba", path: '../'
|
18
|
+
gem "oj"
|
19
|
+
gem "multi_json"
|
20
|
+
end
|
21
|
+
|
22
|
+
require "active_record"
|
23
|
+
require "sqlite3"
|
24
|
+
require "logger"
|
25
|
+
require "oj"
|
26
|
+
Oj.optimize_rails
|
27
|
+
|
28
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
29
|
+
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
30
|
+
|
31
|
+
ActiveRecord::Schema.define do
|
32
|
+
create_table :posts, force: true do |t|
|
33
|
+
t.string :body
|
34
|
+
end
|
35
|
+
|
36
|
+
create_table :comments, force: true do |t|
|
37
|
+
t.integer :post_id
|
38
|
+
t.string :body
|
39
|
+
t.integer :commenter_id
|
40
|
+
end
|
41
|
+
|
42
|
+
create_table :users, force: true do |t|
|
43
|
+
t.string :name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Post < ActiveRecord::Base
|
48
|
+
has_many :comments
|
49
|
+
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
50
|
+
|
51
|
+
def attributes
|
52
|
+
{id: nil, body: nil, commenter_names: commenter_names}
|
53
|
+
end
|
54
|
+
|
55
|
+
def commenter_names
|
56
|
+
commenters.pluck(:name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Comment < ActiveRecord::Base
|
61
|
+
belongs_to :post
|
62
|
+
belongs_to :commenter, class_name: 'User'
|
63
|
+
|
64
|
+
def attributes
|
65
|
+
{id: nil, body: nil}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class User < ActiveRecord::Base
|
70
|
+
has_many :comments
|
71
|
+
end
|
72
|
+
|
73
|
+
require "alba"
|
74
|
+
Alba.backend = :oj
|
75
|
+
|
76
|
+
class AlbaCommentResource
|
77
|
+
include ::Alba::Resource
|
78
|
+
attributes :id, :body
|
79
|
+
end
|
80
|
+
|
81
|
+
class AlbaPostResource
|
82
|
+
include ::Alba::Resource
|
83
|
+
attributes :id, :body
|
84
|
+
many :comments, resource: AlbaCommentResource
|
85
|
+
attribute :commenter_names do |post|
|
86
|
+
post.commenters.pluck(:name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
require "jbuilder"
|
91
|
+
class Post
|
92
|
+
def to_builder
|
93
|
+
Jbuilder.new do |post|
|
94
|
+
post.call(self, :id, :body, :comments, :commenter_names)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def commenter_names
|
99
|
+
commenters.pluck(:name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Comment
|
104
|
+
def to_builder
|
105
|
+
Jbuilder.new do |comment|
|
106
|
+
comment.call(self, :id, :body)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
require "active_model_serializers"
|
112
|
+
|
113
|
+
class AMSCommentSerializer < ActiveModel::Serializer
|
114
|
+
attributes :id, :body
|
115
|
+
end
|
116
|
+
|
117
|
+
class AMSPostSerializer < ActiveModel::Serializer
|
118
|
+
attributes :id, :body
|
119
|
+
has_many :comments, serializer: AMSCommentSerializer
|
120
|
+
attribute :commenter_names
|
121
|
+
def commenter_names
|
122
|
+
object.commenters.pluck(:name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
require "blueprinter"
|
127
|
+
|
128
|
+
class CommentBlueprint < Blueprinter::Base
|
129
|
+
fields :id, :body
|
130
|
+
end
|
131
|
+
|
132
|
+
class PostBlueprint < Blueprinter::Base
|
133
|
+
fields :id, :body, :commenter_names
|
134
|
+
association :comments, blueprint: CommentBlueprint
|
135
|
+
def commenter_names
|
136
|
+
commenters.pluck(:name)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
require "representable"
|
141
|
+
|
142
|
+
class CommentRepresenter < Representable::Decorator
|
143
|
+
include Representable::JSON
|
144
|
+
|
145
|
+
property :id
|
146
|
+
property :body
|
147
|
+
end
|
148
|
+
|
149
|
+
class PostRepresenter < Representable::Decorator
|
150
|
+
include Representable::JSON
|
151
|
+
|
152
|
+
property :id
|
153
|
+
property :body
|
154
|
+
property :commenter_names
|
155
|
+
collection :comments
|
156
|
+
|
157
|
+
def commenter_names
|
158
|
+
commenters.pluck(:name)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
post = Post.create!(body: 'post')
|
163
|
+
user1 = User.create!(name: 'John')
|
164
|
+
user2 = User.create!(name: 'Jane')
|
165
|
+
post.comments.create!(commenter: user1, body: 'Comment1')
|
166
|
+
post.comments.create!(commenter: user2, body: 'Comment2')
|
167
|
+
post.reload
|
168
|
+
|
169
|
+
alba = Proc.new { AlbaPostResource.new(post).serialize }
|
170
|
+
jbuilder = Proc.new { post.to_builder.target! }
|
171
|
+
ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
|
172
|
+
rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
|
173
|
+
blueprinter = Proc.new { PostBlueprint.render(post) }
|
174
|
+
representable = Proc.new { PostRepresenter.new(post).to_json }
|
175
|
+
alba_inline = Proc.new do
|
176
|
+
Alba.serialize(post) do
|
177
|
+
attributes :id, :body
|
178
|
+
attribute :commenter_names do |post|
|
179
|
+
post.commenters.pluck(:name)
|
180
|
+
end
|
181
|
+
many :comments do
|
182
|
+
attributes :id, :body
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
[alba, jbuilder, ams, rails, blueprinter, representable, alba_inline].each {|x| puts x.call }
|
187
|
+
|
188
|
+
require 'benchmark'
|
189
|
+
time = 1000
|
190
|
+
Benchmark.bmbm do |x|
|
191
|
+
x.report(:alba) { time.times(&alba) }
|
192
|
+
x.report(:jbuilder) { time.times(&jbuilder) }
|
193
|
+
x.report(:ams) { time.times(&ams) }
|
194
|
+
x.report(:rails) { time.times(&rails) }
|
195
|
+
x.report(:blueprinter) { time.times(&blueprinter) }
|
196
|
+
x.report(:representable) { time.times(&representable) }
|
197
|
+
x.report(:alba_inline) { time.times(&alba_inline) }
|
198
|
+
end
|
data/lib/alba.rb
CHANGED
@@ -1,21 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require_relative 'alba/version'
|
2
|
+
require_relative 'alba/serializer'
|
3
|
+
require_relative 'alba/resource'
|
4
4
|
|
5
5
|
# Core module
|
6
6
|
module Alba
|
7
|
+
# Base class for Errors
|
7
8
|
class Error < StandardError; end
|
9
|
+
|
10
|
+
# Error class for backend which is not supported
|
8
11
|
class UnsupportedBackend < Error; end
|
9
12
|
|
10
13
|
class << self
|
11
14
|
attr_reader :backend, :encoder
|
12
15
|
attr_accessor :default_serializer
|
13
16
|
|
17
|
+
# Set the backend, which actually serializes object into JSON
|
18
|
+
#
|
19
|
+
# @param backend [#to_sym, nil] the name of the backend
|
20
|
+
# Possible values are `oj`, `active_support`, `default`, `json` and nil
|
21
|
+
# @return [Proc] the proc to encode object into JSON
|
22
|
+
# @raise [Alba::UnsupportedBackend] if backend is not supported
|
14
23
|
def backend=(backend)
|
15
24
|
@backend = backend&.to_sym
|
16
25
|
set_encoder
|
17
26
|
end
|
18
27
|
|
28
|
+
# Serialize the object with inline definitions
|
29
|
+
#
|
30
|
+
# @param object [Object] the object to be serialized
|
31
|
+
# @param with [nil, Proc, Alba::Serializer] selializer
|
32
|
+
# @param block [Block] resource block
|
33
|
+
# @return [String] serialized JSON string
|
34
|
+
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
19
35
|
def serialize(object, with: nil, &block)
|
20
36
|
raise ArgumentError, 'Block required' unless block
|
21
37
|
|
@@ -63,9 +79,9 @@ module Alba
|
|
63
79
|
|
64
80
|
def resource_class
|
65
81
|
@resource_class ||= begin
|
66
|
-
|
67
|
-
|
68
|
-
|
82
|
+
klass = Class.new
|
83
|
+
klass.include(Alba::Resource)
|
84
|
+
end
|
69
85
|
end
|
70
86
|
end
|
71
87
|
|
data/lib/alba/association.rb
CHANGED
@@ -2,6 +2,10 @@ module Alba
|
|
2
2
|
# Base class for `One` and `Many`
|
3
3
|
# Child class should implement `to_hash` method
|
4
4
|
class Association
|
5
|
+
# @param name [Symbol] name of the method to fetch association
|
6
|
+
# @param condition [Proc] a proc filtering data
|
7
|
+
# @param resource [Class<Alba::Resource>] a resource class for the association
|
8
|
+
# @param block [Block] used to define resource when resource arg is absent
|
5
9
|
def initialize(name:, condition: nil, resource: nil, &block)
|
6
10
|
@name = name
|
7
11
|
@condition = condition
|
@@ -10,6 +14,7 @@ module Alba
|
|
10
14
|
raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
|
11
15
|
end
|
12
16
|
|
17
|
+
# @abstract
|
13
18
|
def to_hash
|
14
19
|
:not_implemented
|
15
20
|
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
case transform_type
|
20
|
+
when :camel
|
21
|
+
ActiveSupport::Inflector.camelize(key)
|
22
|
+
when :lower_camel
|
23
|
+
ActiveSupport::Inflector.camelize(key, false)
|
24
|
+
when :dash
|
25
|
+
ActiveSupport::Inflector.dasherize(key)
|
26
|
+
else
|
27
|
+
raise ::Alba::Error, "Unknown transform_type: #{transform_type}. Supported transform_type are :camel, :lower_camel and :dash."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/alba/many.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
-
|
1
|
+
require_relative 'association'
|
2
2
|
|
3
3
|
module Alba
|
4
4
|
# Representing many association
|
5
5
|
class Many < Association
|
6
|
+
# Recursively converts objects into an Array of Hashes
|
7
|
+
#
|
8
|
+
# @param target [Object] the object having an association method
|
9
|
+
# @param params [Hash] user-given Hash for arbitrary data
|
10
|
+
# @return [Array<Hash>]
|
6
11
|
def to_hash(target, params: {})
|
7
12
|
objects = target.public_send(@name)
|
8
|
-
objects = @condition.call(objects) if @condition
|
13
|
+
objects = @condition.call(objects, params) if @condition
|
14
|
+
return if objects.nil?
|
15
|
+
|
9
16
|
objects.map { |o| @resource.new(o, params: params).to_hash }
|
10
17
|
end
|
11
18
|
end
|
data/lib/alba/one.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
-
|
1
|
+
require_relative 'association'
|
2
2
|
|
3
3
|
module Alba
|
4
4
|
# Representing one association
|
5
5
|
class One < Association
|
6
|
+
# Recursively converts an object into a Hash
|
7
|
+
#
|
8
|
+
# @param target [Object] the object having an association method
|
9
|
+
# @param params [Hash] user-given Hash for arbitrary data
|
10
|
+
# @return [Hash]
|
6
11
|
def to_hash(target, params: {})
|
7
12
|
object = target.public_send(@name)
|
8
|
-
object = @condition.call(object) if @condition
|
13
|
+
object = @condition.call(object, params) if @condition
|
14
|
+
return if object.nil?
|
15
|
+
|
9
16
|
@resource.new(object, params: params).to_hash
|
10
17
|
end
|
11
18
|
end
|
data/lib/alba/resource.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require_relative 'serializer'
|
2
|
+
require_relative 'one'
|
3
|
+
require_relative 'many'
|
4
4
|
|
5
5
|
module Alba
|
6
6
|
# This module represents what should be serialized
|
7
7
|
module Resource
|
8
|
-
|
8
|
+
# @!parse include InstanceMethods
|
9
|
+
# @!parse extend ClassMethods
|
10
|
+
DSLS = {_attributes: {}, _serializer: nil, _key: nil, _transform_keys: nil}.freeze
|
11
|
+
private_constant :DSLS
|
12
|
+
|
13
|
+
# @private
|
9
14
|
def self.included(base)
|
10
15
|
super
|
11
16
|
base.class_eval do
|
@@ -22,12 +27,18 @@ module Alba
|
|
22
27
|
module InstanceMethods
|
23
28
|
attr_reader :object, :_key, :params
|
24
29
|
|
30
|
+
# @param object [Object] the object to be serialized
|
31
|
+
# @param params [Hash] user-given Hash for arbitrary data
|
25
32
|
def initialize(object, params: {})
|
26
33
|
@object = object
|
27
|
-
@params = params
|
34
|
+
@params = params.freeze
|
28
35
|
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
|
29
36
|
end
|
30
37
|
|
38
|
+
# Get serializer with `with` argument and serialize self with it
|
39
|
+
#
|
40
|
+
# @param with [nil, Proc, Alba::Serializer] selializer
|
41
|
+
# @return [String] serialized JSON string
|
31
42
|
def serialize(with: nil)
|
32
43
|
serializer = case with
|
33
44
|
when nil
|
@@ -42,31 +53,49 @@ module Alba
|
|
42
53
|
serializer.new(self).serialize
|
43
54
|
end
|
44
55
|
|
56
|
+
# A Hash for serialization
|
57
|
+
#
|
58
|
+
# @return [Hash]
|
45
59
|
def serializable_hash
|
46
60
|
collection? ? @object.map(&converter) : converter.call(@object)
|
47
61
|
end
|
48
62
|
alias to_hash serializable_hash
|
49
63
|
|
64
|
+
# @return [Symbol]
|
50
65
|
def key
|
51
66
|
@_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
|
52
67
|
end
|
53
68
|
|
54
69
|
private
|
55
70
|
|
71
|
+
# rubocop:disable Style/MethodCalledOnDoEndBlock
|
56
72
|
def converter
|
57
73
|
lambda do |resource|
|
58
|
-
@_attributes.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
74
|
+
@_attributes.map do |key, attribute|
|
75
|
+
[transform_key(key), fetch_attribute(resource, attribute)]
|
76
|
+
end.to_h
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# rubocop:enable Style/MethodCalledOnDoEndBlock
|
80
|
+
|
81
|
+
# Override this method to supply custom key transform method
|
82
|
+
def transform_key(key)
|
83
|
+
return key unless @_transform_keys
|
84
|
+
|
85
|
+
require_relative 'key_transformer'
|
86
|
+
KeyTransformer.transform(key, @_transform_keys)
|
87
|
+
end
|
88
|
+
|
89
|
+
def fetch_attribute(resource, attribute)
|
90
|
+
case attribute
|
91
|
+
when Symbol
|
92
|
+
resource.public_send attribute
|
93
|
+
when Proc
|
94
|
+
instance_exec(resource, &attribute)
|
95
|
+
when Alba::One, Alba::Many
|
96
|
+
attribute.to_hash(resource, params: params)
|
97
|
+
else
|
98
|
+
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
70
99
|
end
|
71
100
|
end
|
72
101
|
|
@@ -91,43 +120,86 @@ module Alba
|
|
91
120
|
module ClassMethods
|
92
121
|
attr_reader(*DSLS.keys)
|
93
122
|
|
123
|
+
# @private
|
94
124
|
def inherited(subclass)
|
95
125
|
super
|
96
126
|
DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
|
97
127
|
end
|
98
128
|
|
129
|
+
# Set multiple attributes at once
|
130
|
+
#
|
131
|
+
# @param attrs [Array<String, Symbol>]
|
99
132
|
def attributes(*attrs)
|
100
133
|
attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
|
101
134
|
end
|
102
135
|
|
136
|
+
# Set an attribute with the given block
|
137
|
+
#
|
138
|
+
# @param name [String, Symbol] key name
|
139
|
+
# @param block [Block] the block called during serialization
|
140
|
+
# @raise [ArgumentError] if block is absent
|
103
141
|
def attribute(name, &block)
|
104
142
|
raise ArgumentError, 'No block given in attribute method' unless block
|
105
143
|
|
106
144
|
@_attributes[name.to_sym] = block
|
107
145
|
end
|
108
146
|
|
147
|
+
# Set One association
|
148
|
+
#
|
149
|
+
# @param name [String, Symbol]
|
150
|
+
# @param condition [Proc]
|
151
|
+
# @param resource [Class<Alba::Resource>]
|
152
|
+
# @param key [String, Symbol] used as key when given
|
153
|
+
# @param block [Block]
|
154
|
+
# @see Alba::One#initialize
|
109
155
|
def one(name, condition = nil, resource: nil, key: nil, &block)
|
110
156
|
@_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
|
111
157
|
end
|
158
|
+
alias has_one one
|
112
159
|
|
160
|
+
# Set Many association
|
161
|
+
#
|
162
|
+
# @param name [String, Symbol]
|
163
|
+
# @param condition [Proc]
|
164
|
+
# @param resource [Class<Alba::Resource>]
|
165
|
+
# @param key [String, Symbol] used as key when given
|
166
|
+
# @param block [Block]
|
167
|
+
# @see Alba::Many#initialize
|
113
168
|
def many(name, condition = nil, resource: nil, key: nil, &block)
|
114
169
|
@_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
|
115
170
|
end
|
171
|
+
alias has_many many
|
116
172
|
|
173
|
+
# Set serializer for the resource
|
174
|
+
#
|
175
|
+
# @param name [Alba::Serializer]
|
117
176
|
def serializer(name)
|
118
177
|
@_serializer = name <= Alba::Serializer ? name : nil
|
119
178
|
end
|
120
179
|
|
180
|
+
# Set key
|
181
|
+
#
|
182
|
+
# @param key [String, Symbol]
|
121
183
|
def key(key)
|
122
184
|
@_key = key.to_sym
|
123
185
|
end
|
124
186
|
|
187
|
+
# Delete attributes
|
125
188
|
# Use this DSL in child class to ignore certain attributes
|
189
|
+
#
|
190
|
+
# @param attributes [Array<String, Symbol>]
|
126
191
|
def ignoring(*attributes)
|
127
192
|
attributes.each do |attr_name|
|
128
193
|
@_attributes.delete(attr_name.to_sym)
|
129
194
|
end
|
130
195
|
end
|
196
|
+
|
197
|
+
# Transform keys as specified type
|
198
|
+
#
|
199
|
+
# @param type [String, Symbol]
|
200
|
+
def transform_keys(type)
|
201
|
+
@_transform_keys = type.to_sym
|
202
|
+
end
|
131
203
|
end
|
132
204
|
end
|
133
205
|
end
|
data/lib/alba/serializer.rb
CHANGED
@@ -1,26 +1,34 @@
|
|
1
1
|
module Alba
|
2
2
|
# This module represents how a resource should be serialized.
|
3
3
|
module Serializer
|
4
|
+
# @!parse include InstanceMethods
|
5
|
+
# @!parse extend ClassMethods
|
6
|
+
|
7
|
+
# @private
|
4
8
|
def self.included(base)
|
5
9
|
super
|
6
|
-
base.
|
7
|
-
|
8
|
-
@_metadata = {} unless instance_variable_defined?('@_metadata')
|
9
|
-
end
|
10
|
+
base.instance_variable_set('@_opts', {}) unless base.instance_variable_defined?('@_opts')
|
11
|
+
base.instance_variable_set('@_metadata', {}) unless base.instance_variable_defined?('@_metadata')
|
10
12
|
base.include InstanceMethods
|
11
13
|
base.extend ClassMethods
|
12
14
|
end
|
13
15
|
|
14
16
|
# Instance methods
|
15
17
|
module InstanceMethods
|
18
|
+
# @param resource [Alba::Resource]
|
16
19
|
def initialize(resource)
|
17
20
|
@resource = resource
|
18
21
|
@hash = resource.serializable_hash
|
19
22
|
@hash = {key.to_sym => @hash} if key
|
23
|
+
return if metadata.empty?
|
24
|
+
|
20
25
|
# @hash is either Hash or Array
|
21
26
|
@hash.is_a?(Hash) ? @hash.merge!(metadata.to_h) : @hash << metadata
|
22
27
|
end
|
23
28
|
|
29
|
+
# Use real encoder to actually serialize to JSON
|
30
|
+
#
|
31
|
+
# @return [String] JSON string
|
24
32
|
def serialize
|
25
33
|
Alba.encoder.call(@hash)
|
26
34
|
end
|
@@ -42,17 +50,25 @@ module Alba
|
|
42
50
|
module ClassMethods
|
43
51
|
attr_reader :_opts, :_metadata
|
44
52
|
|
53
|
+
# @private
|
45
54
|
def inherited(subclass)
|
46
55
|
super
|
47
56
|
%w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", public_send(name).clone) }
|
48
57
|
end
|
49
58
|
|
59
|
+
# Set options, currently key only
|
60
|
+
#
|
61
|
+
# @param key [Boolean, Symbol]
|
50
62
|
def set(key: false)
|
51
63
|
@_opts[:key] = key
|
52
64
|
end
|
53
65
|
|
66
|
+
# Set metadata
|
67
|
+
#
|
68
|
+
# @param name [String, Symbol] key for the metadata
|
69
|
+
# @param block [Block] the content of the metadata
|
54
70
|
def metadata(name, &block)
|
55
|
-
@_metadata[name] = block
|
71
|
+
@_metadata[name.to_sym] = block
|
56
72
|
end
|
57
73
|
end
|
58
74
|
end
|
data/lib/alba/version.rb
CHANGED
data/sider.yml
CHANGED
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: 0.
|
4
|
+
version: 0.13.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKURA Masafumi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Alba is designed to be a simple, easy to use and fast alternative to
|
14
14
|
existing JSON serializers. Its performance is better than almost all gems which
|
@@ -19,9 +19,10 @@ executables: []
|
|
19
19
|
extensions: []
|
20
20
|
extra_rdoc_files: []
|
21
21
|
files:
|
22
|
+
- ".github/workflows/main.yml"
|
22
23
|
- ".gitignore"
|
23
24
|
- ".rubocop.yml"
|
24
|
-
- ".
|
25
|
+
- ".yardopts"
|
25
26
|
- CODE_OF_CONDUCT.md
|
26
27
|
- Gemfile
|
27
28
|
- Gemfile.lock
|
@@ -29,10 +30,12 @@ files:
|
|
29
30
|
- README.md
|
30
31
|
- Rakefile
|
31
32
|
- alba.gemspec
|
33
|
+
- benchmark/local.rb
|
32
34
|
- bin/console
|
33
35
|
- bin/setup
|
34
36
|
- lib/alba.rb
|
35
37
|
- lib/alba/association.rb
|
38
|
+
- lib/alba/key_transformer.rb
|
36
39
|
- lib/alba/many.rb
|
37
40
|
- lib/alba/one.rb
|
38
41
|
- lib/alba/resource.rb
|
@@ -61,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
64
|
- !ruby/object:Gem::Version
|
62
65
|
version: '0'
|
63
66
|
requirements: []
|
64
|
-
rubygems_version: 3.
|
67
|
+
rubygems_version: 3.2.11
|
65
68
|
signing_key:
|
66
69
|
specification_version: 4
|
67
70
|
summary: Alba is the fastest JSON serializer for Ruby.
|