alba 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -3
- data/Gemfile +1 -2
- data/README.md +91 -33
- data/benchmark/collection.rb +9 -147
- data/benchmark/prep.rb +82 -0
- data/benchmark/single_resource.rb +3 -154
- data/docs/migrate_from_active_model_serializers.md +2 -2
- data/docs/rails.md +1 -1
- data/lib/alba/association.rb +6 -4
- data/lib/alba/conditional_attribute.rb +1 -1
- data/lib/alba/resource.rb +35 -28
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +27 -4
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86253dcf425b1a8bf489db53327a4f9a13c1ae046f87a5f558b75b9c1df18964
|
|
4
|
+
data.tar.gz: de8b3a29bd9b12f8cd6718a04f6140bec7611bedbf654232e9119f541d7f904e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dea93b6391ceaab7ec2759758d24df0eb14e8c59c52d8a174aeda79373afac23ca9f909a65aae3c2442106dd9d8dc700668eb6b791002ef0f9cf34833f53099a
|
|
7
|
+
data.tar.gz: 535c33bd2bea00d3029ab07a0848e37e8a30b8c6d66c3a1f894b51ad9b4aae30fc498dd000086c3e6a851c31069e6ab5d5ce11f8d96b4c687c26ae0174d42e54
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.1.0] 2022-12-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add `select` method for filtering attributes [#270](https://github.com/okuramasafumi/alba/pull/270)
|
|
14
|
+
- Allow ConditionalAttribute with 2-arity proc to reject nil attributes [#273](https://github.com/okuramasafumi/alba/pull/273)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Add support for proc resource in one polymorphic associations [#281](https://github.com/okuramasafumi/alba/pull/281)
|
|
19
|
+
|
|
20
|
+
### Deprecated
|
|
21
|
+
|
|
22
|
+
- Deprecate `inference` related methods in favor of a unified `inflector` interface.
|
|
23
|
+
Deprecated methods are: `Alba.enable_inference!`, `Alba.disable_inference!`, and `Alba.inferring`.
|
|
24
|
+
Use `Alba.inflector = :active_support/:dry` or `Alba.inflector = SomeInflector` to enable.
|
|
25
|
+
Use `Alba.inflector = nil` to disable.
|
|
26
|
+
Use `Alba.inflector` to check if enabled.
|
|
27
|
+
|
|
9
28
|
## [2.0.1] 2022-11-02
|
|
10
29
|
|
|
11
30
|
### Fix
|
|
@@ -17,9 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
36
|
|
|
18
37
|
### Breaking changes
|
|
19
38
|
|
|
20
|
-
- All Hash-related methods now return String
|
|
21
|
-
This affects all users, but you can use `deep_symbolize_keys` in Rails environment if you prefer Symbol keys
|
|
22
|
-
Some DSLs that take key argument such as `on_nil` and `on_error`, are also affected
|
|
39
|
+
- All Hash-related methods now return String keys instead of Symbol keys.
|
|
40
|
+
This affects all users, but you can use `deep_symbolize_keys` in Rails environment if you prefer Symbol keys, or `with_indifferent_access` to support both String and Symbol keys.
|
|
41
|
+
Some DSLs that take key argument such as `on_nil` and `on_error`, are also affected.
|
|
23
42
|
- Remove deprecated methods: `Resource#to_hash`, `Resource.ignoring`, `Alba.on_nil`, `Alba.on_error`, `Alba.enable_root_key_transformation!` and `Alba.disable_root_key_transformation!`
|
|
24
43
|
- If using `transform_keys`, the default inflector is no longer set by default [d02245c8](https://github.com/okuramasafumi/alba/commit/d02245c87e9df303cb20e354a81e5457ea460bdd#diff-ecd8c835d2390b8cb89e7ff75e599f0c15cdbe18c30981d6090f4a515566686f)
|
|
25
44
|
To retain this functionality in Rails, add an initializer with the following:
|
data/Gemfile
CHANGED
|
@@ -6,11 +6,10 @@ gemspec
|
|
|
6
6
|
gem 'activesupport', require: false # For backend
|
|
7
7
|
gem 'dry-inflector', require: false # For inflection
|
|
8
8
|
gem 'ffaker', require: false # For testing
|
|
9
|
-
gem 'inch', require: false # For inline documents
|
|
10
9
|
gem 'minitest', '~> 5.14' # For test
|
|
11
10
|
gem 'rake', '~> 13.0' # For test and automation
|
|
12
11
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
|
13
|
-
gem 'rubocop-minitest', '~> 0.
|
|
12
|
+
gem 'rubocop-minitest', '~> 0.24.0', require: false # For lint
|
|
14
13
|
gem 'rubocop-performance', '~> 1.15.0', require: false # For lint
|
|
15
14
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
|
16
15
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Alba is easy to use because there are only a few methods to remember. It's also
|
|
|
37
37
|
|
|
38
38
|
### Feature rich
|
|
39
39
|
|
|
40
|
-
While Alba's core is simple, it provides additional features when you need them, For example, Alba provides [a way to control circular associations](#circular-associations-control), [
|
|
40
|
+
While Alba's core is simple, it provides additional features when you need them, For example, Alba provides [a way to control circular associations](#circular-associations-control), [root key and association resource name inference](#root-key-and-association-resource-name-inference) and [supports layouts](#layout).
|
|
41
41
|
|
|
42
42
|
## Installation
|
|
43
43
|
|
|
@@ -57,7 +57,7 @@ Or install it yourself as:
|
|
|
57
57
|
|
|
58
58
|
## Supported Ruby versions
|
|
59
59
|
|
|
60
|
-
Alba supports CRuby 2.
|
|
60
|
+
Alba supports CRuby 2.6 and higher and latest JRuby and TruffleRuby.
|
|
61
61
|
|
|
62
62
|
## Documentation
|
|
63
63
|
|
|
@@ -68,10 +68,9 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
|
68
68
|
* Conditional attributes and associations
|
|
69
69
|
* Selectable backend
|
|
70
70
|
* Key transformation
|
|
71
|
-
* Root key inference
|
|
71
|
+
* Root key and association resource name inference
|
|
72
72
|
* Error handling
|
|
73
73
|
* Nil handling
|
|
74
|
-
* Resource name inflection based on association name
|
|
75
74
|
* Circular associations control
|
|
76
75
|
* [Experimental] Types for validation and conversion
|
|
77
76
|
* Layout
|
|
@@ -113,19 +112,33 @@ You can consider setting a backend with Symbol as a shortcut to set encoder.
|
|
|
113
112
|
|
|
114
113
|
#### Inference configuration
|
|
115
114
|
|
|
116
|
-
You can enable inference feature using `
|
|
115
|
+
You can enable the inference feature using the `Alba.inflector = SomeInflector` API. For example, in a Rails initializer:
|
|
117
116
|
|
|
118
117
|
```ruby
|
|
119
|
-
Alba.
|
|
118
|
+
Alba.inflector = :active_support
|
|
120
119
|
```
|
|
121
120
|
|
|
122
|
-
You can choose which inflector Alba uses for inference. Possible
|
|
121
|
+
You can choose which inflector Alba uses for inference. Possible options are:
|
|
123
122
|
|
|
124
123
|
- `:active_support` for `ActiveSupport::Inflector`
|
|
125
124
|
- `:dry` for `Dry::Inflector`
|
|
126
|
-
- any object which
|
|
125
|
+
- any object which conforms to the protocol (see [below](#custom-inflector))
|
|
127
126
|
|
|
128
|
-
|
|
127
|
+
To disable inference, set the `inflector` to `nil`:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
Alba.inflector = nil
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
To check if inference is enabled etc, inspect the return value of `inflector`:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
if Alba.inflector == nil
|
|
137
|
+
puts "inflector not set"
|
|
138
|
+
else
|
|
139
|
+
puts "inflector is set to #{Alba.inflector}"
|
|
140
|
+
end
|
|
141
|
+
```
|
|
129
142
|
|
|
130
143
|
### Simple serialization with root key
|
|
131
144
|
|
|
@@ -157,7 +170,7 @@ end
|
|
|
157
170
|
|
|
158
171
|
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
|
159
172
|
UserResource.new(user).serialize
|
|
160
|
-
# =>
|
|
173
|
+
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}'
|
|
161
174
|
```
|
|
162
175
|
|
|
163
176
|
You can define instance methods on resources so that you can use it as attribute name in `attributes`.
|
|
@@ -184,7 +197,7 @@ This even works with users collection.
|
|
|
184
197
|
user1 = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
|
185
198
|
user2 = User.new(2, 'Test User', 'test@example.com')
|
|
186
199
|
UserResource.new([user1, user2]).serialize
|
|
187
|
-
# =>
|
|
200
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"},{"id":2,"name":"Test User","name_with_email":"Test User: test@example.com"}]}'
|
|
188
201
|
```
|
|
189
202
|
|
|
190
203
|
If you have a simple case where you want to change only the name, you can use the Symbol to Proc shortcut:
|
|
@@ -210,8 +223,8 @@ class UserResource
|
|
|
210
223
|
end
|
|
211
224
|
|
|
212
225
|
user = User.new(1, 'Masa', 'test@example.com')
|
|
213
|
-
UserResource.new(user).serialize # =>
|
|
214
|
-
UserResource.new(user, params: {upcase: true}).serialize # =>
|
|
226
|
+
UserResource.new(user).serialize # => '{"name":"Masa"}'
|
|
227
|
+
UserResource.new(user, params: {upcase: true}).serialize # => '{"name":"MASA"}'
|
|
215
228
|
```
|
|
216
229
|
|
|
217
230
|
### Serialization with associations
|
|
@@ -368,9 +381,11 @@ UserResource.new(user).serialize
|
|
|
368
381
|
# => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
|
|
369
382
|
```
|
|
370
383
|
|
|
371
|
-
You can omit resource option if you enable Alba's inference feature.
|
|
384
|
+
You can omit the resource option if you enable Alba's [inference](#inference-configuration) feature.
|
|
372
385
|
|
|
373
386
|
```ruby
|
|
387
|
+
Alba.inflector = :active_support
|
|
388
|
+
|
|
374
389
|
class UserResource
|
|
375
390
|
include Alba::Resource
|
|
376
391
|
|
|
@@ -390,7 +405,7 @@ class UserResource
|
|
|
390
405
|
|
|
391
406
|
attributes :id
|
|
392
407
|
|
|
393
|
-
many :articles, ->(article) { article.with_comment? ? ArticleWithCommentResource : ArticleResource }
|
|
408
|
+
many :articles, resource: ->(article) { article.with_comment? ? ArticleWithCommentResource : ArticleResource }
|
|
394
409
|
end
|
|
395
410
|
```
|
|
396
411
|
|
|
@@ -564,13 +579,19 @@ end
|
|
|
564
579
|
Foo = Struct.new(:bar, :baz)
|
|
565
580
|
foo = Foo.new(1, 2)
|
|
566
581
|
FooResource.new(foo).serialize # => '{"foo":{"bar":1}}'
|
|
567
|
-
ExtendedFooResource.new(foo).serialize # => '{"
|
|
582
|
+
ExtendedFooResource.new(foo).serialize # => '{"foofoo":{"bar":1,"baz":2}}'
|
|
568
583
|
```
|
|
569
584
|
|
|
570
585
|
In this example we add `baz` attribute and change `root_key`. This way, you can extend existing resource classes just like normal OOP. Don't forget that when your inheritance structure is too deep it'll become difficult to modify existing classes.
|
|
571
586
|
|
|
572
587
|
### Filtering attributes
|
|
573
588
|
|
|
589
|
+
Filtering attributes can be done in two ways - with `attributes` and `select`. They have different semantics and usage.
|
|
590
|
+
|
|
591
|
+
`select` is a new and more intuitive API, so generally it's recommended to use `select`.
|
|
592
|
+
|
|
593
|
+
#### Filtering attributes with `attributes`
|
|
594
|
+
|
|
574
595
|
You can filter out certain attributes by overriding `attributes` instance method. This is useful when you want to customize existing resource with inheritance.
|
|
575
596
|
|
|
576
597
|
You can access raw attributes via `super` call. It returns a Hash whose keys are the name of the attribute and whose values are the body. Usually you need only keys to filter out, like below.
|
|
@@ -598,19 +619,52 @@ class RestrictedFooResource < GenericFooResource
|
|
|
598
619
|
end
|
|
599
620
|
end
|
|
600
621
|
|
|
622
|
+
foo = Foo.new(1, 'my foo', 'body')
|
|
623
|
+
|
|
601
624
|
RestrictedFooResource.new(foo).serialize
|
|
602
625
|
# => '{"name":"my foo"}'
|
|
603
626
|
```
|
|
604
627
|
|
|
605
|
-
|
|
628
|
+
#### Filtering attributes with `select`
|
|
629
|
+
|
|
630
|
+
When you want to filter attributes based on more complex logic, you can use `select` instance method. `select` takes two parameters, the name of an attribute and the value of an attribute. If it returns false that attribute is rejected.
|
|
631
|
+
|
|
632
|
+
```ruby
|
|
633
|
+
class Foo
|
|
634
|
+
attr_accessor :id, :name, :body
|
|
635
|
+
|
|
636
|
+
def initialize(id, name, body)
|
|
637
|
+
@id = id
|
|
638
|
+
@name = name
|
|
639
|
+
@body = body
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
class GenericFooResource
|
|
644
|
+
include Alba::Resource
|
|
645
|
+
|
|
646
|
+
attributes :id, :name, :body
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
class RestrictedFooResource < GenericFooResource
|
|
650
|
+
def select(_key, value)
|
|
651
|
+
!value.nil?
|
|
652
|
+
end
|
|
653
|
+
end
|
|
606
654
|
|
|
607
|
-
|
|
608
|
-
* install it
|
|
609
|
-
* use a [custom inflector](#custom-inflector)
|
|
655
|
+
foo = Foo.new(1, nil, 'body')
|
|
610
656
|
|
|
611
|
-
|
|
657
|
+
RestrictedFooResource.new(foo).serialize
|
|
658
|
+
# => '{"id":1,"body":"body"}'
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Key transformation
|
|
662
|
+
|
|
663
|
+
If you have [inference](#inference-configuration) enabled, you can use the `transform_keys` DSL to transform attribute keys.
|
|
612
664
|
|
|
613
665
|
```ruby
|
|
666
|
+
Alba.inflector = :active_support
|
|
667
|
+
|
|
614
668
|
class User
|
|
615
669
|
attr_reader :id, :first_name, :last_name
|
|
616
670
|
|
|
@@ -646,12 +700,12 @@ Possible values for `transform_keys` argument are:
|
|
|
646
700
|
|
|
647
701
|
You can also transform root key when:
|
|
648
702
|
|
|
649
|
-
* `Alba.
|
|
703
|
+
* `Alba.inflector` is set
|
|
650
704
|
* `root_key!` is called in Resource class
|
|
651
705
|
* `root` option of `transform_keys` is set to true
|
|
652
706
|
|
|
653
707
|
```ruby
|
|
654
|
-
Alba.
|
|
708
|
+
Alba.inflector = :active_support
|
|
655
709
|
|
|
656
710
|
class BankAccount
|
|
657
711
|
attr_reader :account_number
|
|
@@ -675,7 +729,9 @@ BankAccountResource.new(bank_account).serialize
|
|
|
675
729
|
# => '{"bank-account":{"account-number":123456789}}'
|
|
676
730
|
```
|
|
677
731
|
|
|
678
|
-
This
|
|
732
|
+
This is the default behavior from version 2.
|
|
733
|
+
|
|
734
|
+
Find more details in the [Inference configuration](#inference-configuration) section.
|
|
679
735
|
|
|
680
736
|
#### Key transformation cascading
|
|
681
737
|
|
|
@@ -687,7 +743,7 @@ You can also turn it off by setting `cascade: false` option to `transform_keys`.
|
|
|
687
743
|
|
|
688
744
|
```ruby
|
|
689
745
|
class User
|
|
690
|
-
attr_reader :id, :first_name, :last_name
|
|
746
|
+
attr_reader :id, :first_name, :last_name, :bank_account
|
|
691
747
|
|
|
692
748
|
def initialize(id, first_name, last_name)
|
|
693
749
|
@id = id
|
|
@@ -746,7 +802,7 @@ module CustomInflector
|
|
|
746
802
|
end
|
|
747
803
|
end
|
|
748
804
|
|
|
749
|
-
Alba.
|
|
805
|
+
Alba.inflector = CustomInflector
|
|
750
806
|
```
|
|
751
807
|
|
|
752
808
|
### Conditional attributes
|
|
@@ -757,7 +813,7 @@ In these cases, conditional attributes works well. We can pass `if` option to `a
|
|
|
757
813
|
|
|
758
814
|
```ruby
|
|
759
815
|
class User
|
|
760
|
-
attr_accessor :id, :name, :email
|
|
816
|
+
attr_accessor :id, :name, :email
|
|
761
817
|
|
|
762
818
|
def initialize(id, name, email)
|
|
763
819
|
@id = id
|
|
@@ -790,12 +846,12 @@ end
|
|
|
790
846
|
|
|
791
847
|
We believe this is clearer than using some (not implemented yet) DSL such as `default` because there are some conditions where default values should be applied (`nil`, `blank?`, `empty?` etc.)
|
|
792
848
|
|
|
793
|
-
###
|
|
849
|
+
### Root key and association resource name inference
|
|
794
850
|
|
|
795
|
-
|
|
851
|
+
If [inference](#inference-configuration) is enabled, Alba tries to infer the root key and association resource names.
|
|
796
852
|
|
|
797
853
|
```ruby
|
|
798
|
-
Alba.
|
|
854
|
+
Alba.inflector = :active_support
|
|
799
855
|
|
|
800
856
|
class User
|
|
801
857
|
attr_reader :id
|
|
@@ -825,7 +881,7 @@ end
|
|
|
825
881
|
class UserResource
|
|
826
882
|
include Alba::Resource
|
|
827
883
|
|
|
828
|
-
|
|
884
|
+
root_key!
|
|
829
885
|
|
|
830
886
|
attributes :id
|
|
831
887
|
|
|
@@ -843,6 +899,8 @@ This resource automatically sets its root key to either "users" or "user", depen
|
|
|
843
899
|
|
|
844
900
|
Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
|
|
845
901
|
|
|
902
|
+
Find more details in the [Inference configuration](#inference-configuration) section.
|
|
903
|
+
|
|
846
904
|
### Error handling
|
|
847
905
|
|
|
848
906
|
You can set error handler globally or per resource using `on_error`.
|
|
@@ -934,7 +992,7 @@ class UserResource
|
|
|
934
992
|
include Alba::Resource
|
|
935
993
|
|
|
936
994
|
on_nil do |object, key|
|
|
937
|
-
if key == age
|
|
995
|
+
if key == 'age'
|
|
938
996
|
20
|
|
939
997
|
else
|
|
940
998
|
"User#{object.id}"
|
|
@@ -990,7 +1048,7 @@ class UserResourceWithoutMeta
|
|
|
990
1048
|
attributes :id, :name
|
|
991
1049
|
end
|
|
992
1050
|
|
|
993
|
-
|
|
1051
|
+
UserResourceWithoutMeta.new([user]).serialize(meta: {foo: :bar})
|
|
994
1052
|
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"foo":"bar"}}'
|
|
995
1053
|
```
|
|
996
1054
|
|
data/benchmark/collection.rb
CHANGED
|
@@ -1,88 +1,7 @@
|
|
|
1
1
|
# Benchmark script to run varieties of JSON serializers
|
|
2
2
|
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require "bundler/inline"
|
|
7
|
-
|
|
8
|
-
gemfile(true) do
|
|
9
|
-
source "https://rubygems.org"
|
|
10
|
-
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
11
|
-
|
|
12
|
-
gem "active_model_serializers"
|
|
13
|
-
gem "activerecord", "6.1.3"
|
|
14
|
-
gem "alba", path: '../'
|
|
15
|
-
gem "benchmark-ips"
|
|
16
|
-
gem "benchmark-memory"
|
|
17
|
-
gem "blueprinter"
|
|
18
|
-
gem "fast_serializer_ruby"
|
|
19
|
-
gem "jbuilder"
|
|
20
|
-
gem 'turbostreamer'
|
|
21
|
-
gem "jserializer"
|
|
22
|
-
gem "multi_json"
|
|
23
|
-
gem "panko_serializer"
|
|
24
|
-
gem "pg"
|
|
25
|
-
gem "primalize"
|
|
26
|
-
gem "oj"
|
|
27
|
-
gem "representable"
|
|
28
|
-
gem "simple_ams"
|
|
29
|
-
gem "sqlite3"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# --- Test data model setup ---
|
|
33
|
-
|
|
34
|
-
require "pg"
|
|
35
|
-
require "active_record"
|
|
36
|
-
require "active_record/connection_adapters/postgresql_adapter"
|
|
37
|
-
require "logger"
|
|
38
|
-
require "oj"
|
|
39
|
-
require "sqlite3"
|
|
40
|
-
Oj.optimize_rails
|
|
41
|
-
|
|
42
|
-
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
|
43
|
-
# ActiveRecord::Base.logger = Logger.new($stdout)
|
|
44
|
-
|
|
45
|
-
ActiveRecord::Schema.define do
|
|
46
|
-
create_table :posts, force: true do |t|
|
|
47
|
-
t.string :body
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
create_table :comments, force: true do |t|
|
|
51
|
-
t.integer :post_id
|
|
52
|
-
t.string :body
|
|
53
|
-
t.integer :commenter_id
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
create_table :users, force: true do |t|
|
|
57
|
-
t.string :name
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
class Post < ActiveRecord::Base
|
|
62
|
-
has_many :comments
|
|
63
|
-
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
|
64
|
-
|
|
65
|
-
def attributes
|
|
66
|
-
{id: nil, body: nil, commenter_names: commenter_names}
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def commenter_names
|
|
70
|
-
commenters.pluck(:name)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
class Comment < ActiveRecord::Base
|
|
75
|
-
belongs_to :post
|
|
76
|
-
belongs_to :commenter, class_name: 'User'
|
|
77
|
-
|
|
78
|
-
def attributes
|
|
79
|
-
{id: nil, body: nil}
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
class User < ActiveRecord::Base
|
|
84
|
-
has_many :comments
|
|
85
|
-
end
|
|
4
|
+
require_relative 'prep'
|
|
86
5
|
|
|
87
6
|
# --- Alba serializers ---
|
|
88
7
|
|
|
@@ -160,30 +79,6 @@ class FastSerializerPostResource
|
|
|
160
79
|
has_many :comments, serializer: FastSerializerCommentResource
|
|
161
80
|
end
|
|
162
81
|
|
|
163
|
-
# --- JBuilder serializers ---
|
|
164
|
-
|
|
165
|
-
require "jbuilder"
|
|
166
|
-
|
|
167
|
-
class Post
|
|
168
|
-
def to_builder
|
|
169
|
-
Jbuilder.new do |post|
|
|
170
|
-
post.call(self, :id, :body, :commenter_names, :comments)
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def commenter_names
|
|
175
|
-
commenters.pluck(:name)
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
class Comment
|
|
180
|
-
def to_builder
|
|
181
|
-
Jbuilder.new do |comment|
|
|
182
|
-
comment.call(self, :id, :body)
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
82
|
# --- Jserializer serializers ---
|
|
188
83
|
|
|
189
84
|
require 'jserializer'
|
|
@@ -220,31 +115,6 @@ class PankoPostSerializer < Panko::Serializer
|
|
|
220
115
|
end
|
|
221
116
|
end
|
|
222
117
|
|
|
223
|
-
# --- Primalize serializers ---
|
|
224
|
-
#
|
|
225
|
-
class PrimalizeCommentResource < Primalize::Single
|
|
226
|
-
attributes id: integer, body: string
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
class PrimalizePostResource < Primalize::Single
|
|
230
|
-
alias post object
|
|
231
|
-
|
|
232
|
-
attributes(
|
|
233
|
-
id: integer,
|
|
234
|
-
body: string,
|
|
235
|
-
comments: array(primalize(PrimalizeCommentResource)),
|
|
236
|
-
commenter_names: array(string),
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
def commenter_names
|
|
240
|
-
post.commenters.pluck(:name)
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
class PrimalizePostsResource < Primalize::Many
|
|
245
|
-
attributes posts: enumerable(PrimalizePostResource)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
118
|
# --- Representable serializers ---
|
|
249
119
|
|
|
250
120
|
require "representable"
|
|
@@ -349,13 +219,6 @@ end
|
|
|
349
219
|
ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {each_serializer: AMSPostSerializer}).to_json }
|
|
350
220
|
blueprinter = Proc.new { PostBlueprint.render(posts) }
|
|
351
221
|
fast_serializer = Proc.new { FastSerializerPostResource.new(posts).to_json }
|
|
352
|
-
jbuilder = Proc.new do
|
|
353
|
-
Jbuilder.new do |json|
|
|
354
|
-
json.array!(posts) do |post|
|
|
355
|
-
json.post post.to_builder
|
|
356
|
-
end
|
|
357
|
-
end.target!
|
|
358
|
-
end
|
|
359
222
|
jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
|
|
360
223
|
panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
|
|
361
224
|
primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
|
|
@@ -368,22 +231,25 @@ turbostreamer = Proc.new { TurbostreamerSerializer.new(posts).to_json }
|
|
|
368
231
|
|
|
369
232
|
# --- Execute the serializers to check their output ---
|
|
370
233
|
GC.disable
|
|
371
|
-
puts "
|
|
234
|
+
puts "Checking outputs..."
|
|
235
|
+
correct = alba.call
|
|
236
|
+
parsed_correct = JSON.parse(correct)
|
|
372
237
|
{
|
|
373
|
-
alba: alba,
|
|
374
238
|
alba_inline: alba_inline,
|
|
375
239
|
ams: ams,
|
|
376
240
|
blueprinter: blueprinter,
|
|
377
241
|
fast_serializer: fast_serializer,
|
|
378
|
-
jbuilder: jbuilder, # different order
|
|
379
242
|
jserializer: jserializer,
|
|
380
243
|
panko: panko,
|
|
381
|
-
primalize: primalize,
|
|
382
244
|
rails: rails,
|
|
383
245
|
representable: representable,
|
|
384
246
|
simple_ams: simple_ams,
|
|
385
247
|
turbostreamer: turbostreamer
|
|
386
|
-
}.each
|
|
248
|
+
}.each do |name, serializer|
|
|
249
|
+
result = serializer.call
|
|
250
|
+
parsed_result = JSON.parse(result)
|
|
251
|
+
puts "#{name} yields wrong output: #{parsed_result}" unless parsed_result == parsed_correct
|
|
252
|
+
end
|
|
387
253
|
|
|
388
254
|
# --- Run the benchmarks ---
|
|
389
255
|
|
|
@@ -394,10 +260,8 @@ Benchmark.ips do |x|
|
|
|
394
260
|
x.report(:ams, &ams)
|
|
395
261
|
x.report(:blueprinter, &blueprinter)
|
|
396
262
|
x.report(:fast_serializer, &fast_serializer)
|
|
397
|
-
x.report(:jbuilder, &jbuilder)
|
|
398
263
|
x.report(:jserializer, &jserializer)
|
|
399
264
|
x.report(:panko, &panko)
|
|
400
|
-
x.report(:primalize, &primalize)
|
|
401
265
|
x.report(:rails, &rails)
|
|
402
266
|
x.report(:representable, &representable)
|
|
403
267
|
x.report(:simple_ams, &simple_ams)
|
|
@@ -414,10 +278,8 @@ Benchmark.memory do |x|
|
|
|
414
278
|
x.report(:ams, &ams)
|
|
415
279
|
x.report(:blueprinter, &blueprinter)
|
|
416
280
|
x.report(:fast_serializer, &fast_serializer)
|
|
417
|
-
x.report(:jbuilder, &jbuilder)
|
|
418
281
|
x.report(:jserializer, &jserializer)
|
|
419
282
|
x.report(:panko, &panko)
|
|
420
|
-
x.report(:primalize, &primalize)
|
|
421
283
|
x.report(:rails, &rails)
|
|
422
284
|
x.report(:representable, &representable)
|
|
423
285
|
x.report(:simple_ams, &simple_ams)
|
data/benchmark/prep.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# --- Bundle dependencies ---
|
|
2
|
+
|
|
3
|
+
require "bundler/inline"
|
|
4
|
+
|
|
5
|
+
gemfile(true) do
|
|
6
|
+
source "https://rubygems.org"
|
|
7
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
8
|
+
|
|
9
|
+
gem "active_model_serializers"
|
|
10
|
+
gem "activerecord", "6.1.3"
|
|
11
|
+
gem "alba", path: '../'
|
|
12
|
+
gem "benchmark-ips"
|
|
13
|
+
gem "benchmark-memory"
|
|
14
|
+
gem "blueprinter"
|
|
15
|
+
gem "fast_serializer_ruby"
|
|
16
|
+
gem "jbuilder"
|
|
17
|
+
gem 'turbostreamer'
|
|
18
|
+
gem "jserializer"
|
|
19
|
+
gem "multi_json"
|
|
20
|
+
gem "panko_serializer"
|
|
21
|
+
gem "pg"
|
|
22
|
+
gem "primalize"
|
|
23
|
+
gem "oj"
|
|
24
|
+
gem "representable"
|
|
25
|
+
gem "simple_ams"
|
|
26
|
+
gem "sqlite3"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# --- Test data model setup ---
|
|
30
|
+
|
|
31
|
+
require "pg"
|
|
32
|
+
require "active_record"
|
|
33
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
|
34
|
+
require "logger"
|
|
35
|
+
require "oj"
|
|
36
|
+
require "sqlite3"
|
|
37
|
+
Oj.optimize_rails unless ENV['NO_OJ_OPTIMIZE_RAILS']
|
|
38
|
+
|
|
39
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
|
40
|
+
# ActiveRecord::Base.logger = Logger.new($stdout)
|
|
41
|
+
|
|
42
|
+
ActiveRecord::Schema.define do
|
|
43
|
+
create_table :posts, force: true do |t|
|
|
44
|
+
t.string :body
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
create_table :comments, force: true do |t|
|
|
48
|
+
t.integer :post_id
|
|
49
|
+
t.string :body
|
|
50
|
+
t.integer :commenter_id
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
create_table :users, force: true do |t|
|
|
54
|
+
t.string :name
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Post < ActiveRecord::Base
|
|
59
|
+
has_many :comments
|
|
60
|
+
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
|
61
|
+
|
|
62
|
+
def attributes
|
|
63
|
+
{id: nil, body: nil, commenter_names: commenter_names}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def commenter_names
|
|
67
|
+
commenters.pluck(:name)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class Comment < ActiveRecord::Base
|
|
72
|
+
belongs_to :post
|
|
73
|
+
belongs_to :commenter, class_name: 'User'
|
|
74
|
+
|
|
75
|
+
def attributes
|
|
76
|
+
{id: nil, body: nil}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class User < ActiveRecord::Base
|
|
81
|
+
has_many :comments
|
|
82
|
+
end
|
|
@@ -1,88 +1,7 @@
|
|
|
1
1
|
# Benchmark script to run varieties of JSON serializers
|
|
2
2
|
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require "bundler/inline"
|
|
7
|
-
|
|
8
|
-
gemfile(true) do
|
|
9
|
-
source "https://rubygems.org"
|
|
10
|
-
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
11
|
-
|
|
12
|
-
gem "active_model_serializers"
|
|
13
|
-
gem "activerecord", "6.1.3"
|
|
14
|
-
gem "alba", path: '../'
|
|
15
|
-
gem "benchmark-ips"
|
|
16
|
-
gem "benchmark-memory"
|
|
17
|
-
gem "blueprinter"
|
|
18
|
-
gem "jbuilder"
|
|
19
|
-
gem 'turbostreamer'
|
|
20
|
-
gem "jserializer"
|
|
21
|
-
gem "jsonapi-serializer" # successor of fast_jsonapi
|
|
22
|
-
gem "multi_json"
|
|
23
|
-
gem "panko_serializer"
|
|
24
|
-
gem "pg"
|
|
25
|
-
gem "primalize"
|
|
26
|
-
gem "oj"
|
|
27
|
-
gem "representable"
|
|
28
|
-
gem "simple_ams"
|
|
29
|
-
gem "sqlite3"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# --- Test data model setup ---
|
|
33
|
-
|
|
34
|
-
require "pg"
|
|
35
|
-
require "active_record"
|
|
36
|
-
require "active_record/connection_adapters/postgresql_adapter"
|
|
37
|
-
require "logger"
|
|
38
|
-
require "oj"
|
|
39
|
-
require "sqlite3"
|
|
40
|
-
Oj.optimize_rails
|
|
41
|
-
|
|
42
|
-
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
|
43
|
-
# ActiveRecord::Base.logger = Logger.new($stdout)
|
|
44
|
-
|
|
45
|
-
ActiveRecord::Schema.define do
|
|
46
|
-
create_table :posts, force: true do |t|
|
|
47
|
-
t.string :body
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
create_table :comments, force: true do |t|
|
|
51
|
-
t.integer :post_id
|
|
52
|
-
t.string :body
|
|
53
|
-
t.integer :commenter_id
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
create_table :users, force: true do |t|
|
|
57
|
-
t.string :name
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
class Post < ActiveRecord::Base
|
|
62
|
-
has_many :comments
|
|
63
|
-
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
|
64
|
-
|
|
65
|
-
def attributes
|
|
66
|
-
{id: nil, body: nil, commenter_names: commenter_names}
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def commenter_names
|
|
70
|
-
commenters.pluck(:name)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
class Comment < ActiveRecord::Base
|
|
75
|
-
belongs_to :post
|
|
76
|
-
belongs_to :commenter, class_name: 'User'
|
|
77
|
-
|
|
78
|
-
def attributes
|
|
79
|
-
{id: nil, body: nil}
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
class User < ActiveRecord::Base
|
|
84
|
-
has_many :comments
|
|
85
|
-
end
|
|
4
|
+
require_relative 'prep'
|
|
86
5
|
|
|
87
6
|
# --- Alba serializers ---
|
|
88
7
|
|
|
@@ -177,70 +96,8 @@ class JserializerPostSerializer < Jserializer::Base
|
|
|
177
96
|
end
|
|
178
97
|
end
|
|
179
98
|
|
|
180
|
-
|
|
181
|
-
# --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
|
|
182
|
-
|
|
183
|
-
class JsonApiStandardCommentSerializer
|
|
184
|
-
include JSONAPI::Serializer
|
|
185
|
-
|
|
186
|
-
attribute :id
|
|
187
|
-
attribute :body
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
class JsonApiStandardPostSerializer
|
|
191
|
-
include JSONAPI::Serializer
|
|
192
|
-
|
|
193
|
-
# set_type :post # optional
|
|
194
|
-
attribute :id
|
|
195
|
-
attribute :body
|
|
196
|
-
attribute :commenter_names
|
|
197
|
-
|
|
198
|
-
attribute :comments do |post|
|
|
199
|
-
post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
|
|
204
|
-
|
|
205
|
-
# code to convert from JSON:API output to "flat" JSON, like the other serializers build
|
|
206
|
-
class JsonApiSameFormatSerializer
|
|
207
|
-
include JSONAPI::Serializer
|
|
208
|
-
|
|
209
|
-
def as_json(*_options)
|
|
210
|
-
hash = serializable_hash
|
|
211
|
-
|
|
212
|
-
if hash[:data].is_a? Hash
|
|
213
|
-
hash[:data][:attributes]
|
|
214
|
-
|
|
215
|
-
elsif hash[:data].is_a? Array
|
|
216
|
-
hash[:data].pluck(:attributes)
|
|
217
|
-
|
|
218
|
-
elsif hash[:data].nil?
|
|
219
|
-
{ }
|
|
220
|
-
|
|
221
|
-
else
|
|
222
|
-
raise "unexpected data type #{hash[:data].class}"
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
|
|
228
|
-
attribute :id
|
|
229
|
-
attribute :body
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
|
|
233
|
-
attribute :id
|
|
234
|
-
attribute :body
|
|
235
|
-
attribute :commenter_names
|
|
236
|
-
|
|
237
|
-
attribute :comments do |post|
|
|
238
|
-
post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
99
|
# --- Panko serializers ---
|
|
243
|
-
|
|
100
|
+
|
|
244
101
|
require "panko_serializer"
|
|
245
102
|
|
|
246
103
|
class PankoCommentSerializer < Panko::Serializer
|
|
@@ -259,7 +116,7 @@ class PankoPostSerializer < Panko::Serializer
|
|
|
259
116
|
end
|
|
260
117
|
|
|
261
118
|
# --- Primalize serializers ---
|
|
262
|
-
|
|
119
|
+
|
|
263
120
|
class PrimalizeCommentResource < Primalize::Single
|
|
264
121
|
attributes id: integer, body: string
|
|
265
122
|
end
|
|
@@ -376,8 +233,6 @@ ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
|
|
|
376
233
|
blueprinter = Proc.new { PostBlueprint.render(post) }
|
|
377
234
|
jbuilder = Proc.new { post.to_builder.target! }
|
|
378
235
|
jserializer = Proc.new { JserializerPostSerializer.new(post).to_json }
|
|
379
|
-
jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
|
|
380
|
-
jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
|
|
381
236
|
panko = proc { PankoPostSerializer.new.serialize_to_json(post) }
|
|
382
237
|
primalize = proc { PrimalizePostResource.new(post).to_json }
|
|
383
238
|
rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
|
|
@@ -395,8 +250,6 @@ puts "Serializer outputs ----------------------------------"
|
|
|
395
250
|
blueprinter: blueprinter,
|
|
396
251
|
jbuilder: jbuilder, # different order
|
|
397
252
|
jserializer: jserializer,
|
|
398
|
-
jsonapi: jsonapi, # nested JSON:API format
|
|
399
|
-
jsonapi_same_format: jsonapi_same_format,
|
|
400
253
|
panko: panko,
|
|
401
254
|
primalize: primalize,
|
|
402
255
|
rails: rails,
|
|
@@ -417,8 +270,6 @@ Benchmark.ips do |x|
|
|
|
417
270
|
x.report(:blueprinter, &blueprinter)
|
|
418
271
|
x.report(:jbuilder, &jbuilder)
|
|
419
272
|
x.report(:jserializer, &jserializer)
|
|
420
|
-
x.report(:jsonapi, &jsonapi)
|
|
421
|
-
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
|
422
273
|
x.report(:panko, &panko)
|
|
423
274
|
x.report(:primalize, &primalize)
|
|
424
275
|
x.report(:rails, &rails)
|
|
@@ -438,8 +289,6 @@ Benchmark.memory do |x|
|
|
|
438
289
|
x.report(:blueprinter, &blueprinter)
|
|
439
290
|
x.report(:jbuilder, &jbuilder)
|
|
440
291
|
x.report(:jserializer, &jserializer)
|
|
441
|
-
x.report(:jsonapi, &jsonapi)
|
|
442
|
-
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
|
443
292
|
x.report(:panko, &panko)
|
|
444
293
|
x.report(:primalize, &primalize)
|
|
445
294
|
x.report(:rails, &rails)
|
|
@@ -12,7 +12,7 @@ This guide is aimed at helping ActiveModelSerializers users transition to Alba,
|
|
|
12
12
|
|
|
13
13
|
## Example class
|
|
14
14
|
|
|
15
|
-
Example
|
|
15
|
+
Example class is inherited `ActiveRecord::Base`, because [serializing PORO with AMS is pretty hard](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/howto/serialize_poro.md).
|
|
16
16
|
|
|
17
17
|
```rb
|
|
18
18
|
class User < ActiveRecord::Base
|
|
@@ -175,7 +175,7 @@ class UserSerializer < ActiveModel::Serializer
|
|
|
175
175
|
has_many :articles, serializer: ArticleSerializer # For has_many relation
|
|
176
176
|
end
|
|
177
177
|
user = User.create!
|
|
178
|
-
user.
|
|
178
|
+
user.create_profile! email: email
|
|
179
179
|
user.articles.create! title: title, body: body
|
|
180
180
|
ActiveModelSerializers::SerializableResource.new(
|
|
181
181
|
user
|
data/docs/rails.md
CHANGED
|
@@ -14,7 +14,7 @@ You might want to add some configurations to initializer file such as `alba.rb`
|
|
|
14
14
|
```ruby
|
|
15
15
|
# alba.rb
|
|
16
16
|
Alba.backend = :active_support
|
|
17
|
-
Alba.enable_inference!(:active_support)
|
|
17
|
+
Alba.enable_inference!(with: :active_support)
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
You can also use `:oj_rails` for backend if you prefer using Oj.
|
data/lib/alba/association.rb
CHANGED
|
@@ -37,8 +37,10 @@ module Alba
|
|
|
37
37
|
@object = @condition.call(object, params, target) if @condition
|
|
38
38
|
return if @object.nil?
|
|
39
39
|
|
|
40
|
-
if @resource.is_a?(Proc)
|
|
41
|
-
to_h_with_each_resource(within, params)
|
|
40
|
+
if @resource.is_a?(Proc)
|
|
41
|
+
return to_h_with_each_resource(within, params) if @object.is_a?(Enumerable)
|
|
42
|
+
|
|
43
|
+
@resource.call(@object).new(@object, within: within, params: params).to_h
|
|
42
44
|
else
|
|
43
45
|
to_h_with_constantize_resource(within, params)
|
|
44
46
|
end
|
|
@@ -63,10 +65,10 @@ module Alba
|
|
|
63
65
|
klass.transform_keys(key_transformation)
|
|
64
66
|
klass.class_eval(&block)
|
|
65
67
|
klass
|
|
66
|
-
elsif Alba.
|
|
68
|
+
elsif Alba.inflector
|
|
67
69
|
Alba.infer_resource_class(@name, nesting: nesting)
|
|
68
70
|
else
|
|
69
|
-
raise ArgumentError, 'When Alba.
|
|
71
|
+
raise ArgumentError, 'When Alba.inflector is nil, either resource or block is required'
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
|
|
@@ -20,7 +20,7 @@ module Alba
|
|
|
20
20
|
return Alba::REMOVE_KEY unless condition_passes?(resource, object)
|
|
21
21
|
|
|
22
22
|
fetched_attribute = yield(@body)
|
|
23
|
-
return fetched_attribute if
|
|
23
|
+
return fetched_attribute if !with_two_arity_proc_condition
|
|
24
24
|
|
|
25
25
|
return Alba::REMOVE_KEY unless resource.instance_exec(object, attribute_from_association_body_or(fetched_attribute), &@condition)
|
|
26
26
|
|
data/lib/alba/resource.rb
CHANGED
|
@@ -41,7 +41,6 @@ module Alba
|
|
|
41
41
|
@object = object
|
|
42
42
|
@params = params
|
|
43
43
|
@within = within
|
|
44
|
-
@method_existence = {} # Cache for `respond_to?` result
|
|
45
44
|
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.__send__(name)) }
|
|
46
45
|
end
|
|
47
46
|
|
|
@@ -149,7 +148,7 @@ module Alba
|
|
|
149
148
|
end
|
|
150
149
|
|
|
151
150
|
def _key_for_collection
|
|
152
|
-
if Alba.
|
|
151
|
+
if Alba.inflector
|
|
153
152
|
@_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection.to_s
|
|
154
153
|
else
|
|
155
154
|
@_key_for_collection == true ? raise_root_key_inference_error : @_key_for_collection.to_s
|
|
@@ -158,7 +157,7 @@ module Alba
|
|
|
158
157
|
|
|
159
158
|
# @return [String]
|
|
160
159
|
def _key
|
|
161
|
-
if Alba.
|
|
160
|
+
if Alba.inflector
|
|
162
161
|
@_key == true ? resource_name(pluralized: false) : @_key.to_s
|
|
163
162
|
else
|
|
164
163
|
@_key == true ? raise_root_key_inference_error : @_key.to_s
|
|
@@ -174,7 +173,7 @@ module Alba
|
|
|
174
173
|
end
|
|
175
174
|
|
|
176
175
|
def raise_root_key_inference_error
|
|
177
|
-
raise Alba::Error, 'You must
|
|
176
|
+
raise Alba::Error, 'You must set inflector when setting root key as true.'
|
|
178
177
|
end
|
|
179
178
|
|
|
180
179
|
def transforming_root_key?
|
|
@@ -182,27 +181,27 @@ module Alba
|
|
|
182
181
|
end
|
|
183
182
|
|
|
184
183
|
def converter
|
|
185
|
-
lambda do |
|
|
186
|
-
attributes_to_hash(
|
|
184
|
+
lambda do |obj|
|
|
185
|
+
attributes_to_hash(obj, {})
|
|
187
186
|
end
|
|
188
187
|
end
|
|
189
188
|
|
|
190
189
|
def collection_converter
|
|
191
|
-
lambda do |
|
|
190
|
+
lambda do |obj, a|
|
|
192
191
|
a << {}
|
|
193
192
|
h = a.last
|
|
194
|
-
attributes_to_hash(
|
|
193
|
+
attributes_to_hash(obj, h)
|
|
195
194
|
a
|
|
196
195
|
end
|
|
197
196
|
end
|
|
198
197
|
|
|
199
|
-
def attributes_to_hash(
|
|
198
|
+
def attributes_to_hash(obj, hash)
|
|
200
199
|
attributes.each do |key, attribute|
|
|
201
|
-
set_key_and_attribute_body_from(
|
|
200
|
+
set_key_and_attribute_body_from(obj, key, attribute, hash)
|
|
202
201
|
rescue ::Alba::Error, FrozenError, TypeError
|
|
203
202
|
raise
|
|
204
203
|
rescue StandardError => e
|
|
205
|
-
handle_error(e,
|
|
204
|
+
handle_error(e, obj, key, attribute, hash)
|
|
206
205
|
end
|
|
207
206
|
hash
|
|
208
207
|
end
|
|
@@ -213,20 +212,28 @@ module Alba
|
|
|
213
212
|
@_attributes
|
|
214
213
|
end
|
|
215
214
|
|
|
216
|
-
|
|
215
|
+
# Default implementation for selecting attributes
|
|
216
|
+
# Override this method to filter attributes based on key and value
|
|
217
|
+
def select(_key, _value)
|
|
218
|
+
true
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def set_key_and_attribute_body_from(obj, key, attribute, hash)
|
|
217
222
|
key = transform_key(key)
|
|
218
|
-
value = fetch_attribute(
|
|
223
|
+
value = fetch_attribute(obj, key, attribute)
|
|
224
|
+
return unless select(key, value)
|
|
225
|
+
|
|
219
226
|
hash[key] = value unless value == Alba::REMOVE_KEY
|
|
220
227
|
end
|
|
221
228
|
|
|
222
|
-
def handle_error(error,
|
|
229
|
+
def handle_error(error, obj, key, attribute, hash)
|
|
223
230
|
on_error = @_on_error || :raise
|
|
224
231
|
case on_error # rubocop:disable Style/MissingElse
|
|
225
232
|
when :raise, nil then raise(error)
|
|
226
233
|
when :nullify then hash[key] = nil
|
|
227
234
|
when :ignore then nil
|
|
228
235
|
when Proc
|
|
229
|
-
key, value = on_error.call(error,
|
|
236
|
+
key, value = on_error.call(error, obj, key, attribute, self.class)
|
|
230
237
|
hash[key] = value
|
|
231
238
|
end
|
|
232
239
|
end
|
|
@@ -237,7 +244,7 @@ module Alba
|
|
|
237
244
|
return key if @_transform_type == :none || key.empty? # We can skip transformation
|
|
238
245
|
|
|
239
246
|
inflector = Alba.inflector
|
|
240
|
-
raise Alba::Error, 'Inflector is nil. You
|
|
247
|
+
raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
|
|
241
248
|
|
|
242
249
|
case @_transform_type # rubocop:disable Style/MissingElse
|
|
243
250
|
when :camel then inflector.camelize(key)
|
|
@@ -247,23 +254,23 @@ module Alba
|
|
|
247
254
|
end
|
|
248
255
|
end
|
|
249
256
|
|
|
250
|
-
def fetch_attribute(
|
|
257
|
+
def fetch_attribute(obj, key, attribute) # rubocop:disable Metrics/CyclomaticComplexity
|
|
251
258
|
value = case attribute
|
|
252
|
-
when Symbol then fetch_attribute_from_object_and_resource(
|
|
253
|
-
when Proc then instance_exec(
|
|
254
|
-
when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(
|
|
255
|
-
when TypedAttribute, NestedAttribute then attribute.value(
|
|
256
|
-
when ConditionalAttribute then attribute.with_passing_condition(resource: self, object:
|
|
259
|
+
when Symbol then fetch_attribute_from_object_and_resource(obj, attribute)
|
|
260
|
+
when Proc then instance_exec(obj, &attribute)
|
|
261
|
+
when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
|
|
262
|
+
when TypedAttribute, NestedAttribute then attribute.value(obj)
|
|
263
|
+
when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
|
|
257
264
|
else
|
|
258
265
|
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
|
259
266
|
end
|
|
260
|
-
value.nil? && nil_handler ? instance_exec(
|
|
267
|
+
value.nil? && nil_handler ? instance_exec(obj, key, attribute, &nil_handler) : value
|
|
261
268
|
end
|
|
262
269
|
|
|
263
|
-
def fetch_attribute_from_object_and_resource(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
270
|
+
def fetch_attribute_from_object_and_resource(obj, attribute)
|
|
271
|
+
obj.__send__(attribute)
|
|
272
|
+
rescue NoMethodError
|
|
273
|
+
__send__(attribute, obj)
|
|
267
274
|
end
|
|
268
275
|
|
|
269
276
|
def nil_handler
|
|
@@ -313,7 +320,7 @@ module Alba
|
|
|
313
320
|
# @param attrs_with_types [Hash<[Symbol, String], [Array<Symbol, Proc>, Symbol]>]
|
|
314
321
|
# attributes with name in its key and type and optional type converter in its value
|
|
315
322
|
# @return [void]
|
|
316
|
-
def attributes(*attrs, if: nil, **attrs_with_types)
|
|
323
|
+
def attributes(*attrs, if: nil, **attrs_with_types)
|
|
317
324
|
if_value = binding.local_variable_get(:if)
|
|
318
325
|
assign_attributes(attrs, if_value)
|
|
319
326
|
assign_attributes_with_types(attrs_with_types, if_value)
|
data/lib/alba/version.rb
CHANGED
data/lib/alba.rb
CHANGED
|
@@ -7,10 +7,10 @@ require_relative 'alba/deprecation'
|
|
|
7
7
|
# Core module
|
|
8
8
|
module Alba
|
|
9
9
|
class << self
|
|
10
|
-
attr_reader :backend, :encoder
|
|
10
|
+
attr_reader :backend, :encoder
|
|
11
11
|
|
|
12
|
-
#
|
|
13
|
-
|
|
12
|
+
# Getter for inflector, a module responsible for inflecting strings
|
|
13
|
+
attr_reader :inflector
|
|
14
14
|
|
|
15
15
|
# Set the backend, which actually serializes object into JSON
|
|
16
16
|
#
|
|
@@ -54,14 +54,36 @@ module Alba
|
|
|
54
54
|
# @param with [Symbol, Class, Module] inflector
|
|
55
55
|
# When it's a Symbol, it sets inflector with given name
|
|
56
56
|
# When it's a Class or a Module, it sets given object to inflector
|
|
57
|
+
# @deprecated Use {#inflector=} instead
|
|
57
58
|
def enable_inference!(with:)
|
|
59
|
+
Alba::Deprecation.warn('Alba.enable_inference! is deprecated. Use `Alba.inflector=` instead.')
|
|
58
60
|
@inflector = inflector_from(with)
|
|
59
61
|
@inferring = true
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
# Disable inference for key and resource name
|
|
65
|
+
#
|
|
66
|
+
# @deprecated Use {#inflector=} instead
|
|
63
67
|
def disable_inference!
|
|
68
|
+
Alba::Deprecation.warn('Alba.disable_inference! is deprecated. Use `Alba.inflector = nil` instead.')
|
|
64
69
|
@inferring = false
|
|
70
|
+
@inflector = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @deprecated Use {#inflector} instead
|
|
74
|
+
# @return [Boolean] whether inference is enabled or not
|
|
75
|
+
def inferring
|
|
76
|
+
Alba::Deprecation.warn('Alba.inferring is deprecated. Use `Alba.inflector` instead.')
|
|
77
|
+
@inferring
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Set an inflector
|
|
81
|
+
#
|
|
82
|
+
# @param inflector [Symbol, Class, Module] inflector
|
|
83
|
+
# When it's a Symbol, it accepts `:default`, `:active_support` or `:dry`
|
|
84
|
+
# When it's a Class or a Module, it should have some methods, see {Alba::DefaultInflector}
|
|
85
|
+
def inflector=(inflector)
|
|
86
|
+
@inflector = inflector_from(inflector)
|
|
65
87
|
end
|
|
66
88
|
|
|
67
89
|
# @param block [Block] resource body
|
|
@@ -77,7 +99,7 @@ module Alba
|
|
|
77
99
|
# @param nesting [String, nil] namespace Alba tries to find resource class in
|
|
78
100
|
# @return [Class<Alba::Resource>] resource class
|
|
79
101
|
def infer_resource_class(name, nesting: nil)
|
|
80
|
-
raise Alba::Error, 'Inference is disabled so Alba cannot infer resource name.
|
|
102
|
+
raise Alba::Error, 'Inference is disabled so Alba cannot infer resource name. Set inflector before use.' unless Alba.inflector
|
|
81
103
|
|
|
82
104
|
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
|
83
105
|
const_parent.const_get("#{inflector.classify(name)}Resource")
|
|
@@ -95,6 +117,7 @@ module Alba
|
|
|
95
117
|
|
|
96
118
|
def inflector_from(name_or_module)
|
|
97
119
|
case name_or_module
|
|
120
|
+
when nil then nil
|
|
98
121
|
when :default, :active_support
|
|
99
122
|
require_relative 'alba/default_inflector'
|
|
100
123
|
Alba::DefaultInflector
|
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: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OKURA Masafumi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-12-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
|
|
14
14
|
flexibility and usability.
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- alba.gemspec
|
|
41
41
|
- benchmark/README.md
|
|
42
42
|
- benchmark/collection.rb
|
|
43
|
+
- benchmark/prep.rb
|
|
43
44
|
- benchmark/single_resource.rb
|
|
44
45
|
- bin/console
|
|
45
46
|
- bin/setup
|