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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 993f4b887c19f4e1149ddb850cd07a8cb8c2042cde0fb3974b1380537707a55d
4
- data.tar.gz: acf77a0aa35f7fe23fc3f98843064d2800e0b4e0739e587dd29aab65f06269ad
3
+ metadata.gz: b2c2e8c84ddccf4db9f9f18dd155361567f2a1733c55f817f5b4d97d573675d5
4
+ data.tar.gz: 3f11dd120c8b57aef909d79f6570482b8af810cd19c57ddcaa0d7b2ee70c2d6b
5
5
  SHA512:
6
- metadata.gz: 9b8e52dcaf4ca33cba0685c2a0a06c16df69cd04b6d399c092e920548f5baa6a21aeab8097f90e92ab35ac9c260d83858c19462a9ec2a689d97c6999daef5e3a
7
- data.tar.gz: e525a66c30477cab6741436128398a6f0309a0f786040fe946a9f2783d08f619df00d85157d04c5876cc579e19cceabf22dcb3556addf6ad061d44d1f0cfa888
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
@@ -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
@@ -6,3 +6,6 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ Gemfile.lock
11
+ /gemfiles/*.lock
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
- Metrics/ClassLength:
42
+ # In test code we don't care about the metrics!
43
+ Metrics:
33
44
  Exclude:
34
- - 'test/alba_test.rb'
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
@@ -1,2 +1,4 @@
1
1
  --no-private
2
2
  --exclude lib/alba/version.rb
3
+ --embed-mixin ClassMethods
4
+ --embed-mixin InstanceMethods
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 'coveralls', require: false # For test coverage
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.10.3', require: false # For lint
13
- gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
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
- [![Build Status](https://travis-ci.com/okuramasafumi/alba.svg?branch=master)](https://travis-ci.com/okuramasafumi/alba)
3
- [![Coverage Status](https://coveralls.io/repos/github/okuramasafumi/alba/badge.svg?branch=master)](https://coveralls.io/github/okuramasafumi/alba?branch=master)
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
- `Alba` is the fastest JSON serializer for Ruby.
10
+ Alba is the fastest JSON serializer for Ruby, JRuby, and TruffleRuby.
11
11
 
12
- ## Why yet another JSON serializer?
12
+ ## Discussions
13
13
 
14
- We know that there are several other JSON serializers for Ruby around, but none of them made us satisfied.
14
+ Alba uses [GitHub Discussions](https://github.com/okuramasafumi/alba/discussions) to openly discuss the project.
15
15
 
16
- Alba has some advantages over other JSON serializers which we've wanted to have.
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
- ### Easy to understand
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
- 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.
20
+ ## Why Alba?
21
21
 
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!
22
+ Because it's fast, flexible and well-maintained!
23
23
 
24
- ### Performance
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.7 and higher and latest TruffleRuby.
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
- * 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
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, with: proc { set key: :foo }) do
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 both Resource and Serializer.
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
- ### Attribute key transformation
247
+ ### Key transformation
226
248
 
227
- ** Note: You need to install `active_support` gem to use `transform_keys` DSL.
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 `active_support` installed, you can transform attribute keys.
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
- ## Comparison
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
- Alba is faster than alternatives.
298
- For a performance benchmark, see https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829.
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
- ## Alba internals
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
- `One` and `Many` are the special object fetching other resources and converting them into Hash.
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
- The main `Alba` module holds config values and one convenience method, `.serialize`.
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