alba 0.11.1 → 1.0.1

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: 9aae01860e12e1e5fd71f066e8772fb3a80b4aec6e99cf9dd55ad8652896ea24
4
- data.tar.gz: c9930853dcaaa5ea2ff28e954c3b0d7e2ba559efc64b82e7da6b0c28968f55ed
3
+ metadata.gz: 3fc2741e03be6373aa324768358a1acf4b713ec43bba5daf80d133a08d0cc12b
4
+ data.tar.gz: 7228dc09384d5d4d6e99456a6f14d6e0cb3d678483cd59a7439d6a150f463e4b
5
5
  SHA512:
6
- metadata.gz: 7882b0950c339f87fc75b19e79bcdc2481a296331e9be47a646db997fa4db5639af31c49d529063d099cd5df405357d245397af23b0038c568e32feb86bd5718
7
- data.tar.gz: 2bc8c03f0ca5049a23b7dd5c2f6582051f98296a0ba9e0a436e08e9a426a53896e7ac94af276cc3a82e5a0a253f30d381db5384e483c77918ff3d5ef9165a1b7
6
+ metadata.gz: 0fb68a3a1c786aa1b776246d08fd23bada8d4c7fcff0ea2ba33a49a1410d9b1e839407450ff2d1137f531a8e80738977369a4f857199d3bb65c98b140d7cd6b9
7
+ data.tar.gz: fcfb93544bab6b48e6ae24946992231a6023df52763d807dad276f1468c1ce77d255fe637de3a72efacffe2d20915d203ddc1f247908c4f6e4d69ae6ea4c97da
@@ -0,0 +1,34 @@
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, jruby, truffleruby]
12
+ gemfile: [all, without_active_support, without_oj]
13
+ exclude:
14
+ - os: windows-latest
15
+ ruby: jruby
16
+ - os: windows-latest
17
+ ruby: truffleruby
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
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+ - name: Run the default task
29
+ run: |
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,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ Gemfile.lock
data/.rubocop.yml CHANGED
@@ -6,13 +6,16 @@ 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
18
+ TargetRubyVersion: 2.5
16
19
 
17
20
  # Oneline comment is not valid so until it gets valid, we disable it
18
21
  Bundler/GemComment:
@@ -34,6 +37,16 @@ Metrics/ClassLength:
34
37
  Metrics/MethodLength:
35
38
  Max: 15
36
39
 
40
+ # Resource class includes DSLs, which tend to accept long list of parameters
41
+ Metrics/ParameterLists:
42
+ Exclude:
43
+ - 'lib/alba/resource.rb'
44
+
45
+ # We need to eval resource code to test errors on resource classes
46
+ Security/Eval:
47
+ Exclude:
48
+ - 'test/**/*.rb'
49
+
37
50
  Style/ConstantVisibility:
38
51
  Exclude:
39
52
  - 'lib/alba/version.rb'
@@ -41,6 +54,10 @@ Style/ConstantVisibility:
41
54
  Style/Copyright:
42
55
  Enabled: false
43
56
 
57
+ # I know what I do :)
58
+ Style/DisableCopsWithinSourceCodeDirective:
59
+ Enabled: false
60
+
44
61
  Style/FrozenStringLiteralComment:
45
62
  Enabled: false
46
63
 
@@ -49,3 +66,7 @@ Style/InlineComment:
49
66
 
50
67
  Style/MethodCallWithArgsParentheses:
51
68
  Enabled: false
69
+
70
+ # There are so many cases we just want `if` expression!
71
+ Style/MissingElse:
72
+ EnforcedStyle: case
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
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.0.1] - 2021-04-15
10
+
11
+ - [Fix] Don't cache resource class for `Alba.serialize` [9ed5253]
12
+ - [Improve] Warn when `ActiveSupport` or `Oj` are absent [d3ab3eb]
13
+ - [Fix] Delete unreachable `to_hash` method on Association [1ba1f90]
14
+ - [Fix] Stringify key before transforming [b4eb79e]
15
+ - [Misc] Support Ruby 2.5.0 and above, not 2.5.7 and above [43f1d17]
16
+ - [Fix] Remove accidentally added `p` debug [5d0324b]
17
+
18
+ ## [1.0.0] - 2021-04-07
19
+
20
+ 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,12 +4,18 @@ 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
8
7
  gem 'minitest', '~> 5.14' # For test
9
- gem 'oj', '~> 3.10', platform: :ruby, require: false # For backend
10
8
  gem 'rake', '~> 13.0' # For test and automation
11
9
  gem 'rubocop', '>= 0.79.0', require: false # For lint
12
- gem 'rubocop-minitest', '~> 0.10.1', require: false # For lint
13
- gem 'rubocop-performance', '~> 1.7.1', require: false # For lint
10
+ gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
11
+ gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
12
+ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
14
13
  gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
14
+ gem 'simplecov', '~> 0.21.0', require: false # For test coverage
15
+ gem 'simplecov-cobertura', require: false # For test coverage
15
16
  gem 'yard', require: false
17
+
18
+ platforms :ruby do
19
+ gem 'oj', '~> 3.11', require: false # For backend
20
+ gem 'ruby-prof', require: false # For performance profiling
21
+ end
data/README.md CHANGED
@@ -1,29 +1,37 @@
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 an 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 less than 300 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
- Alba is faster than most of the alternatives. We have a [benchmark](https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829).
26
+ Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
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.
27
35
 
28
36
  ## Installation
29
37
 
@@ -43,11 +51,11 @@ 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
 
50
- You can find the documentation on [RubyDoc](https://rubydoc.info/gems/alba).
58
+ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramasafumi/alba).
51
59
 
52
60
  ## Features
53
61
 
@@ -56,9 +64,12 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/gems/alba).
56
64
  * One and many association with the ability to define them inline
57
65
  * Adding condition and filter to association
58
66
  * Parameters can be injected and used in attributes and associations
59
- * Setting root key separately in Serializer
60
- * Adding metadata
67
+ * Conditional attributes and associations
61
68
  * Selectable backend
69
+ * Key transformation
70
+ * Root key inference
71
+ * Error handling
72
+ * Resource name inflection based on association name
62
73
  * No runtime dependencies
63
74
 
64
75
  ## Anti features
@@ -68,7 +79,6 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/gems/alba).
68
79
  * Supporting all existing JSON encoder/decoder
69
80
  * Cache
70
81
  * [JSON:API](https://jsonapi.org) support
71
- * Association name inflection
72
82
  * And many others
73
83
 
74
84
  ## Usage
@@ -77,7 +87,7 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/gems/alba).
77
87
 
78
88
  Alba's configuration is fairly simple.
79
89
 
80
- #### Backend
90
+ #### Backend configuration
81
91
 
82
92
  Backend is the actual part serializing an object into JSON. Alba supports these backends.
83
93
 
@@ -91,6 +101,26 @@ You can set a backend like this:
91
101
  Alba.backend = :oj
92
102
  ```
93
103
 
104
+ #### Inference configuration
105
+
106
+ You can enable inference feature using `enable_inference!` method.
107
+
108
+ ```ruby
109
+ Alba.enable_inference!
110
+ ```
111
+
112
+ You must install `ActiveSupport` to enable inference.
113
+
114
+ #### Error handling configuration
115
+
116
+ You can configure error handling with `on_error` method.
117
+
118
+ ```ruby
119
+ Alba.on_error :ignore
120
+ ```
121
+
122
+ For the details, see [Error handling section](#error-handling)
123
+
94
124
  ### Simple serialization with key
95
125
 
96
126
  ```ruby
@@ -108,6 +138,8 @@ end
108
138
  class UserResource
109
139
  include Alba::Resource
110
140
 
141
+ key :user
142
+
111
143
  attributes :id, :name
112
144
 
113
145
  attribute :name_with_email do |resource|
@@ -115,12 +147,6 @@ class UserResource
115
147
  end
116
148
  end
117
149
 
118
- class SerializerWithKey
119
- include Alba::Serializer
120
-
121
- set key: :user
122
- end
123
-
124
150
  user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
125
151
  UserResource.new(user).serialize
126
152
  # => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
@@ -180,7 +206,7 @@ UserResource.new(user).serialize
180
206
  `Alba.serialize` method is a shortcut to define everything inline.
181
207
 
182
208
  ```ruby
183
- Alba.serialize(user, with: proc { set key: :foo }) do
209
+ Alba.serialize(user, key: :foo) do
184
210
  attributes :id
185
211
  many :articles do
186
212
  attributes :title, :body
@@ -189,7 +215,7 @@ end
189
215
  # => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
190
216
  ```
191
217
 
192
- Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
218
+ Although this might be useful sometimes, it's generally recommended to define a class for Resource.
193
219
 
194
220
  ### Inheritance and Ignorance
195
221
 
@@ -221,6 +247,213 @@ RestrictedFooResouce.new(foo).serialize
221
247
  end
222
248
  ```
223
249
 
250
+ ### Attribute key transformation
251
+
252
+ ** Note: You need to install `active_support` gem to use `transform_keys` DSL.
253
+
254
+ With `active_support` installed, you can transform attribute keys.
255
+
256
+ ```ruby
257
+ class User
258
+ attr_reader :id, :first_name, :last_name
259
+
260
+ def initialize(id, first_name, last_name)
261
+ @id = id
262
+ @first_name = first_name
263
+ @last_name = last_name
264
+ end
265
+ end
266
+
267
+ class UserResource
268
+ include Alba::Resource
269
+
270
+ attributes :id, :first_name, :last_name
271
+
272
+ transform_keys :lower_camel
273
+ end
274
+
275
+ user = User.new(1, 'Masafumi', 'Okura')
276
+ UserResourceCamel.new(user).serialize
277
+ # => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
278
+ ```
279
+
280
+ Supported transformation types are :camel, :lower_camel and :dash.
281
+
282
+ ### Filtering attributes
283
+
284
+ You can filter attributes by overriding `Alba::Resource#converter` method, but it's a bit tricky.
285
+
286
+ ```ruby
287
+ class User
288
+ attr_accessor :id, :name, :email, :created_at, :updated_at
289
+
290
+ def initialize(id, name, email)
291
+ @id = id
292
+ @name = name
293
+ @email = email
294
+ end
295
+ end
296
+
297
+ class UserResource
298
+ include Alba::Resource
299
+
300
+ attributes :id, :name, :email
301
+
302
+ private
303
+
304
+ # Here using `Proc#>>` method to compose a proc from `super`
305
+ def converter
306
+ super >> proc { |hash| hash.compact }
307
+ end
308
+ end
309
+
310
+ user = User.new(1, nil, nil)
311
+ UserResource.new(user).serialize # => '{"id":1}'
312
+ ```
313
+
314
+ 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.
315
+
316
+ It's not recommended to swap the whole conversion logic. It's recommended to always call `super` when you override `converter`.
317
+
318
+ ### Conditional attributes
319
+
320
+ 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.
321
+
322
+ 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).
323
+
324
+ ```ruby
325
+ class User
326
+ attr_accessor :id, :name, :email, :created_at, :updated_at
327
+
328
+ def initialize(id, name, email)
329
+ @id = id
330
+ @name = name
331
+ @email = email
332
+ end
333
+ end
334
+
335
+ class UserResource
336
+ include Alba::Resource
337
+
338
+ attributes :id, :name, :email, if: proc { |user, attribute| !attribute.nil? }
339
+ end
340
+
341
+ user = User.new(1, nil, nil)
342
+ UserResource.new(user).serialize # => '{"id":1}'
343
+ ```
344
+
345
+ ### Inference
346
+
347
+ After `Alba.enable_inference!` called, Alba tries to infer root key and association resource name.
348
+
349
+ ```ruby
350
+ Alba.enable_inference!
351
+
352
+ class User
353
+ attr_reader :id
354
+ attr_accessor :articles
355
+
356
+ def initialize(id)
357
+ @id = id
358
+ @articles = []
359
+ end
360
+ end
361
+
362
+ class Article
363
+ attr_accessor :id, :title
364
+
365
+ def initialize(id, title)
366
+ @id = id
367
+ @title = title
368
+ end
369
+ end
370
+
371
+ class ArticleResource
372
+ include Alba::Resource
373
+
374
+ attributes :title
375
+ end
376
+
377
+ class UserResource
378
+ include Alba::Resource
379
+
380
+ key!
381
+
382
+ attributes :id
383
+
384
+ many :articles
385
+ end
386
+
387
+ user = User.new(1)
388
+ user.articles << Article.new(1, 'The title')
389
+
390
+ UserResource.new(user).serialize # => '{"user":{"id":1,"articles":[{"title":"The title"}]}}'
391
+ UserResource.new([user]).serialize # => '{"users":[{"id":1,"articles":[{"title":"The title"}]}]}'
392
+ ```
393
+
394
+ This resource automatically sets its root key to either "users" or "user", depending on the given object is collection or not.
395
+
396
+ Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
397
+
398
+ Note that to enable this feature you must install `ActiveSupport` gem.
399
+
400
+ ### Error handling
401
+
402
+ You can set error handler globally or per resource using `on_error`.
403
+
404
+ ```ruby
405
+ class User
406
+ attr_accessor :id, :name
407
+
408
+ def initialize(id, name, email)
409
+ @id = id
410
+ @name = name
411
+ @email = email
412
+ end
413
+
414
+ def email
415
+ raise RuntimeError, 'Error!'
416
+ end
417
+ end
418
+
419
+ class UserResource
420
+ include Alba::Resource
421
+
422
+ attributes :id, :name, :email
423
+
424
+ on_error :ignore
425
+ end
426
+
427
+ user = User.new(1, 'Test', 'email@example.com')
428
+ UserResource.new(user).serialize # => '{"id":1,"name":"Test"}'
429
+ ```
430
+
431
+ This way you can exclude an entry when fetching an attribute gives an exception.
432
+
433
+ There are four possible arguments `on_error` method accepts.
434
+
435
+ * `:raise` re-raises an error. This is the default behavior.
436
+ * `:ignore` ignores the entry with the error.
437
+ * `:nullify` sets the attribute with the error to `nil`.
438
+ * Block gives you more control over what to be returned.
439
+
440
+ The block receives five arguments, `error`, `object`, `key`, `attribute` and `resource class` and must return a two-element array. Below is an example.
441
+
442
+ ```ruby
443
+ # Global error handling
444
+ Alba.on_error do |error, object, key, attribute, resource_class|
445
+ if resource_class == MyResource
446
+ ['error_fallback', object.error_fallback]
447
+ else
448
+ [key, error.message]
449
+ end
450
+ end
451
+ ```
452
+
453
+ ### Caching
454
+
455
+ 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).
456
+
224
457
  ## Comparison
225
458
 
226
459
  Alba is faster than alternatives.
@@ -238,17 +471,12 @@ Alba.backend = :active_support
238
471
 
239
472
  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.
240
473
 
241
- ## Alba internals
242
-
243
- Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conceptual and not implemented directly).
244
-
245
- `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`.
246
-
247
- `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`.
474
+ ## Pioneers
248
475
 
249
- `One` and `Many` are the special object fetching other resources and converting them into Hash.
476
+ There are great pioneers in Ruby's ecosystem which does basically the same thing as Alba does. To name a few:
250
477
 
251
- The main `Alba` module holds config values and one convenience method, `.serialize`.
478
+ * [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) a.k.a AMS, the most famous implementation of JSON serializer for Ruby
479
+ * [Blueprinter](https://github.com/procore/blueprinter) shares some concepts with Alba
252
480
 
253
481
  ## Development
254
482