alba 0.13.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/main.yml +10 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +33 -2
- data/.yardopts +2 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +10 -4
- data/README.md +308 -45
- data/Rakefile +4 -1
- data/SECURITY.md +12 -0
- data/alba.gemspec +3 -3
- data/benchmark/collection.rb +392 -0
- data/benchmark/single_resource.rb +370 -0
- data/codecov.yml +8 -0
- data/gemfiles/all.gemfile +19 -0
- data/gemfiles/without_active_support.gemfile +17 -0
- data/gemfiles/without_oj.gemfile +17 -0
- data/lib/alba.rb +57 -19
- data/lib/alba/association.rb +30 -7
- data/lib/alba/default_inflector.rb +36 -0
- data/lib/alba/key_transform_factory.rb +33 -0
- data/lib/alba/many.rb +7 -5
- data/lib/alba/one.rb +7 -5
- data/lib/alba/resource.rb +170 -63
- data/lib/alba/typed_attribute.rb +64 -0
- data/lib/alba/version.rb +1 -1
- data/sider.yml +2 -4
- metadata +21 -12
- data/Gemfile.lock +0 -92
- data/benchmark/local.rb +0 -198
- data/lib/alba/key_transformer.rb +0 -31
- data/lib/alba/serializer.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2c2e8c84ddccf4db9f9f18dd155361567f2a1733c55f817f5b4d97d573675d5
|
4
|
+
data.tar.gz: 3f11dd120c8b57aef909d79f6570482b8af810cd19c57ddcaa0d7b2ee70c2d6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8948a681fb88b3d84e3751e6df0f5ca28314d6c9b5e183666780be95b14fd3c7728e6ad1eb13309d4b7114115b56edbb28fe929c2376bbcc5b762846a7d00404
|
7
|
+
data.tar.gz: 9a2430efc4cf3b7a24b27bb40228dad67e00bd0cb10c9e0fc3764eb49e9caafda5b6d9fa0cc0335c77edc42e0a60416b20343dee5cee23dcfbfe350bed90e9a6
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
name: Bug report
|
3
|
+
about: Create a report to help us improve
|
4
|
+
title: ''
|
5
|
+
labels: bug
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
## Describe the bug
|
11
|
+
A clear and concise description of what the bug is.
|
12
|
+
|
13
|
+
## To Reproduce
|
14
|
+
Steps to reproduce the behavior:
|
15
|
+
|
16
|
+
## Expected behavior
|
17
|
+
A clear and concise description of what you expected to happen.
|
18
|
+
|
19
|
+
## Actual behavior
|
20
|
+
A clear and concise description of what actually happened.
|
21
|
+
|
22
|
+
## Environment
|
23
|
+
- Ruby version:
|
24
|
+
|
25
|
+
## Additional context
|
26
|
+
Add any other context about the problem here.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
name: Feature request
|
3
|
+
about: Suggest an idea for this project
|
4
|
+
title: ''
|
5
|
+
labels: enhancement
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
## Is your feature request related to a problem? Please describe.
|
11
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
12
|
+
|
13
|
+
## Describe the solution you'd like
|
14
|
+
A clear and concise description of what you want to happen.
|
15
|
+
|
16
|
+
## Describe alternatives you've considered
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
18
|
+
|
19
|
+
## Additional context
|
20
|
+
Add any other context or screenshots about the feature request here.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
version: 2
|
2
|
+
updates:
|
3
|
+
- package-ecosystem: bundler
|
4
|
+
directory: "/"
|
5
|
+
schedule:
|
6
|
+
interval: daily
|
7
|
+
time: "20:00"
|
8
|
+
open-pull-requests-limit: 10
|
9
|
+
ignore:
|
10
|
+
- dependency-name: rubocop
|
11
|
+
versions:
|
12
|
+
- 1.12.0
|
13
|
+
- 1.9.0
|
14
|
+
- dependency-name: rubocop-performance
|
15
|
+
versions:
|
16
|
+
- 1.10.0
|
17
|
+
- 1.10.2
|
18
|
+
- dependency-name: oj
|
19
|
+
versions:
|
20
|
+
- 3.11.3
|
21
|
+
- dependency-name: minitest
|
22
|
+
versions:
|
23
|
+
- 5.14.4
|
24
|
+
- dependency-name: activesupport
|
25
|
+
versions:
|
26
|
+
- 6.1.2
|
data/.github/workflows/main.yml
CHANGED
@@ -8,11 +8,16 @@ jobs:
|
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
10
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
11
|
-
ruby: [2.5, 2.6, 2.7, 3.0, head, truffleruby]
|
11
|
+
ruby: [2.5, 2.6, 2.7, 3.0, head, jruby, truffleruby]
|
12
|
+
gemfile: [all, without_active_support, without_oj]
|
12
13
|
exclude:
|
14
|
+
- os: windows-latest
|
15
|
+
ruby: jruby
|
13
16
|
- os: windows-latest
|
14
17
|
ruby: truffleruby
|
15
18
|
runs-on: ${{ matrix.os }}
|
19
|
+
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
20
|
+
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
16
21
|
steps:
|
17
22
|
- uses: actions/checkout@v2
|
18
23
|
- name: Set up Ruby
|
@@ -23,3 +28,7 @@ jobs:
|
|
23
28
|
- name: Run the default task
|
24
29
|
run: |
|
25
30
|
bundle exec rake
|
31
|
+
- name: CodeCov
|
32
|
+
uses: codecov/codecov-action@v1
|
33
|
+
with:
|
34
|
+
files: ./coverage/coverage.xml
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -15,11 +15,21 @@ AllCops:
|
|
15
15
|
- 'benchmark/**/*.rb'
|
16
16
|
NewCops: enable
|
17
17
|
EnabledByDefault: true
|
18
|
+
TargetRubyVersion: 2.5
|
18
19
|
|
19
20
|
# Oneline comment is not valid so until it gets valid, we disable it
|
20
21
|
Bundler/GemComment:
|
21
22
|
Enabled: false
|
22
23
|
|
24
|
+
# We'd like to write something like:
|
25
|
+
# assert_equal(
|
26
|
+
# expected,
|
27
|
+
# actual
|
28
|
+
# )
|
29
|
+
Layout/RedundantLineBreak:
|
30
|
+
Exclude:
|
31
|
+
- 'test/**/*'
|
32
|
+
|
23
33
|
Layout/SpaceInsideHashLiteralBraces:
|
24
34
|
EnforcedStyle: no_space
|
25
35
|
|
@@ -29,13 +39,30 @@ Layout/MultilineAssignmentLayout:
|
|
29
39
|
Lint/ConstantResolution:
|
30
40
|
Enabled: false
|
31
41
|
|
32
|
-
|
42
|
+
# In test code we don't care about the metrics!
|
43
|
+
Metrics:
|
33
44
|
Exclude:
|
34
|
-
- 'test
|
45
|
+
- 'test/**/*.rb'
|
35
46
|
|
36
47
|
Metrics/MethodLength:
|
37
48
|
Max: 15
|
38
49
|
|
50
|
+
# `Resource` module is a core module and its length tends to be long...
|
51
|
+
Metrics/ModuleLength:
|
52
|
+
Exclude:
|
53
|
+
- 'lib/alba/resource.rb'
|
54
|
+
|
55
|
+
# Resource class includes DSLs, which tend to accept long list of parameters
|
56
|
+
Metrics/ParameterLists:
|
57
|
+
Exclude:
|
58
|
+
- 'lib/alba/resource.rb'
|
59
|
+
- 'test/**/*.rb'
|
60
|
+
|
61
|
+
# We need to eval resource code to test errors on resource classes
|
62
|
+
Security/Eval:
|
63
|
+
Exclude:
|
64
|
+
- 'test/**/*.rb'
|
65
|
+
|
39
66
|
Style/ConstantVisibility:
|
40
67
|
Exclude:
|
41
68
|
- 'lib/alba/version.rb'
|
@@ -55,3 +82,7 @@ Style/InlineComment:
|
|
55
82
|
|
56
83
|
Style/MethodCallWithArgsParentheses:
|
57
84
|
Enabled: false
|
85
|
+
|
86
|
+
# There are so many cases we just want `if` expression!
|
87
|
+
Style/MissingElse:
|
88
|
+
EnforcedStyle: case
|
data/.yardopts
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [1.3.0] 2021-05-31
|
10
|
+
|
11
|
+
- [Perf] Improve performance for `many` [641d8f9]
|
12
|
+
- https://github.com/okuramasafumi/alba/pull/125
|
13
|
+
- [Feat] Add custom inflector feature (#126) [ad73291]
|
14
|
+
- https://github.com/okuramasafumi/alba/pull/126
|
15
|
+
- Thank you @wuarmin !
|
16
|
+
- [Feat] Support params in if condition [6e9915e]
|
17
|
+
- https://github.com/okuramasafumi/alba/pull/128
|
18
|
+
- [Fix] fundamentally broken "circular association control" [fbbc9a1]
|
19
|
+
- https://github.com/okuramasafumi/alba/pull/130
|
20
|
+
|
21
|
+
## [1.2.0] 2021-05-09
|
22
|
+
|
23
|
+
- [Fix] multiple word key inference [6c18e73]
|
24
|
+
- https://github.com/okuramasafumi/alba/pull/120
|
25
|
+
- Thank you @alfonsojimenez !
|
26
|
+
- [Feat] Add `Alba.enable_root_key_transformation!` [f172839]
|
27
|
+
- https://github.com/okuramasafumi/alba/pull/121
|
28
|
+
- [Feat] Implement type validation and auto conversion [cbe00c7]
|
29
|
+
- https://github.com/okuramasafumi/alba/pull/122
|
30
|
+
|
31
|
+
## [1.1.0] - 2021-04-23
|
32
|
+
|
33
|
+
- [Feat] Implement circular associations control [71e1543]
|
34
|
+
- [Feat] Support :oj_rails backend [76e519e]
|
35
|
+
|
36
|
+
## [1.0.1] - 2021-04-15
|
37
|
+
|
38
|
+
- [Fix] Don't cache resource class for `Alba.serialize` [9ed5253]
|
39
|
+
- [Improve] Warn when `ActiveSupport` or `Oj` are absent [d3ab3eb]
|
40
|
+
- [Fix] Delete unreachable `to_hash` method on Association [1ba1f90]
|
41
|
+
- [Fix] Stringify key before transforming [b4eb79e]
|
42
|
+
- [Misc] Support Ruby 2.5.0 and above, not 2.5.7 and above [43f1d17]
|
43
|
+
- [Fix] Remove accidentally added `p` debug [5d0324b]
|
44
|
+
|
45
|
+
## [1.0.0] - 2021-04-07
|
46
|
+
|
47
|
+
This is the first major release of Alba and it includes so many features. To see all the features you can have a look at [README](https://github.com/okuramasafumi/alba/blob/master/README.md#features).
|
data/Gemfile
CHANGED
@@ -4,13 +4,19 @@ source 'https://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
gem 'activesupport', require: false # For backend
|
7
|
-
gem '
|
7
|
+
gem 'ffaker', require: false # For testing
|
8
8
|
gem 'minitest', '~> 5.14' # For test
|
9
|
-
gem 'oj', '~> 3.11', platform: :ruby, require: false # For backend
|
10
9
|
gem 'rake', '~> 13.0' # For test and automation
|
11
10
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
12
|
-
gem 'rubocop-minitest', '~> 0.
|
13
|
-
gem 'rubocop-performance', '~> 1.
|
11
|
+
gem 'rubocop-minitest', '~> 0.12.0', require: false # For lint
|
12
|
+
gem 'rubocop-performance', '~> 1.11.0', require: false # For lint
|
14
13
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
15
14
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
15
|
+
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
16
|
+
gem 'simplecov-cobertura', require: false # For test coverage
|
16
17
|
gem 'yard', require: false
|
18
|
+
|
19
|
+
platforms :ruby do
|
20
|
+
gem 'oj', '~> 3.11', require: false # For backend
|
21
|
+
gem 'ruby-prof', require: false # For performance profiling
|
22
|
+
end
|
data/README.md
CHANGED
@@ -1,30 +1,38 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/alba.svg)](https://badge.fury.io/rb/alba)
|
2
|
-
[![
|
3
|
-
[![
|
2
|
+
[![CI](https://github.com/okuramasafumi/alba/actions/workflows/main.yml/badge.svg)](https://github.com/okuramasafumi/alba/actions/workflows/main.yml)
|
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
5
|
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
|
6
6
|
![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
|
7
7
|
|
8
8
|
# Alba
|
9
9
|
|
10
|
-
|
10
|
+
Alba is the fastest JSON serializer for Ruby, JRuby, and TruffleRuby.
|
11
11
|
|
12
|
-
##
|
12
|
+
## Discussions
|
13
13
|
|
14
|
-
|
14
|
+
Alba uses [GitHub Discussions](https://github.com/okuramasafumi/alba/discussions) to openly discuss the project.
|
15
15
|
|
16
|
-
Alba
|
16
|
+
If you've already used Alba, please consider posting your thoughts and feelings on [Feedback](https://github.com/okuramasafumi/alba/discussions/categories/feedback). The fact that you enjoy using Alba gives me energy to keep developing Alba!
|
17
17
|
|
18
|
-
|
18
|
+
If you have feature requests or interesting ideas, join us with [Ideas](https://github.com/okuramasafumi/alba/discussions/categories/ideas). Let's make Alba even better, together!
|
19
19
|
|
20
|
-
|
20
|
+
## Why Alba?
|
21
21
|
|
22
|
-
|
22
|
+
Because it's fast, flexible and well-maintained!
|
23
23
|
|
24
|
-
###
|
24
|
+
### Fast
|
25
25
|
|
26
26
|
Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
|
27
27
|
|
28
|
+
### Flexible
|
29
|
+
|
30
|
+
Alba provides a small set of DSL to define your serialization logic. It also provides methods you can override to alter and filter serialized hash so that you have full control over the result.
|
31
|
+
|
32
|
+
### Maintained
|
33
|
+
|
34
|
+
Alba is well-maintained and adds features quickly. [Coverage Status](https://coveralls.io/github/okuramasafumi/alba?branch=master) and [CodeClimate Maintainability](https://codeclimate.com/github/okuramasafumi/alba/maintainability) show the code base is quite healthy.
|
35
|
+
|
28
36
|
## Installation
|
29
37
|
|
30
38
|
Add this line to your application's Gemfile:
|
@@ -43,7 +51,7 @@ Or install it yourself as:
|
|
43
51
|
|
44
52
|
## Supported Ruby versions
|
45
53
|
|
46
|
-
Alba supports CRuby 2.5
|
54
|
+
Alba supports CRuby 2.5 and higher and latest JRuby and TruffleRuby.
|
47
55
|
|
48
56
|
## Documentation
|
49
57
|
|
@@ -51,15 +59,14 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
51
59
|
|
52
60
|
## Features
|
53
61
|
|
54
|
-
*
|
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
|
62
|
+
* Conditional attributes and associations
|
61
63
|
* Selectable backend
|
62
64
|
* Key transformation
|
65
|
+
* Root key inference
|
66
|
+
* Error handling
|
67
|
+
* Resource name inflection based on association name
|
68
|
+
* Circular associations control
|
69
|
+
* [Experimental] Types for validation and conversion
|
63
70
|
* No runtime dependencies
|
64
71
|
|
65
72
|
## Anti features
|
@@ -69,7 +76,6 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
69
76
|
* Supporting all existing JSON encoder/decoder
|
70
77
|
* Cache
|
71
78
|
* [JSON:API](https://jsonapi.org) support
|
72
|
-
* Association name inflection
|
73
79
|
* And many others
|
74
80
|
|
75
81
|
## Usage
|
@@ -78,7 +84,7 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
78
84
|
|
79
85
|
Alba's configuration is fairly simple.
|
80
86
|
|
81
|
-
#### Backend
|
87
|
+
#### Backend configuration
|
82
88
|
|
83
89
|
Backend is the actual part serializing an object into JSON. Alba supports these backends.
|
84
90
|
|
@@ -92,6 +98,26 @@ You can set a backend like this:
|
|
92
98
|
Alba.backend = :oj
|
93
99
|
```
|
94
100
|
|
101
|
+
#### Inference configuration
|
102
|
+
|
103
|
+
You can enable inference feature using `enable_inference!` method.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
Alba.enable_inference!
|
107
|
+
```
|
108
|
+
|
109
|
+
You must install `ActiveSupport` to enable inference.
|
110
|
+
|
111
|
+
#### Error handling configuration
|
112
|
+
|
113
|
+
You can configure error handling with `on_error` method.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
Alba.on_error :ignore
|
117
|
+
```
|
118
|
+
|
119
|
+
For the details, see [Error handling section](#error-handling)
|
120
|
+
|
95
121
|
### Simple serialization with key
|
96
122
|
|
97
123
|
```ruby
|
@@ -109,6 +135,8 @@ end
|
|
109
135
|
class UserResource
|
110
136
|
include Alba::Resource
|
111
137
|
|
138
|
+
key :user
|
139
|
+
|
112
140
|
attributes :id, :name
|
113
141
|
|
114
142
|
attribute :name_with_email do |resource|
|
@@ -116,12 +144,6 @@ class UserResource
|
|
116
144
|
end
|
117
145
|
end
|
118
146
|
|
119
|
-
class SerializerWithKey
|
120
|
-
include Alba::Serializer
|
121
|
-
|
122
|
-
set key: :user
|
123
|
-
end
|
124
|
-
|
125
147
|
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
126
148
|
UserResource.new(user).serialize
|
127
149
|
# => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
|
@@ -181,7 +203,7 @@ UserResource.new(user).serialize
|
|
181
203
|
`Alba.serialize` method is a shortcut to define everything inline.
|
182
204
|
|
183
205
|
```ruby
|
184
|
-
Alba.serialize(user,
|
206
|
+
Alba.serialize(user, key: :foo) do
|
185
207
|
attributes :id
|
186
208
|
many :articles do
|
187
209
|
attributes :title, :body
|
@@ -190,7 +212,7 @@ end
|
|
190
212
|
# => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
|
191
213
|
```
|
192
214
|
|
193
|
-
Although this might be useful sometimes, it's generally recommended to define a class for
|
215
|
+
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
194
216
|
|
195
217
|
### Inheritance and Ignorance
|
196
218
|
|
@@ -222,11 +244,13 @@ RestrictedFooResouce.new(foo).serialize
|
|
222
244
|
end
|
223
245
|
```
|
224
246
|
|
225
|
-
###
|
247
|
+
### Key transformation
|
226
248
|
|
227
|
-
|
249
|
+
If you want to use `transform_keys` DSL and you already have `active_support` installed, key transformation will work out of the box, using `ActiveSupport::Inflector`. If `active_support` is not around, you have 2 possibilities:
|
250
|
+
* install it
|
251
|
+
* use a [custom inflector](#custom-inflector)
|
228
252
|
|
229
|
-
With `
|
253
|
+
With `transform_keys` DSL, you can transform attribute keys.
|
230
254
|
|
231
255
|
```ruby
|
232
256
|
class User
|
@@ -252,8 +276,69 @@ UserResourceCamel.new(user).serialize
|
|
252
276
|
# => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
|
253
277
|
```
|
254
278
|
|
279
|
+
You can also transform root key when:
|
280
|
+
|
281
|
+
* `Alba.enable_inference!` is called
|
282
|
+
* `key!` is called in Resource class
|
283
|
+
* `root` option of `transform_keys` is set to true or `Alba.enable_root_key_transformation!` is called.
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
Alba.enable_inference!
|
287
|
+
|
288
|
+
class BankAccount
|
289
|
+
attr_reader :account_number
|
290
|
+
|
291
|
+
def initialize(account_number)
|
292
|
+
@account_number = account_number
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
class BankAccountResource
|
297
|
+
include Alba::Resource
|
298
|
+
|
299
|
+
key!
|
300
|
+
|
301
|
+
attributes :account_number
|
302
|
+
transform_keys :dash, root: true
|
303
|
+
end
|
304
|
+
|
305
|
+
bank_account = BankAccount.new(123_456_789)
|
306
|
+
BankAccountResource.new(bank_account).serialize
|
307
|
+
# => '{"bank-account":{"account-number":123456789}}'
|
308
|
+
```
|
309
|
+
|
310
|
+
This behavior to transform root key will become default at version 2.
|
311
|
+
|
255
312
|
Supported transformation types are :camel, :lower_camel and :dash.
|
256
313
|
|
314
|
+
#### Custom inflector
|
315
|
+
|
316
|
+
A custom inflector can be plugged in as follows...
|
317
|
+
```ruby
|
318
|
+
Alba.inflector = MyCustomInflector
|
319
|
+
```
|
320
|
+
...and has to implement following interface (the parameter `key` is of type `String`):
|
321
|
+
```ruby
|
322
|
+
module InflectorInterface
|
323
|
+
def camelize(key)
|
324
|
+
raise "Not implemented"
|
325
|
+
end
|
326
|
+
|
327
|
+
def camelize_lower(key)
|
328
|
+
raise "Not implemented"
|
329
|
+
end
|
330
|
+
|
331
|
+
def dasherize(key)
|
332
|
+
raise "Not implemented"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
```
|
337
|
+
For example you could use `Dry::Inflector`, which implements exactly the above interface. If you are developing a `Hanami`-Application `Dry::Inflector` is around. In this case the following would be sufficient:
|
338
|
+
```ruby
|
339
|
+
Alba.inflector = Dry::Inflector.new
|
340
|
+
```
|
341
|
+
|
257
342
|
### Filtering attributes
|
258
343
|
|
259
344
|
You can filter attributes by overriding `Alba::Resource#converter` method, but it's a bit tricky.
|
@@ -284,18 +369,199 @@ end
|
|
284
369
|
|
285
370
|
user = User.new(1, nil, nil)
|
286
371
|
UserResource.new(user).serialize # => '{"id":1}'
|
287
|
-
|
288
|
-
|
289
372
|
```
|
290
373
|
|
291
374
|
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
375
|
|
293
376
|
It's not recommended to swap the whole conversion logic. It's recommended to always call `super` when you override `converter`.
|
294
377
|
|
295
|
-
|
378
|
+
### Conditional attributes
|
379
|
+
|
380
|
+
Filtering attributes with overriding `convert` works well for simple cases. However, It's cumbersome when we want to filter various attributes based on different conditions for keys.
|
381
|
+
|
382
|
+
In these cases, conditional attributes works well. We can pass `if` option to `attributes`, `attribute`, `one` and `many`. Below is an example for the same effect as [filtering attributes section](#filtering-attributes).
|
296
383
|
|
297
|
-
|
298
|
-
|
384
|
+
```ruby
|
385
|
+
class User
|
386
|
+
attr_accessor :id, :name, :email, :created_at, :updated_at
|
387
|
+
|
388
|
+
def initialize(id, name, email)
|
389
|
+
@id = id
|
390
|
+
@name = name
|
391
|
+
@email = email
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
class UserResource
|
396
|
+
include Alba::Resource
|
397
|
+
|
398
|
+
attributes :id, :name, :email, if: proc { |user, attribute| !attribute.nil? }
|
399
|
+
end
|
400
|
+
|
401
|
+
user = User.new(1, nil, nil)
|
402
|
+
UserResource.new(user).serialize # => '{"id":1}'
|
403
|
+
```
|
404
|
+
|
405
|
+
### Inference
|
406
|
+
|
407
|
+
After `Alba.enable_inference!` called, Alba tries to infer root key and association resource name.
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
Alba.enable_inference!
|
411
|
+
|
412
|
+
class User
|
413
|
+
attr_reader :id
|
414
|
+
attr_accessor :articles
|
415
|
+
|
416
|
+
def initialize(id)
|
417
|
+
@id = id
|
418
|
+
@articles = []
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
class Article
|
423
|
+
attr_accessor :id, :title
|
424
|
+
|
425
|
+
def initialize(id, title)
|
426
|
+
@id = id
|
427
|
+
@title = title
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
class ArticleResource
|
432
|
+
include Alba::Resource
|
433
|
+
|
434
|
+
attributes :title
|
435
|
+
end
|
436
|
+
|
437
|
+
class UserResource
|
438
|
+
include Alba::Resource
|
439
|
+
|
440
|
+
key!
|
441
|
+
|
442
|
+
attributes :id
|
443
|
+
|
444
|
+
many :articles
|
445
|
+
end
|
446
|
+
|
447
|
+
user = User.new(1)
|
448
|
+
user.articles << Article.new(1, 'The title')
|
449
|
+
|
450
|
+
UserResource.new(user).serialize # => '{"user":{"id":1,"articles":[{"title":"The title"}]}}'
|
451
|
+
UserResource.new([user]).serialize # => '{"users":[{"id":1,"articles":[{"title":"The title"}]}]}'
|
452
|
+
```
|
453
|
+
|
454
|
+
This resource automatically sets its root key to either "users" or "user", depending on the given object is collection or not.
|
455
|
+
|
456
|
+
Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
|
457
|
+
|
458
|
+
Note that to enable this feature you must install `ActiveSupport` gem.
|
459
|
+
|
460
|
+
### Error handling
|
461
|
+
|
462
|
+
You can set error handler globally or per resource using `on_error`.
|
463
|
+
|
464
|
+
```ruby
|
465
|
+
class User
|
466
|
+
attr_accessor :id, :name
|
467
|
+
|
468
|
+
def initialize(id, name, email)
|
469
|
+
@id = id
|
470
|
+
@name = name
|
471
|
+
@email = email
|
472
|
+
end
|
473
|
+
|
474
|
+
def email
|
475
|
+
raise RuntimeError, 'Error!'
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
class UserResource
|
480
|
+
include Alba::Resource
|
481
|
+
|
482
|
+
attributes :id, :name, :email
|
483
|
+
|
484
|
+
on_error :ignore
|
485
|
+
end
|
486
|
+
|
487
|
+
user = User.new(1, 'Test', 'email@example.com')
|
488
|
+
UserResource.new(user).serialize # => '{"id":1,"name":"Test"}'
|
489
|
+
```
|
490
|
+
|
491
|
+
This way you can exclude an entry when fetching an attribute gives an exception.
|
492
|
+
|
493
|
+
There are four possible arguments `on_error` method accepts.
|
494
|
+
|
495
|
+
* `:raise` re-raises an error. This is the default behavior.
|
496
|
+
* `:ignore` ignores the entry with the error.
|
497
|
+
* `:nullify` sets the attribute with the error to `nil`.
|
498
|
+
* Block gives you more control over what to be returned.
|
499
|
+
|
500
|
+
The block receives five arguments, `error`, `object`, `key`, `attribute` and `resource class` and must return a two-element array. Below is an example.
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
# Global error handling
|
504
|
+
Alba.on_error do |error, object, key, attribute, resource_class|
|
505
|
+
if resource_class == MyResource
|
506
|
+
['error_fallback', object.error_fallback]
|
507
|
+
else
|
508
|
+
[key, error.message]
|
509
|
+
end
|
510
|
+
end
|
511
|
+
```
|
512
|
+
|
513
|
+
### Circular associations control
|
514
|
+
|
515
|
+
**Note that this feature works correctly since version 1.3. In previous versions it doesn't work as expected.**
|
516
|
+
|
517
|
+
You can control circular associations with `within` option. `within` option is a nested Hash such as `{book: {authors: books}}`. In this example, Alba serializes a book's authors' books. This means you can reference `BookResource` from `AuthorResource` and vice versa. This is really powerful when you have a complex data structure and serialize certain parts of it.
|
518
|
+
|
519
|
+
For more details, please refer to [test code](https://github.com/okuramasafumi/alba/blob/master/test/usecases/circular_association_test.rb)
|
520
|
+
|
521
|
+
### Experimental support of types
|
522
|
+
|
523
|
+
You can validate and convert input with types.
|
524
|
+
|
525
|
+
```ruby
|
526
|
+
class User
|
527
|
+
attr_reader :id, :name, :age, :bio, :admin, :created_at
|
528
|
+
|
529
|
+
def initialize(id, name, age, bio = '', admin = false) # rubocop:disable Style/OptionalBooleanParameter
|
530
|
+
@id = id
|
531
|
+
@name = name
|
532
|
+
@age = age
|
533
|
+
@admin = admin
|
534
|
+
@bio = bio
|
535
|
+
@created_at = Time.new(2020, 10, 10)
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
class UserResource
|
540
|
+
include Alba::Resource
|
541
|
+
|
542
|
+
attributes :name, id: [String, true], age: [Integer, true], bio: String, admin: [:Boolean, true], created_at: [String, ->(object) { object.strftime('%F') }]
|
543
|
+
end
|
544
|
+
|
545
|
+
user = User.new(1, 'Masafumi OKURA', '32', 'Ruby dev')
|
546
|
+
UserResource.new(user).serialize
|
547
|
+
# => '{"name":"Masafumi OKURA","id":"1","age":32,"bio":"Ruby dev","admin":false,"created_at":"2020-10-10"}'
|
548
|
+
```
|
549
|
+
|
550
|
+
Notice that `id` and `created_at` are converted to String and `age` is converted to Integer.
|
551
|
+
|
552
|
+
If type is not correct and auto conversion is disabled (default), `TypeError` occurs.
|
553
|
+
|
554
|
+
```ruby
|
555
|
+
user = User.new(1, 'Masafumi OKURA', '32', nil) # bio is nil and auto conversion is disabled for bio
|
556
|
+
UserResource.new(user).serialize
|
557
|
+
# => TypeError, 'Attribute bio is expected to be String but actually nil.'
|
558
|
+
```
|
559
|
+
|
560
|
+
Note that this feature is experimental and interfaces are subject to change.
|
561
|
+
|
562
|
+
### Caching
|
563
|
+
|
564
|
+
Currently, Alba doesn't support caching, primarily due to the behavior of `ActiveRecord::Relation`'s cache. See [the issue](https://github.com/rails/rails/issues/41784).
|
299
565
|
|
300
566
|
## Rails
|
301
567
|
|
@@ -303,23 +569,20 @@ When you use Alba in Rails, you can create an initializer file with the line bel
|
|
303
569
|
|
304
570
|
```ruby
|
305
571
|
Alba.backend = :active_support
|
572
|
+
# or
|
573
|
+
Alba.backend = :oj_rails
|
306
574
|
```
|
307
575
|
|
308
576
|
## Why named "Alba"?
|
309
577
|
|
310
578
|
The name "Alba" comes from "albatross", a kind of birds. In Japanese, this bird is called "Aho-dori", which means "stupid bird". I find it funny because in fact albatrosses fly really fast. I hope Alba looks stupid but in fact it does its job quick.
|
311
579
|
|
312
|
-
##
|
313
|
-
|
314
|
-
Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conceptual and not implemented directly).
|
315
|
-
|
316
|
-
`Serializer` is a component responsible for rendering JSON output with `Resource`. `Serializer` can add more data to `Resource` such as `metadata`. Users can define one single `Serializer` and reuse it for all `Resource`s. The main interface is `#serialize`.
|
317
|
-
|
318
|
-
`Resource` is a component responsible for defining how an object (or a collection of objects) is converted into JSON. The difference between `Serializer` and `Resource` is that while `Serializer` can add arbitrary data into JSON, `Resource` can get data only from the object under it. The main interface is `#serializable_hash`.
|
580
|
+
## Pioneers
|
319
581
|
|
320
|
-
|
582
|
+
There are great pioneers in Ruby's ecosystem which does basically the same thing as Alba does. To name a few:
|
321
583
|
|
322
|
-
|
584
|
+
* [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) a.k.a AMS, the most famous implementation of JSON serializer for Ruby
|
585
|
+
* [Blueprinter](https://github.com/procore/blueprinter) shares some concepts with Alba
|
323
586
|
|
324
587
|
## Development
|
325
588
|
|